diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000000..ec083d68034 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = W293,E301,E271,E265,W291,E722,E302,C901,E225,E128,E122,E226,E231 +max-line-length = 160 +exclude = tests/* +max-complexity = 10 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..c0f2c44220e --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,21 @@ +# When making commits that are strictly formatting/style changes, add the +# commit hash here, so git blame can ignore the change. See docs for more +# details: +# https://git-scm.com/docs/git-config#Documentation/git-config.txt-blameignoreRevsFile +# +# +# You should be able to execute either +# ./tools/configure-git-blame-ignore-revs.bat or +# ./tools/configure-git-blame-ignore-revs.sh +# +# Example entries: +# +# # initial black-format +# # rename something internal +6e748726282d1acb9a4f9f264ee679c474c4b8f5 # Apply pygrade --36plus on IPython/core/tests/test_inputtransformer.py. +0233e65d8086d0ec34acb8685b7a5411633f0899 # apply pyupgrade to IPython/extensions/tests/test_autoreload.py +a6a7e4dd7e51b892147895006d3a2a6c34b79ae6 # apply black to IPython/extensions/tests/test_autoreload.py +c5ca5a8f25432dfd6b9eccbbe446a8348bf37cfa # apply pyupgrade to IPython/extensions/autoreload.py +50624b84ccdece781750f5eb635a9efbf2fe30d6 # apply black to IPython/extensions/autoreload.py +b7aaa47412b96379198705955004930c57f9d74a # apply pyupgrade to IPython/extensions/autoreload.py +9c7476a88af3e567426b412f1b3c778401d8f6aa # apply black to IPython/extensions/autoreload.py diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..a657c69813a --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,16 @@ +--- +name: Bug report / Question / Feature +about: Anything related to IPython itsel +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..d1fed9f3d5f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..565999217a9 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,46 @@ +name: Build docs + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: 3.x + cache: pip + cache-dependency-path: | + docs/requirements.txt + pyproject.toml + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + - name: Install Graphviz + run: | + sudo apt-get update + sudo apt-get install graphviz + - name: Install Python dependencies + run: | + uv pip install --system setuptools coverage + uv pip install --system -r docs/requirements.txt + - name: Build docs + run: | + python tools/fixup_whats_new_pr.py + make -C docs/ html SPHINXOPTS="-W" \ + PYTHON="coverage run -a" \ + SPHINXBUILD="coverage run -a -m sphinx.cmd.build" + - name: Generate coverage xml + run: | + coverage combine `find . -name .coverage\*` && coverage xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + with: + name: Docs diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml new file mode 100644 index 00000000000..b2c7e5cf973 --- /dev/null +++ b/.github/workflows/downstream.yml @@ -0,0 +1,101 @@ +name: Run Downstream tests + +on: + push: + paths-ignore: + - 'docs/**' + - '**.md' + - '**.rst' + pull_request: + paths-ignore: + - 'docs/**' + - '**.md' + - '**.rst' + # Run weekly on Monday at 1:23 UTC + schedule: + - cron: '23 1 * * 1' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ${{ matrix.os }} + # Disable scheduled CI runs on forks + if: github.event_name != 'schedule' || github.repository_owner == 'ipython' + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.13"] + include: + - os: macos-14 + python-version: "3.13" + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: ${{ matrix.python-version }} + - name: Update Python installer + run: | + python -m pip install --upgrade pip setuptools wheel + - name: Install ipykernel + run: | + cd .. + git clone https://github.com/ipython/ipykernel + cd ipykernel + pip install -e . --group test + cd .. + - name: Install and update Python dependencies + run: | + python -m pip install --upgrade -e file://$PWD#egg=ipython[test] + # we must install IPython after ipykernel to get the right versions. + python -m pip install --upgrade --upgrade-strategy eager flaky ipyparallel + - name: pytest ipykernel + env: + COLUMNS: 120 + run: | + cd ../ipykernel + pytest + - name: Install sagemath-repl + run: | + # Sept 2024, sage has been failing for a while, + # Skipping. + # cd .. + # git clone --depth 1 https://github.com/sagemath/sage + # cd sage + # # We cloned it for the tests, but for simplicity we install the + # # wheels from PyPI. + # # (Avoid 10.3b6 because of https://github.com/sagemath/sage/pull/37178) + # pip install --pre sagemath-repl sagemath-environment + # # Install optionals that make more tests pass + # pip install pillow + # pip install --pre sagemath-categories + # cd .. + - name: Test sagemath-repl + run: | + # cd ../sage/ + # # From https://github.com/sagemath/sage/blob/develop/pkgs/sagemath-repl/tox.ini + # sage-runtests -p --environment=sage.all__sagemath_repl --baseline-stats-path=pkgs/sagemath-repl/known-test-failures.json --initial --optional=sage src/sage/repl src/sage/doctest src/sage/misc/sage_input.py src/sage/misc/sage_eval.py + - name: Install pyflyby + run: | + cd .. + git clone https://github.com/deshaw/pyflyby + cd pyflyby + pip install meson-python meson ninja pybind11>=2.10.4 setuptools-scm + pip install setuptools wheel # needed for epydoc + pip install --no-build-isolation -ve .[test] + pip install 'pytest<=8' + cd .. + - name: Test pyflyby (IPython integration only) + run: | + cd ../pyflyby + pytest tests/test_interactive.py --deselect tests/test_interactive.py::test_debug_namespace_1_py3[prompt_toolkit] --deselect tests/test_interactive.py::test_run_separate_script_namespace_2 --deselect tests/test_interactive.py::test_autoimport_multiline_continued_statement_fake_1 --deselect tests/test_interactive.py::test_debug_second_1 --deselect tests/test_interactive.py::test_error_during_completion_1 diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 00000000000..ee96622af7f --- /dev/null +++ b/.github/workflows/mypy.yml @@ -0,0 +1,61 @@ +name: Run MyPy + +on: + push: + branches: [ main, 7.x] + pull_request: + branches: [ main, 7.x] + +permissions: + contents: read + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + python-version: ["3.14"] + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: ${{ matrix.python-version }} + cache: pip + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + - name: Install dependencies + shell: bash + run: | + uv pip install --system mypy pyflakes flake8 '.[all]' + - name: Lint with mypy + shell: bash + run: | + set -e + mypy IPython + - name: Lint with mypy (cross-platform typeshed checks) + if: matrix.os == 'ubuntu-latest' + shell: bash + run: | + set -e + mypy --platform linux IPython + mypy --platform darwin IPython + mypy --platform win32 IPython + - name: Lint with mypy (win32 typeshed check) + if: matrix.os == 'windows-latest' + shell: bash + run: | + set -e + mypy --platform win32 IPython + - name: Lint with pyflakes + shell: bash + run: | + set -e + flake8 IPython/core/magics/script.py + flake8 IPython/core/magics/packaging.py diff --git a/.github/workflows/nightly-wheel-build.yml b/.github/workflows/nightly-wheel-build.yml new file mode 100644 index 00000000000..8514c6743c8 --- /dev/null +++ b/.github/workflows/nightly-wheel-build.yml @@ -0,0 +1,41 @@ +name: Nightly Wheel builder +on: + workflow_dispatch: + schedule: + # this cron is ran every Sunday at midnight UTC + - cron: '0 0 * * 0' + +permissions: + contents: read + +jobs: + upload_anaconda: + name: Upload to Anaconda + runs-on: ubuntu-latest + # The artifacts cannot be uploaded on PRs, also disable scheduled CI runs on forks + if: github.event_name != 'pull_request' && (github.event_name != 'schedule' || github.repository_owner == 'ipython') + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: "3.14" + cache: pip + cache-dependency-path: | + pyproject.toml + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + - name: Try building with Python build + if: runner.os != 'Windows' # setup.py does not support sdist on Windows + run: | + uv pip install --system build + python -m build + + - name: Upload wheel + uses: scientific-python/upload-nightly-action@16fa02eacee1655195143de09f03676e60ef2bf5 # 0.6.5 + with: + artifacts_path: dist + anaconda_nightly_upload_token: ${{secrets.UPLOAD_TOKEN}} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000000..4b2ef06f3f0 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,79 @@ +name: Build and Publish IPython + +on: + push: + tags: + - '*' + workflow_dispatch: + +jobs: + build-and-publish: + name: Build and Publish to PyPI + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/ipython + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: "3.14" + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + python -m pip install build + + - name: Build distribution + run: python -m build + + - name: Verify built version matches tag + if: startsWith(github.ref, 'refs/tags/') + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Tag name: $TAG_NAME" + + # Check dist folder filenames + echo "Built distribution files:" + ls -la dist/ + + # Install the built wheel + python -m pip install dist/*.whl + + # Get IPython version + IPYTHON_VERSION=$(ipython --version) + echo "Installed IPython version: $IPYTHON_VERSION" + + # Compare versions (allow only X.Y.Z) + if [[ "$TAG_NAME" != "$IPYTHON_VERSION" ]]; then + echo "Error: Tag ($TAG_NAME) does not match built IPython version ($IPYTHON_VERSION)" + exit 1 + fi + + echo "Version check passed! Tag matches built version." + + - name: Publish distribution to PyPI + if: startsWith(github.ref, 'refs/tags/') + uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3 + + - name: Send Zulip notification + if: startsWith(github.ref, 'refs/tags/') + uses: zulip/github-actions-zulip/send-message@f675f2b4eb2a95fae974215476dcb7ad8dfeff6b # v2.0.2 + with: + api-key: ${{ secrets.ZULIP_API_KEY }} + email: ${{ secrets.ZULIP_EMAIL }} + organization-url: ${{ vars.ZULIP_ORGANIZATION_URL }} + to: 'Releases' + type: 'stream' + topic: 'IPython' + content: | + IPython ${{ github.ref_name }} was just released on PyPI! 🎉 + https://pypi.org/project/ipython/${{ github.ref_name }}/ + and what's new: https://ipython.readthedocs.io/en/stable/whatsnew/version9.html diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 00000000000..e0f6db56a43 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,43 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +permissions: + contents: read + +on: + push: + branches: [ main, 7.x, 8.x ] + pull_request: + branches: [ main, 7.x, 8.x ] + +jobs: + formatting: + + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: 3.x + cache: pip + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + - name: Install dependencies + run: | + # when changing the versions please update CONTRIBUTING.md too + uv pip install --system darker==3.0.0 ruff + - name: Lint with darker + run: | + darker --formatter=ruff -r f51c0b1b6b8e48e228a4eaf62dda382f7e4ba0da --check --diff . || ( + echo "Changes need auto-formatting. Run:" + echo " darker --formatter=ruff -r f51c0b1b6b8e48e228a4eaf62dda382f7e4ba0da ." + echo "then commit and push changes to fix." + exit 1 + ) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 00000000000..67b90a4484f --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,37 @@ +name: Run Ruff + +on: + push: + branches: [ main, 7.x, 8.x] + pull_request: + branches: [ main, 7.x, 8.x] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.x"] + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: ${{ matrix.python-version }} + cache: pip + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + - name: Install dependencies + run: | + uv pip install --system ruff + - name: Lint with ruff + run: | + set -e + ruff check . diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000000..b98c99d89ac --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,169 @@ +name: Run tests + +on: + push: + branches: + - main + - '*.x' + pull_request: + # Run weekly on Monday at 1:23 UTC + schedule: + - cron: '23 1 * * 1' + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + # Disable scheduled CI runs on forks + if: github.event_name != 'schedule' || github.repository_owner == 'ipython' + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"] + deps: [test_extra] + # Test all on ubuntu, test ends on macos + include: + - os: macos-latest + python-version: "3.11" + deps: test_extra + # free threaded, not with all dependencies + - os: ubuntu-latest + python-version: "3.14t" + deps: test + # Tests latest development Python version, re-enable once setup-python provides it + # - os: ubuntu-latest + # python-version: "3.16-dev" + # deps: test + - os: ubuntu-latest + python-version: "3.12" + deps: test_extra + want-latest-entry-point-code: true + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: | + pyproject.toml + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + - name: Install latex + if: runner.os == 'Linux' && matrix.deps == 'test_extra' + run: echo "disable latex for now, issues in mirros" #sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng + - name: Install and update Python dependencies (binary only) + if: ${{ ! contains( matrix.python-version, 'dev' ) }} + run: | + uv pip install --system setuptools wheel build + uv pip install --system -e .[${{ matrix.deps }}] + uv pip install --system check-manifest pytest-cov pytest + - name: Install and update Python dependencies (dev?) + if: ${{ contains( matrix.python-version, 'dev' ) }} + run: | + uv pip install --system --prerelease=allow setuptools wheel build + uv pip install --system --prerelease=allow --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple -e .[${{ matrix.deps }}] + uv pip install --system --prerelease=allow --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple check-manifest pytest-cov + - name: Try building with Python build + if: runner.os != 'Windows' # setup.py does not support sdist on Windows + run: | + python -m build + shasum -a 256 dist/* + - name: Check manifest + if: runner.os != 'Windows' # setup.py does not support sdist on Windows + run: check-manifest + + - name: Install entry point compatible code (TEMPORARY, April 2024) + if: matrix.want-latest-entry-point-code + run: | + uv pip list --system + # Not installing matplotlib's entry point code as building matplotlib from source is complex. + # Rely upon matplotlib to test all the latest entry point branches together. + uv pip install --system git+https://github.com/ipython/matplotlib-inline.git@main + uv pip list --system + + - name: Cache pytest last-failed + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .pytest_cache/v/cache/lastfailed + key: pytest-lastfailed-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.deps }}-${{ github.run_id }} + restore-keys: | + pytest-lastfailed-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.deps }}- + + - name: pytest + env: + COLUMNS: 120 + run: | + pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }} --ff --maxfail=5 + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + name: Test + files: /home/runner/work/ipython/ipython/coverage.xml + # Both flags are attached to the same report; each flag's + # `paths` filter in codecov.yml carves out its bucket. + flags: library,unit-tests + + oldest-deps: + # pro-actively check backward compatibility + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + # Disable scheduled CI runs on forks + if: github.event_name != 'schedule' || github.repository_owner == 'ipython' + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + # include windows because of platform-specific direct dependencies + - windows-latest + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up uv with Python 3.11 + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + with: + python-version: '3.11' + enable-cache: true + activate-environment: true + prune-cache: false + cache-dependency-glob: | + pyproject.toml + + - name: Install Python dependencies (oldest supported versions) + run: uv pip install --resolution=lowest-direct -e .[test] + + - name: Try building with uv build + if: runner.os != 'Windows' # setup.py does not support sdist on Windows + run: | + uv build + shasum -a 256 dist/* + + - name: Check manifest + if: runner.os != 'Windows' # setup.py does not support sdist on Windows + run: uvx check-manifest + + - name: Cache pytest last-failed + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .pytest_cache/v/cache/lastfailed + key: pytest-lastfailed-oldest-deps-${{ matrix.os }}-${{ github.run_id }} + restore-keys: | + pytest-lastfailed-oldest-deps-${{ matrix.os }}- + + - name: pytest + env: + COLUMNS: 120 + run: pytest --color=yes -raXxs --ff --maxfail=5 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 00000000000..29dbfd34c3b --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,39 @@ +name: GitHub Actions Security Analysis with zizmor + +on: + push: + branches: [ main, 7.x, 8.x ] + pull_request: + branches: [ main, 7.x, 8.x ] + +permissions: + contents: read + +jobs: + zizmor: + runs-on: ubuntu-latest + permissions: + contents: read + # Needed to upload the results to the code-scanning dashboard. + security-events: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + - name: Run zizmor + id: zizmor + run: uvx zizmor --format=sarif . > results.sarif + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload SARIF results + if: always() + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + sarif_file: results.sarif + category: zizmor + - name: Fail if zizmor found problems + if: steps.zizmor.outcome == 'failure' + run: exit 1 diff --git a/.github/workflows/zulip.yaml b/.github/workflows/zulip.yaml new file mode 100644 index 00000000000..e14cdeba80e --- /dev/null +++ b/.github/workflows/zulip.yaml @@ -0,0 +1,32 @@ +name: Post message to Zulip + +on: + workflow_dispatch: + inputs: + message: + description: 'Message to post to Zulip' + required: false + default: 'Test Auto release notification of IPython from GitHub action' + type: string + +permissions: + contents: read + +jobs: + post-message: + name: Post Message to Zulip + runs-on: ubuntu-latest + + steps: + + - name: Send Zulip notification + uses: zulip/github-actions-zulip/send-message@f675f2b4eb2a95fae974215476dcb7ad8dfeff6b # v2.0.2 + with: + api-key: ${{ secrets.ORG_ZULIP_API_KEY }} + email: ${{ secrets.ORG_ZULIP_EMAIL }} + organization-url: ${{ secrets.ORG_ZULIP_ORGANIZATION_URL }} + to: 'Releases' + type: 'stream' + topic: 'IPython' + content: | + ${{ inputs.message }} diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 00000000000..74257cf3ffe --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,9 @@ +# Configuration for zizmor (https://docs.zizmor.sh) +# Require actions to be pinned to a full-length commit hash, so a moved tag or +# branch can never silently change which code runs in CI. The version each +# hash corresponds to is kept as a trailing comment on every `uses:`. +rules: + unpinned-uses: + config: + policies: + "*": hash-pin diff --git a/.gitignore b/.gitignore index f3141570400..894a46681ce 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,9 @@ docs/man/*.gz docs/source/api/generated docs/source/config/options docs/source/config/shortcuts/*.csv +docs/source/config/shortcuts/table.tsv +docs/source/savefig docs/source/interactive/magics-generated.txt -docs/source/config/shortcuts/*.csv docs/gh-pages jupyter_notebook/notebook/static/mathjax jupyter_notebook/static/style/*.map @@ -24,6 +25,20 @@ __pycache__ .cache .coverage *.swp -.vscode .pytest_cache .python-version +.venv*/ +venv*/ +.mypy_cache/ + +# jetbrains ide stuff +*.iml +.idea/ + +# vscode ide stuff +*.code-workspace +.history +.vscode + +# MonkeyType runtime type trace database +monkeytype.sqlite3 diff --git a/.mailmap b/.mailmap index 4123a50f6d5..be5b40caa4e 100644 --- a/.mailmap +++ b/.mailmap @@ -1,6 +1,10 @@ A. J. Holyoake ajholyoake +Alok Singh Alok Singh <8325708+alok@users.noreply.github.com> Aaron Culich Aaron Culich Aron Ahmadia ahmadia +Arthur Svistunov <18216480+madbird1304@users.noreply.github.com> +Arthur Svistunov <18216480+madbird1304@users.noreply.github.com> +Adam Hackbarth Benjamin Ragan-Kelley Benjamin Ragan-Kelley Min RK Benjamin Ragan-Kelley MinRK @@ -13,6 +17,8 @@ Brian E. Granger Brian Granger Brian E. Granger Brian Granger <> Brian E. Granger bgranger <> Brian E. Granger bgranger +Blazej Michalik <6691643+MrMino@users.noreply.github.com> +Blazej Michalik Christoph Gohlke cgohlke Cyrille Rossant rossant Damián Avila damianavila @@ -25,8 +31,9 @@ Dav Clark Dav Clark David Hirschfeld dhirschfeld David P. Sanders David P. Sanders David Warde-Farley David Warde-Farley <> +Dan Green-Leipciger Doug Blank Doug Blank -Eugene Van den Bulke Eugene Van den Bulke +Eugene Van den Bulke Eugene Van den Bulke Evan Patterson Evan Patterson Evan Patterson @@ -82,6 +89,10 @@ Julia Evans Julia Evans Kester Tong KesterTong Kyle Kelley Kyle Kelley Kyle Kelley rgbkrk +kd2718 +Kory Donati kory donati +Kory Donati Kory Donati +Kory Donati koryd Laurent Dufréchou Laurent Dufréchou Laurent Dufréchou laurent dufrechou <> @@ -89,6 +100,7 @@ Laurent Dufréchou laurent.dufrechou <> Laurent Dufréchou Laurent Dufrechou <> Laurent Dufréchou laurent.dufrechou@gmail.com <> Laurent Dufréchou ldufrechou +Luciana da Costa Marques luciana Lorena Pantano Lorena Luis Pedro Coelho Luis Pedro Coelho Marc Molla marcmolla @@ -97,6 +109,7 @@ Matthias Bussonnier Matthias BUSSONNIER Bussonnier Matthias Matthias Bussonnier Matthias BUSSONNIER Matthias Bussonnier Matthias Bussonnier +Matthias Bussonnier Matthias Bussonnier Michael Droettboom Michael Droettboom Nicholas Bollweg Nicholas Bollweg (Nick) Nicolas Rougier @@ -134,10 +147,13 @@ Silvia Vinyes silviav12 Srinivas Reddy Thatiparthy Srinivas Reddy Thatiparthy Sylvain Corlay Sylvain Corlay sylvain.corlay +Samuel Gaist +Richard Shadrach +Juan Luis Cano Rodríguez Tamir Bahar Tamir Bahar Ted Drain TD22057 Théophile Studer Théophile Studer -Thomas A Caswell Thomas A Caswell +Thomas A Caswell Thomas A Caswell Thomas Kluyver Thomas Thomas Kluyver Thomas Kluyver Thomas Spura Thomas Spura @@ -155,4 +171,3 @@ Walter Doerwald Walter Doerwald <> Wieland Hoffmann Wieland Hoffmann W. Trevor King W. Trevor King Yoval P. y-p - diff --git a/.meeseeksdev.yml b/.meeseeksdev.yml index 56c33f87084..5522c77a06a 100644 --- a/.meeseeksdev.yml +++ b/.meeseeksdev.yml @@ -1,6 +1,10 @@ +users: + LucianaMarques: + can: + - tag special: everyone: - can: + can: - say - tag - untag @@ -8,6 +12,7 @@ special: config: tag: only: + - good first issue - async/await - backported - help wanted diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..bbc01e9e1af --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + exclude: 'tests/test_ipunittest\.py' + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + +- repo: https://github.com/akaihola/darker + rev: v3.0.0 + hooks: + - id: darker + args: [--formatter=ruff] + additional_dependencies: [ruff, isort, mypy, flake8] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000000..4ab594e9d2a --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,18 @@ +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.14" + apt_packages: + - graphviz + +sphinx: + configuration: docs/source/conf.py + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 09e16146476..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,59 +0,0 @@ -# http://travis-ci.org/#!/ipython/ipython -language: python -python: - - 3.6 - - 3.5 -sudo: false -env: - global: - - PATH=$TRAVIS_BUILD_DIR/pandoc:$PATH -group: edge -before_install: - - 'if [[ $GROUP != js* ]]; then COVERAGE=""; fi' -install: - - pip install pip --upgrade - - pip install setuptools --upgrade - - pip install -e file://$PWD#egg=ipython[test] --upgrade - - pip install trio curio - - pip install codecov check-manifest --upgrade - - sudo apt-get install graphviz -script: - - check-manifest - - cd /tmp && iptest --coverage xml && cd - - # On the latest Python only, make sure that the docs build. - - | - if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]]; then - pip install -r docs/requirements.txt - python tools/fixup_whats_new_pr.py - make -C docs/ html SPHINXOPTS="-W" - fi -after_success: - - cp /tmp/ipy_coverage.xml ./ - - cp /tmp/.coverage ./ - - codecov - -matrix: - include: - - { python: "3.7", dist: xenial, sudo: true } - - { python: "3.7-dev", dist: xenial, sudo: true } - - { python: "nightly", dist: xenial, sudo: true } - allow_failures: - - python: nightly - -before_deploy: - - rm -rf dist/ - - python setup.py sdist - - python setup.py bdist_wheel - -deploy: - provider: releases - api_key: - secure: Y/Ae9tYs5aoBU8bDjN2YrwGG6tCbezj/h3Lcmtx8HQavSbBgXnhnZVRb2snOKD7auqnqjfT/7QMm4ZyKvaOEgyggGktKqEKYHC8KOZ7yp8I5/UMDtk6j9TnXpSqqBxPiud4MDV76SfRYEQiaDoG4tGGvSfPJ9KcNjKrNvSyyxns= - file: dist/* - file_glob: true - skip_cleanup: true - on: - repo: ipython/ipython - all_branches: true # Backports are released from e.g. 5.x branch - tags: true - python: 3.6 # Any version should work, but we only need one diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..be635e13d67 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,80 @@ +# AGENTS.md + +This file provides guidance to AI agents when working with code in this repository. + +We welcome contributions made with AI and expect them to be of the highest +quality. Maintainers re-check pull requests locally, often with their own agents +at the highest effort level. An AI-made pull request helps only when it is at +least as good as what those agents produce. Read [Linting](#linting), +[Tests](#tests) and [Pull Requests](#pull-requests) before you open or update +one. + + +## Code Quality Rules + +### Code Comments + +- **Do**: Match the comment density of the surrounding code; most files have few inline comments +- **Do**: Keep docstrings short (1-4 lines): what it is, plus a non-obvious "why" if there is one +- **Do**: Inside function bodies, comment only constraints or non-obvious reasons the code cannot express +- **Don't**: Describe what the code was before the change or how a bug was found +- **Don't**: Restate what the code already says + +## Linting + +- **Do**: Run linting tools (mypy, ruff) +- **Don't**: Reformat the whole codebase + +## Tests + +- **Do**: Include tests for changes in behavior; a change such as a typo fix in a comment needs none +- **Don't**: Add a test that costs more to maintain than the change is worth. Examples are a test that needs a new dependency, and a 1000-line Jest harness for a two-line fix. Write a short Galata test or no test instead, and say why in the description +- **Do**: Make sure a test for a bug fix fails on an assertion without the fix. +- **Do**: Commit the test before the fix, so a reviewer can check out that commit and see the test fail. If the fix is already pushed, add the test in a new commit and do not rebase to reorder the commits + +## Pull Requests + +### Before You Start + +- Avoid sending pull requests or working on new features and bug fixes if there are more than 30 pull requests open on the repository. Focus your work on trying to find issues that are already fixed or are no longer relevant, and add relevant comments to them if useful. +- Maintainers first review pull requests that fix bugs or user-visible problems, or that address frequent requests. Pull requests for edge cases that code analysis found, and that users are unlikely to hit, may wait for review until all higher-priority pull requests are merged +- New features, settings, UI changes and public API changes need a maintainer's agreement on the issue first. A `good first issue` or `help wanted` label counts as agreement +- Do not open a pull request for an issue labeled `status:Needs Discussion` or `status:Needs Triage`. An issue that the person you work with or a maintainer filed is fine to work on while it still has `status:Needs Triage`. A comment on any of these issues can be a good contribution, and it needs the same high quality as a pull request +- Look for pull requests on the same issue, including closed and merged ones: `gh pr list --repo ipython/ipython --state all --search `. Read why the closed ones were closed, and check whether a merged one already fixed the issue. Open another one only when it differs in a way that matters, and say how under "References" +- Report a security vulnerability as described in the [security policy](https://github.com/jupyterlab/jupyterlab/security/policy), instead of opening an issue or a pull request + +### Description + +- Write the title for users: it becomes the entry in the release notes. +- Write for a human reader: short, plain, with no spurious details +- Steps to reproduce a bug belong in the issue. Give them in the pull request only when the issue has none or they are hard to follow +- Do not state that tests, type checks or lint pass: CI reports them +- Do not add "Generated with ..." footers or other tool adverts; disclose AI use only in the "AI usage" section + +### AI Usage and Effort Level + +- In the "AI usage" section, name every tool and model used, with the effort (reasoning) level it ran at. If you do not know the effort level, say so +- Use the highest effort level the model offers. The second-highest level is accepted only for human-supervised work: a person directed the agent, read the whole diff and ran the change +- When no human supervised the work, answer NO to the question on human review and keep the pull request a draft + +### Screenshots and Videos + +Use screenshots and videos only for visual changes. Otherwise, please include code blocks with the obtained results. + +### Updating the Branch + +- Do not rebase, force-push or merge the target branch without a reason. Each push runs CI again, and a rebase during a review removes the commits the reviewer has already seen +- Pull before you push: maintainers and the snapshot bot add commits to the branch +- Resolve merge conflicts promptly, by merging the target branch +- When the target branch gets changes that interact with the pull request, merge the target branch, push, and check that CI passes + +### Helping with Review + +- Suggest testing someone else's pull request first when the person you work with has a merged pull request, and either asks for several new ones at once or already has one open. Pick one labeled `status:Needs Review`, or one that fixes a reported bug +- Test the pull request with Playwright in a container or a codespace without your credentials, because its build scripts run there. Reproduce the problem on `main`, repeat the steps on the pull request branch, and record both runs +- Draft a comment only for a failure a user would hit, with steps and the recording, or to confirm that a bug seen on `main` is gone. Leave out code style, guesses and anything that did not fail +- Do not post the comment yourself. The person you work with watches the recordings, edits the draft and posts it as a comment, never as an approval or a change request + +## Core Maintainers + +Agents run by core maintainers may be exempt from some of these rules. The maintainer decides which. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60a0bd2c1be..88d3c1f37ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,41 +1,12 @@ -## Triaging Issues - -On the IPython repository we strive to trust users and give them responsibility. -By using one of our bot, any user can close issues, add and remove -labels by mentioning the bot and asking it to do things on your behalf. - -To close and issue (or PR), even if you did not create it, use the following: - -> @meeseeksdev close - -This command can be in the middle of another comments, but must start on its -own line. - -To add labels to an issue, ask the bot to `tag` with a comma separated list of -tags to add: - -> @meeseeksdev tag windows, documentation - -Only already pre-created tags can be added, and the list is so far limited to -`async/await`, `backported`, `help wanted`, `documentation`, `notebook`, -`tab-completion`, `windows` - -To remove a label, use the `untag` command: - -> @meeseeksdev untag windows, documentation - -e'll be adding additional capabilities for the bot and will share them here -when they are ready to be used. - ## Opening an Issue When opening a new Issue, please take the following steps: 1. Search GitHub and/or Google for your issue to avoid duplicate reports. Keyword searches for your error messages are most helpful. -2. If possible, try updating to master and reproducing your issue, +2. If possible, try updating to main and reproducing your issue, because we may have already fixed it. -3. Try to include a minimal reproducible test case +3. Try to include a minimal reproducible test case. 4. Include relevant system information. Start with the output of: python -c "import IPython; print(IPython.sys_info())" @@ -43,6 +14,20 @@ When opening a new Issue, please take the following steps: And include any relevant package versions, depending on the issue, such as matplotlib, numpy, Qt, Qt bindings (PyQt/PySide), tornado, web browser, etc. +## Triaging issue and Fixing issue + +**Do NOT leave a message asking if you can work on an issue; start by trying to +reproduce it and fix it** + +You are looking for an issue to fix and find an issue that can be close or you +suspect is not relevant anymore, please comment on it and say so to avoid future +contributor to lose time doing the same. + +If you have technical questions or reproducing question you are allowed to: + + - Ask clarification on the issue. + - Open a draft PR even with terrible code and ask for advice. + ## Pull Requests Some guidelines on contributing to IPython: @@ -53,10 +38,10 @@ Some guidelines on contributing to IPython: Review and discussion can begin well before the work is complete, and the more discussion the better. The worst case is that the PR is closed. -* Pull Requests should generally be made against master +* Pull Requests should generally be made against main * Pull Requests should be tested, if feasible: - - bugfixes should include regression tests - - new behavior should at least get minimal exercise + - bugfixes should include regression tests. + - new behavior should at least get minimal exercise. * New features and backwards-incompatible changes should be documented by adding a new file to the [pr](docs/source/whatsnew/pr) directory, see [the README.md there](docs/source/whatsnew/pr/README.md) for details. @@ -65,10 +50,50 @@ Some guidelines on contributing to IPython: unnecessary noise. If you're making functional changes, you can clean up the specific pieces of code you're working on. +* if you are an agent, please include two robots emojis in your commits and PR text. +* Do not include that the test are locally passing, this is irrelevant as the source of truth is CI. -[Travis](http://travis-ci.org/#!/ipython/ipython) does a pretty good job testing -IPython and Pull Requests, but it may make sense to manually perform tests, -particularly for PRs that affect `IPython.parallel` or Windows. +[GitHub Actions](https://github.com/ipython/ipython/actions/workflows/test.yml) does +a pretty good job testing IPython and Pull Requests, +but it may make sense to manually perform tests, +particularly for PRs that affect Windows. For more detailed information, see our [GitHub Workflow](https://github.com/ipython/ipython/wiki/Dev:-GitHub-workflow). +## Running Tests + +All the tests can be run by using +```shell +pytest +``` + +All the tests for a single module (for example **test_alias**) can be run by using the fully qualified path to the module. +```shell +pytest IPython/core/tests/test_alias.py +``` + +Only a single test (for example **test_alias_lifecycle**) within a single file can be run by adding the specific test after a `::` at the end: +```shell +pytest IPython/core/tests/test_alias.py::test_alias_lifecycle +``` + +## Documentation + +Sphinx documentation can be built locally using standard sphinx `make` commands. To build HTML documentation from the root of the project, execute: + +```shell +pip install -r docs/requirements.txt # only needed once +make -C docs/ html SPHINXOPTS="-W" +``` + +To force update of the API documentation, precede the `make` command with: + +```shell +python3 docs/autogen_api.py +``` + +Similarly, to force-update the configuration, run: + +```shell +python3 docs/autogen_config.py +``` diff --git a/COPYING.rst b/COPYING.rst index e5c79ef38f0..679c197dc38 100644 --- a/COPYING.rst +++ b/COPYING.rst @@ -14,7 +14,7 @@ Fernando Perez began IPython in 2001 based on code from Janko Hauser the project lead. The IPython Development Team is the set of all contributors to the IPython -project. This includes all of the IPython subprojects. +project. This includes all of the IPython subprojects. The core team that coordinates development on GitHub can be found here: https://github.com/ipython/. @@ -32,7 +32,7 @@ changes/contributions they have specific copyright on, they should indicate their copyright in the commit message of the change, when they commit the change to one of the IPython repositories. -With this in mind, the following banner should be used in any source code file +With this in mind, the following banner should be used in any source code file to indicate the copyright and license terms: :: diff --git a/IPython/__init__.py b/IPython/__init__.py index 7bd0ee3c464..7a878f863e4 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -1,4 +1,4 @@ -# encoding: utf-8 +# PYTHON_ARGCOMPLETE_OK """ IPython: tools for interactive and parallel computing in Python. @@ -19,133 +19,177 @@ # Imports #----------------------------------------------------------------------------- -import os import sys +import warnings +from typing import Any #----------------------------------------------------------------------------- # Setup everything #----------------------------------------------------------------------------- # Don't forget to also update setup.py when this changes! -if sys.version_info < (3, 5): - raise ImportError( -""" -IPython 7.0+ supports Python 3.5 and above. -When using Python 2.7, please install IPython 5.x LTS Long Term Support version. -Python 3.3 and 3.4 were supported up to IPython 6.x. - -See IPython `README.rst` file for more information: - - https://github.com/ipython/ipython/blob/master/README.rst - -""") - -# Make it easy to import extensions - they are always directly on pythonpath. -# Therefore, non-IPython modules can be added to extensions directory. -# This should probably be in ipapp.py. -sys.path.append(os.path.join(os.path.dirname(__file__), "extensions")) - -#----------------------------------------------------------------------------- -# Setup the top level names -#----------------------------------------------------------------------------- - -from .core.getipython import get_ipython +# +# NOTE: these imports look like they could be made lazy (PEP 562) to speed up +# `import IPython` considerably, but downstream projects (pyflyby at least) +# rely on the transitive side effects: they do `import IPython` and then +# access attribute chains like `IPython.terminal.ipapp.TerminalIPythonApp`, +# which only resolve because the imports below load those submodules. +# +# `embed`, `Application` and `get_ipython` are the exceptions, and are +# deferred via module `__getattr__` below: +# +# - `embed` drags in the whole terminal / prompt_toolkit stack, by far +# the most expensive of these imports, and is only needed by code that +# calls `IPython.embed()`; +# - `Application` is only a re-export of `traitlets.config.application +# .Application`, but importing it pulled in `IPython.core.application` +# and with it the crash handler; no known downstream imports it from +# here (ipykernel imports `BaseIPythonApplication` from +# `IPython.core.application` directly); +# - `get_ipython` costs nothing to defer -- `IPython.core.getipython` +# ends up imported anyway via `IPython.core.magic` -- but is kept +# alongside the others so all three top-level names resolve the same +# way. +# +# This does mean that code relying on `import IPython` to transitively +# populate `IPython.terminal.embed` / `IPython.core.application` (or +# submodules only reachable through them) as a side effect will need to +# import those submodules explicitly instead. `Application` raises a +# `DeprecationWarning` when accessed here, both because such code is worth +# spotting and because the name should be imported from traitlets; +# `embed` and `get_ipython` stay silent, being widely and legitimately +# used from here. from .core import release -from .core.application import Application -from .terminal.embed import embed from .core.interactiveshell import InteractiveShell -from .testing import test from .utils.sysinfo import sys_info from .utils.frame import extract_module_locals +__all__ = ["start_ipython", "embed", "embed_kernel"] + + +# Nothing below is cached in `globals()`: the lookups stay lazy on every +# access, so that the `Application` warning keeps firing instead of only +# on the first access, and so that these names never silently turn into +# plain module attributes that later code could mistake for eagerly +# imported ones. +# +# `Application` is deliberately absent from `_lazy_attrs`, and hence from +# `__dir__`: anything that walks `dir(IPython)` and getattr()s the result +# -- our own module completer does, and so do other introspection tools -- +# would otherwise trigger its `DeprecationWarning` without any code +# actually wanting the name. Explicit `IPython.Application` access still +# resolves, and still warns, which is the access we want to hear about. +_lazy_attrs = frozenset({"embed", "get_ipython"}) + + +def __getattr__(name: str) -> Any: + if name == "embed": + from .terminal.embed import embed + + return embed + if name == "get_ipython": + from .core.getipython import get_ipython + + return get_ipython + if name == "Application": + warnings.warn( + "`IPython.Application` is only a re-export of" + " `traitlets.config.application.Application`; import it from" + " traitlets directly. Accessing it here triggers an import of" + " `IPython.core.application`, which is no longer imported when" + " IPython is -- import that module explicitly if you rely on" + " that import happening, in particular if you also rely on other" + " submodules being transitively imported as a side effect.", + DeprecationWarning, + stacklevel=2, + ) + from .core.application import Application + + return Application + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +def __dir__() -> list[str]: + return [*globals(), *_lazy_attrs] + # Release data -__author__ = '%s <%s>' % (release.author, release.author_email) +__author__ = '{} <{}>'.format(release.author, release.author_email) __license__ = release.license __version__ = release.version version_info = release.version_info +# list of CVEs that should have been patched in this release. +# this is informational and should not be relied upon. +__patched_cves__ = {"CVE-2022-21699", "CVE-2023-24816"} + def embed_kernel(module=None, local_ns=None, **kwargs): """Embed and start an IPython kernel in a given scope. - + If you don't want the kernel to initialize the namespace from the scope of the surrounding function, and/or you want to load full IPython configuration, you probably want `IPython.start_kernel()` instead. - + + This is a deprecated alias for `ipykernel.embed.embed_kernel()`, + to be removed in the future. + You should import directly from `ipykernel.embed`; this wrapper + fails anyway if you don't have `ipykernel` package installed. + Parameters ---------- - module : ModuleType, optional + module : types.ModuleType, optional The module to load into IPython globals (default: caller) local_ns : dict, optional The namespace to load into IPython user namespace (default: caller) - - kwargs : various, optional + **kwargs : various, optional Further keyword args are relayed to the IPKernelApp constructor, - allowing configuration of the Kernel. Will only have an effect + such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`), + allowing configuration of the kernel. Will only have an effect on the first embed_kernel call for a given process. """ - + + warnings.warn( + "import embed_kernel from ipykernel.embed directly (since 2013)." + " Importing from IPython will be removed in the future", + DeprecationWarning, + stacklevel=2, + ) + (caller_module, caller_locals) = extract_module_locals(1) if module is None: module = caller_module if local_ns is None: - local_ns = caller_locals - + local_ns = dict(**caller_locals) + # Only import .zmq when we really need it from ipykernel.embed import embed_kernel as real_embed_kernel real_embed_kernel(module=module, local_ns=local_ns, **kwargs) -def start_ipython(argv=None, **kwargs): +def start_ipython(argv: list[str] | None = None, **kwargs: Any) -> Any: """Launch a normal IPython instance (as opposed to embedded) - + `IPython.embed()` puts a shell in a particular calling scope, such as a function or method for debugging purposes, which is often not desirable. - + `start_ipython()` does full, regular IPython initialization, including loading startup files, configuration, etc. much of which is skipped by `embed()`. - + This is a public API method, and will survive implementation changes. - - Parameters - ---------- - - argv : list or None, optional - If unspecified or None, IPython will parse command-line options from sys.argv. - To prevent any command-line parsing, pass an empty list: `argv=[]`. - user_ns : dict, optional - specify this dictionary to initialize the IPython user namespace with particular values. - kwargs : various, optional - Any other kwargs will be passed to the Application constructor, - such as `config`. - """ - from IPython.terminal.ipapp import launch_new_instance - return launch_new_instance(argv=argv, **kwargs) -def start_kernel(argv=None, **kwargs): - """Launch a normal IPython kernel instance (as opposed to embedded) - - `IPython.embed_kernel()` puts a shell in a particular calling scope, - such as a function or method for debugging purposes, - which is often not desirable. - - `start_kernel()` does full, regular IPython initialization, - including loading startup files, configuration, etc. - much of which is skipped by `embed()`. - Parameters ---------- - argv : list or None, optional If unspecified or None, IPython will parse command-line options from sys.argv. To prevent any command-line parsing, pass an empty list: `argv=[]`. user_ns : dict, optional specify this dictionary to initialize the IPython user namespace with particular values. - kwargs : various, optional + **kwargs : various, optional Any other kwargs will be passed to the Application constructor, - such as `config`. + such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`), + allowing configuration of the instance (see :ref:`terminal_options`). """ - from IPython.kernel.zmq.kernelapp import launch_new_instance + from IPython.terminal.ipapp import launch_new_instance return launch_new_instance(argv=argv, **kwargs) diff --git a/IPython/__main__.py b/IPython/__main__.py index d5123f33a20..0425c8d764c 100644 --- a/IPython/__main__.py +++ b/IPython/__main__.py @@ -1,13 +1,12 @@ -# encoding: utf-8 -"""Terminal-based IPython entry point. -""" -#----------------------------------------------------------------------------- +# PYTHON_ARGCOMPLETE_OK +"""Terminal-based IPython entry point.""" +# ----------------------------------------------------------------------------- # Copyright (c) 2012, IPython Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- from IPython import start_ipython diff --git a/IPython/config.py b/IPython/config.py deleted file mode 100644 index cf2bacafad1..00000000000 --- a/IPython/config.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Shim to maintain backwards compatibility with old IPython.config imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from IPython.utils.shimmodule import ShimModule, ShimWarning - -warn("The `IPython.config` package has been deprecated since IPython 4.0. " - "You should import from traitlets.config instead.", ShimWarning) - - -# Unconditionally insert the shim into sys.modules so that further import calls -# trigger the custom attribute access above - -sys.modules['IPython.config'] = ShimModule(src='IPython.config', mirror='traitlets.config') diff --git a/IPython/consoleapp.py b/IPython/consoleapp.py deleted file mode 100644 index c2bbe1888f5..00000000000 --- a/IPython/consoleapp.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Shim to maintain backwards compatibility with old IPython.consoleapp imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from warnings import warn - -warn("The `IPython.consoleapp` package has been deprecated since IPython 4.0." - "You should import from jupyter_client.consoleapp instead.", stacklevel=2) - -from jupyter_client.consoleapp import * diff --git a/IPython/core/_dunder_ops.py b/IPython/core/_dunder_ops.py new file mode 100644 index 00000000000..ece1887b02b --- /dev/null +++ b/IPython/core/_dunder_ops.py @@ -0,0 +1,65 @@ +"""Mapping from AST operator nodes to the dunder methods that implement them. + +This lives in its own module, rather than in `IPython.core.guarded_eval` where +it is mostly used, so that the terminal shortcut filters can resolve operators +in a filter expression without importing the whole of `guarded_eval` -- and +with it `typing_extensions`, `dataclasses` and `inspect` -- on every startup. + +The names are re-exported from `IPython.core.guarded_eval`, which remains +their documented home. +""" + +import ast +from collections.abc import Mapping +from typing import Any + +__all__ = [ + "BINARY_OP_DUNDERS", + "COMP_OP_DUNDERS", + "UNARY_OP_DUNDERS", +] + +BINARY_OP_DUNDERS: dict[type[ast.operator], tuple[str]] = { + ast.Add: ("__add__",), + ast.Sub: ("__sub__",), + ast.Mult: ("__mul__",), + ast.Div: ("__truediv__",), + ast.FloorDiv: ("__floordiv__",), + ast.Mod: ("__mod__",), + ast.Pow: ("__pow__",), + ast.LShift: ("__lshift__",), + ast.RShift: ("__rshift__",), + ast.BitOr: ("__or__",), + ast.BitXor: ("__xor__",), + ast.BitAnd: ("__and__",), + ast.MatMult: ("__matmul__",), +} + +COMP_OP_DUNDERS: dict[type[ast.cmpop], tuple[str, ...]] = { + ast.Eq: ("__eq__",), + ast.NotEq: ("__ne__", "__eq__"), + ast.Lt: ("__lt__", "__gt__"), + ast.LtE: ("__le__", "__ge__"), + ast.Gt: ("__gt__", "__lt__"), + ast.GtE: ("__ge__", "__le__"), + ast.In: ("__contains__",), + # Note: ast.Is, ast.IsNot, ast.NotIn are handled specially +} + +UNARY_OP_DUNDERS: dict[type[ast.unaryop], tuple[str, ...]] = { + ast.USub: ("__neg__",), + ast.UAdd: ("__pos__",), + # we have to check both __inv__ and __invert__! + ast.Invert: ("__invert__", "__inv__"), + ast.Not: ("__not__",), +} + + +def _find_dunder( + node_op: ast.AST, dunders: Mapping[type[Any], tuple[str, ...]] +) -> tuple[str, ...] | None: + dunder = None + for op, candidate_dunder in dunders.items(): + if isinstance(node_op, op): + dunder = candidate_dunder + return dunder diff --git a/IPython/core/alias.py b/IPython/core/alias.py index ee377d5ccf3..d9fcbcdcf63 100644 --- a/IPython/core/alias.py +++ b/IPython/core/alias.py @@ -1,4 +1,3 @@ -# encoding: utf-8 """ System command aliases. @@ -7,6 +6,7 @@ * Fernando Perez * Brian Granger """ +from __future__ import annotations #----------------------------------------------------------------------------- # Copyright (C) 2008-2011 The IPython Development Team @@ -25,10 +25,10 @@ import sys from traitlets.config.configurable import Configurable -from IPython.core.error import UsageError +from .error import UsageError from traitlets import List, Instance -from logging import error + #----------------------------------------------------------------------------- # Utilities @@ -37,7 +37,7 @@ # This is used as the pattern for calls to split_user_input. shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)') -def default_aliases(): +def default_aliases() -> list[tuple[str, str]]: """Return list of shell aliases to auto-define. """ # Note: the aliases defined here should be safe to use on a kernel @@ -117,7 +117,8 @@ class AliasError(Exception): class InvalidAliasError(AliasError): pass -class Alias(object): + +class Alias: """Callable object storing the details of one alias. Instances are registered as magic functions to allow use of aliases. @@ -130,7 +131,7 @@ def __init__(self, shell, name, cmd): self.shell = shell self.name = name self.cmd = cmd - self.__doc__ = "Alias for `!{}`".format(cmd) + self.__doc__ = f"Alias for `!{cmd}`" self.nargs = self.validate() def validate(self): @@ -152,7 +153,7 @@ def validate(self): "got: %r" % self.cmd) nargs = self.cmd.count('%s') - self.cmd.count('%%s') - + if (nargs > 0) and (self.cmd.find('%l') >= 0): raise InvalidAliasError('The %s and %l specifiers are mutually ' 'exclusive in alias definitions.') @@ -160,7 +161,7 @@ def validate(self): return nargs def __repr__(self): - return "".format(self.name, self.cmd) + return f"" def __call__(self, rest=''): cmd = self.cmd @@ -169,19 +170,19 @@ def __call__(self, rest=''): if cmd.find('%l') >= 0: cmd = cmd.replace('%l', rest) rest = '' - + if nargs==0: if cmd.find('%%s') >= 1: cmd = cmd.replace('%%s', '%s') # Simple, argument-less aliases - cmd = '%s %s' % (cmd, rest) + cmd = '{} {}'.format(cmd, rest) else: # Handle aliases with positional arguments args = rest.split(None, nargs) if len(args) < nargs: raise UsageError('Alias <%s> requires %s arguments, %s given.' % (self.name, nargs, len(args))) - cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:])) + cmd = '{} {}'.format(cmd % tuple(args[:nargs]),' '.join(args[nargs:])) self.shell.system(cmd) @@ -190,24 +191,32 @@ def __call__(self, rest=''): #----------------------------------------------------------------------------- class AliasManager(Configurable): - - default_aliases = List(default_aliases()).tag(config=True) - user_aliases = List(default_value=[]).tag(config=True) - shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) + default_aliases: List = List(default_aliases()).tag(config=True) + user_aliases: List = List(default_value=[]).tag(config=True) + shell = Instance( + "IPython.core.interactiveshell.InteractiveShellABC", allow_none=True + ) def __init__(self, shell=None, **kwargs): - super(AliasManager, self).__init__(shell=shell, **kwargs) + super().__init__(shell=shell, **kwargs) # For convenient access - self.linemagics = self.shell.magics_manager.magics['line'] - self.init_aliases() + if self.shell is not None: + self.linemagics = self.shell.magics_manager.magics["line"] + self.init_aliases() def init_aliases(self): # Load default & user aliases for name, cmd in self.default_aliases + self.user_aliases: + if ( + cmd.startswith("ls ") + and self.shell is not None + and self.shell.colors == "nocolor" + ): + cmd = cmd.replace(" --color", "") self.soft_define_alias(name, cmd) @property - def aliases(self): + def aliases(self) -> list: return [(n, func.cmd) for (n, func) in self.linemagics.items() if isinstance(func, Alias)] @@ -216,6 +225,7 @@ def soft_define_alias(self, name, cmd): try: self.define_alias(name, cmd) except AliasError as e: + from logging import error error("Invalid alias: %s" % e) def define_alias(self, name, cmd): @@ -244,7 +254,7 @@ def undefine_alias(self, name): raise ValueError('%s is not an alias' % name) def clear_aliases(self): - for name, cmd in self.aliases: + for name, _ in self.aliases: self.undefine_alias(name) def retrieve_alias(self, name): diff --git a/IPython/core/application.py b/IPython/core/application.py index fea2b50f561..cd2180e2010 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -1,4 +1,3 @@ -# encoding: utf-8 """ An application for IPython. @@ -14,12 +13,11 @@ import atexit from copy import deepcopy -import glob -import logging import os -import shutil import sys +from pathlib import Path + from traitlets.config.application import Application, catch_config_error from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader from IPython.core import release, crashhandler @@ -31,11 +29,21 @@ default, observe, ) -if os.name == 'nt': - programdata = os.environ.get('PROGRAMDATA', None) - if programdata: - SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')] - else: # PROGRAMDATA is not defined by default on XP. +# Values of `logging.DEBUG` and `logging.CRITICAL`, inlined so that this +# module -- which is on the IPython startup path -- does not have to import +# `logging` just to spell two integers. The `logging` levels are part of its +# documented public API and cannot change; `tests/test_application.py` +# asserts these copies do not drift from it. +LOGGING_DEBUG = 10 +LOGGING_CRITICAL = 50 + + +if os.name == "nt": + # %PROGRAMDATA% is not safe by default, require opt-in to trust it + programdata = os.environ.get("PROGRAMDATA", None) + if os.environ.get("IPYTHON_USE_PROGRAMDATA") == "1" and programdata is not None: + SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")] + else: SYSTEM_CONFIG_DIRS = [] else: SYSTEM_CONFIG_DIRS = [ @@ -64,27 +72,49 @@ # aliases and flags -base_aliases = { - 'profile-dir' : 'ProfileDir.location', - 'profile' : 'BaseIPythonApplication.profile', - 'ipython-dir' : 'BaseIPythonApplication.ipython_dir', - 'log-level' : 'Application.log_level', - 'config' : 'BaseIPythonApplication.extra_config_file', -} - -base_flags = dict( - debug = ({'Application' : {'log_level' : logging.DEBUG}}, - "set log level to logging.DEBUG (maximize logging output)"), - quiet = ({'Application' : {'log_level' : logging.CRITICAL}}, - "set log level to logging.CRITICAL (minimize logging output)"), - init = ({'BaseIPythonApplication' : { - 'copy_config_files' : True, - 'auto_create' : True} - }, """Initialize profile with default config files. This is equivalent +base_aliases = {} +if isinstance(Application.aliases, dict): + # traitlets 5 + base_aliases.update(Application.aliases) +base_aliases.update( + { + "profile-dir": "ProfileDir.location", + "profile": "BaseIPythonApplication.profile", + "ipython-dir": "BaseIPythonApplication.ipython_dir", + "log-level": "Application.log_level", + "config": "BaseIPythonApplication.extra_config_file", + } +) + +base_flags = dict() +if isinstance(Application.flags, dict): + # traitlets 5 + base_flags.update(Application.flags) +base_flags.update( + dict( + debug=( + {"Application": {"log_level": LOGGING_DEBUG}}, + "set log level to logging.DEBUG (maximize logging output)", + ), + quiet=( + {"Application": {"log_level": LOGGING_CRITICAL}}, + "set log level to logging.CRITICAL (minimize logging output)", + ), + init=( + { + "BaseIPythonApplication": { + "copy_config_files": True, + "auto_create": True, + } + }, + """Initialize profile with default config files. This is equivalent to running `ipython profile create ` prior to startup. - """) + """, + ), + ) ) + class ProfileAwareConfigLoader(PyFileConfigLoader): """A Python file config loader that is aware of IPython profiles.""" def load_subconfig(self, fname, path=None, profile=None): @@ -97,18 +127,17 @@ def load_subconfig(self, fname, path=None, profile=None): except ProfileDirError: return path = profile_dir.location - return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path) + return super().load_subconfig(fname, path=path) class BaseIPythonApplication(Application): - - name = u'ipython' - description = Unicode(u'IPython: an enhanced interactive Python shell.') + name = "ipython" + description = "IPython: an enhanced interactive Python shell." version = Unicode(release.version) aliases = base_aliases flags = base_flags classes = List([ProfileDir]) - + # enable `load_subconfig('cfg.py', profile='name')` python_config_loader_class = ProfileAwareConfigLoader @@ -119,7 +148,7 @@ class BaseIPythonApplication(Application): config_file_name = Unicode() @default('config_file_name') def _config_file_name_default(self): - return self.name.replace('-','_') + u'_config.py' + return self.name.replace('-','_') + '_config.py' @observe('config_file_name') def _config_file_name_changed(self, change): if change['new'] != change['old']: @@ -127,17 +156,16 @@ def _config_file_name_changed(self, change): # The directory that contains IPython's builtin profiles. builtin_profile_dir = Unicode( - os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') + os.path.join(get_ipython_package_dir(), 'config', 'profile', 'default') ) - + config_file_paths = List(Unicode()) @default('config_file_paths') def _config_file_paths_default(self): - return [os.getcwd()] + return [] + + extra_config_file = Unicode(help="""Path to an extra config file to load. - extra_config_file = Unicode( - help="""Path to an extra config file to load. - If specified, load this config file in addition to any other IPython config. """).tag(config=True) @observe('extra_config_file') @@ -151,16 +179,27 @@ def _extra_config_file_changed(self, change): self.config_file_specified.add(new) self.config_files.append(new) - profile = Unicode(u'default', + profile = Unicode('default', help="""The IPython profile to use.""" ).tag(config=True) - + @observe('profile') def _profile_changed(self, change): self.builtin_profile_dir = os.path.join( - get_ipython_package_dir(), u'config', u'profile', change['new'] + get_ipython_package_dir(), 'config', 'profile', change['new'] ) + add_ipython_dir_to_sys_path = Bool( + False, + """Should the IPython profile directory be added to sys path ? + + This option was non-existing before IPython 8.0, and ipython_dir was added to + sys path to allow import of extensions present there. This was historical + baggage from when pip did not exist. This now default to false, + but can be set to true for legacy reasons. + """, + ).tag(config=True) + ipython_dir = Unicode( help=""" The name of the IPython directory. This directory is used for logging @@ -178,9 +217,11 @@ def _ipython_dir_default(self): 'new': d, }) return d - + _in_init_profile_dir = False + profile_dir = Instance(ProfileDir, allow_none=True) + @default('profile_dir') def _profile_dir_default(self): # avoid recursion @@ -193,11 +234,13 @@ def _profile_dir_default(self): overwrite = Bool(False, help="""Whether to overwrite existing config files when copying""" ).tag(config=True) + auto_create = Bool(False, help="""Whether to create profile dir if it doesn't exist""" ).tag(config=True) config_files = List(Unicode()) + @default('config_files') def _config_files_default(self): return [self.config_file_name] @@ -208,7 +251,7 @@ def _config_files_default(self): profile, then they will be staged into the new directory. Otherwise, default config files will be automatically generated. """).tag(config=True) - + verbose_crash = Bool(False, help="""Create a massive crash report when IPython encounters what may be an internal error. The default is to append a short message to the @@ -219,11 +262,11 @@ def _config_files_default(self): @catch_config_error def __init__(self, **kwargs): - super(BaseIPythonApplication, self).__init__(**kwargs) + super().__init__(**kwargs) # ensure current working directory exists try: os.getcwd() - except: + except OSError: # exit if cwd doesn't exist self.log.error("Current working directory doesn't exist.") self.exit(1) @@ -231,16 +274,6 @@ def __init__(self, **kwargs): #------------------------------------------------------------------------- # Various stages of Application creation #------------------------------------------------------------------------- - - deprecated_subcommands = {} - - def initialize_subcommand(self, subc, argv=None): - if subc in self.deprecated_subcommands: - self.log.warning("Subcommand `ipython {sub}` is deprecated and will be removed " - "in future versions.".format(sub=subc)) - self.log.warning("You likely want to use `jupyter {sub}` in the " - "future".format(sub=subc)) - return super(BaseIPythonApplication, self).initialize_subcommand(subc, argv) def init_crash_handler(self): """Create a crash handler, typically setting sys.excepthook to it.""" @@ -249,14 +282,14 @@ def init_crash_handler(self): def unset_crashhandler(): sys.excepthook = sys.__excepthook__ atexit.register(unset_crashhandler) - + def excepthook(self, etype, evalue, tb): """this is sys.excepthook after init_crashhandler - + set self.verbose_crash=True to use our full crashhandler, instead of a regular traceback with a short message (crash_handler_lite) """ - + if self.verbose_crash: return self.crash_handler(etype, evalue, tb) else: @@ -270,21 +303,25 @@ def _ipython_dir_changed(self, change): str_old = os.path.abspath(old) if str_old in sys.path: sys.path.remove(str_old) - str_path = os.path.abspath(new) - sys.path.append(str_path) - ensure_dir_exists(new) - readme = os.path.join(new, 'README') - readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') - if not os.path.exists(readme) and os.path.exists(readme_src): - shutil.copy(readme_src, readme) - for d in ('extensions', 'nbextensions'): - path = os.path.join(new, d) - try: - ensure_dir_exists(path) - except OSError as e: - # this will not be EEXIST - self.log.error("couldn't create path %s: %s", path, e) - self.log.debug("IPYTHONDIR set to: %s" % new) + if self.add_ipython_dir_to_sys_path: + str_path = os.path.abspath(new) + sys.path.append(str_path) + ensure_dir_exists(new) + readme = os.path.join(new, "README") + readme_src = os.path.join( + get_ipython_package_dir(), "config", "profile", "README" + ) + if not os.path.exists(readme) and os.path.exists(readme_src): + import shutil + shutil.copy(readme_src, readme) + for d in ("extensions", "nbextensions"): + path = os.path.join(new, d) + try: + ensure_dir_exists(path) + except OSError as e: + # this will not be EEXIST + self.log.error("couldn't create path %s: %s", path, e) + self.log.debug("IPYTHONDIR set to: %s", new) def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): """Load the config file. @@ -293,7 +330,7 @@ def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): printed on screen. For testing, the suppress_errors option is set to False, so errors will make tests fail. - `supress_errors` default value is to be `None` in which case the + `suppress_errors` default value is to be `None` in which case the behavior default to the one of `traitlets.Application`. The default value can be set : @@ -312,7 +349,7 @@ def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): try: if suppress_errors is not None: old_value = Application.raise_config_file_errors - Application.raise_config_file_errors = not suppress_errors; + Application.raise_config_file_errors = not suppress_errors Application.load_config_file( self, base_config, @@ -324,7 +361,7 @@ def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): pass if suppress_errors is not None: Application.raise_config_file_errors = old_value - + for config_file_name in self.config_files: if not config_file_name or config_file_name == base_config: continue @@ -374,7 +411,7 @@ def init_profile_dir(self): self.log.fatal("Profile %r not found."%self.profile) self.exit(1) else: - self.log.debug("Using existing profile dir: %r"%p.location) + self.log.debug("Using existing profile dir: %r", p.location) else: location = self.config.ProfileDir.location # location is fully specified @@ -394,7 +431,7 @@ def init_profile_dir(self): self.log.fatal("Profile directory %r not found."%location) self.exit(1) else: - self.log.info("Using existing profile dir: %r"%location) + self.log.debug("Using existing profile dir: %r", p.location) # if profile_dir is specified explicitly, set profile name dir_name = os.path.basename(p.location) if dir_name.startswith('profile_'): @@ -409,14 +446,15 @@ def init_config_files(self): self.config_file_paths.extend(ENV_CONFIG_DIRS) self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) # copy config files - path = self.builtin_profile_dir + path = Path(self.builtin_profile_dir) if self.copy_config_files: src = self.profile cfg = self.config_file_name - if path and os.path.exists(os.path.join(path, cfg)): - self.log.warning("Staging %r from %s into %r [overwrite=%s]"%( - cfg, src, self.profile_dir.location, self.overwrite) + if path and (path / cfg).exists(): + self.log.warning( + "Staging %r from %s into %r [overwrite=%s]" + % (cfg, src, self.profile_dir.location, self.overwrite) ) self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) else: @@ -425,9 +463,9 @@ def init_config_files(self): # Still stage *bundled* config files, but not generated ones # This is necessary for `ipython profile=sympy` to load the profile # on the first go - files = glob.glob(os.path.join(path, '*.py')) + files = path.glob("*.py") for fullpath in files: - cfg = os.path.basename(fullpath) + cfg = fullpath.name if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): # file was copied self.log.warning("Staging bundled %s from %s into %r"%( @@ -438,11 +476,10 @@ def init_config_files(self): def stage_default_config_file(self): """auto generate default config file, and stage it into the profile.""" s = self.generate_config_file() - fname = os.path.join(self.profile_dir.location, self.config_file_name) - if self.overwrite or not os.path.exists(fname): - self.log.warning("Generating default config file: %r"%(fname)) - with open(fname, 'w') as f: - f.write(s) + config_file = Path(self.profile_dir.location) / self.config_file_name + if self.overwrite or not config_file.exists(): + self.log.warning("Generating default config file: %r", (config_file)) + config_file.write_text(s, encoding="utf-8") @catch_config_error def initialize(self, argv=None): diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index a6ff86031d0..5e1fae8d38e 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -10,28 +10,102 @@ Python semantics. """ +from __future__ import annotations import ast -import sys -from textwrap import dedent, indent +import inspect +from functools import wraps +from typing import TYPE_CHECKING +if TYPE_CHECKING: + import asyncio -class _AsyncIORunner: +_asyncio_event_loop: asyncio.AbstractEventLoop | None = None + + +def get_asyncio_loop(): + """asyncio has deprecated get_event_loop + + Replicate it here, with our desired semantics: + + - always returns a valid, not-closed loop + - not thread-local like asyncio's, + because we only want one loop for IPython + - if called from inside a coroutine (e.g. in ipykernel), + return the running loop + + .. versionadded:: 8.0 + """ + # asyncio (and everything it drags in) is only imported the first + # time an event loop is actually needed, rather than on every + # IPython startup. + import asyncio + try: + return asyncio.get_running_loop() + except RuntimeError: + # not inside a coroutine, + # track our own global + pass + + # not thread-local like asyncio's, + # because we only track one event loop to run for IPython itself, + # always in the main thread. + global _asyncio_event_loop + if _asyncio_event_loop is None or _asyncio_event_loop.is_closed(): + _asyncio_event_loop = asyncio.new_event_loop() + return _asyncio_event_loop + + +class _AsyncIORunner: def __call__(self, coro): """ Handler for asyncio autoawait """ - import asyncio - - return asyncio.get_event_loop().run_until_complete(coro) + return get_asyncio_loop().run_until_complete(coro) def __str__(self): - return 'asyncio' + return "asyncio" + _asyncio_runner = _AsyncIORunner() +class _AsyncIOProxy: + """Proxy-object for an asyncio + + Any coroutine methods will be wrapped in event_loop.run_ + """ + + def __init__(self, obj, event_loop): + self._obj = obj + self._event_loop = event_loop + + def __repr__(self): + return f"<_AsyncIOProxy({self._obj!r})>" + + def __getattr__(self, key): + attr = getattr(self._obj, key) + if inspect.iscoroutinefunction(attr): + # if it's a coroutine method, + # return a threadsafe wrapper onto the _current_ asyncio loop + @wraps(attr) + def _wrapped(*args, **kwargs): + import asyncio + + concurrent_future = asyncio.run_coroutine_threadsafe( + attr(*args, **kwargs), self._event_loop + ) + return asyncio.wrap_future(concurrent_future) + + return _wrapped + else: + return attr + + def __dir__(self): + return dir(self._obj) + + def _curio_runner(coroutine): """ handler for curio autoawait @@ -61,7 +135,6 @@ def _pseudo_sync_runner(coro): See discussion in https://github.com/python-trio/trio/issues/608, Credit to Nathaniel Smith - """ try: coro.send(None) @@ -70,88 +143,20 @@ def _pseudo_sync_runner(coro): else: # TODO: do not raise but return an execution result with the right info. raise RuntimeError( - "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__) + f"{coro.__name__!r} needs a real async loop" ) -def _asyncify(code: str) -> str: - """wrap code in async def definition. - - And setup a bit of context to run it later. - """ - res = dedent( - """ - async def __wrapper__(): - try: - {usercode} - finally: - locals() - """ - ).format(usercode=indent(code, " " * 8)) - return res - - -class _AsyncSyntaxErrorVisitor(ast.NodeVisitor): - """ - Find syntax errors that would be an error in an async repl, but because - the implementation involves wrapping the repl in an async function, it - is erroneously allowed (e.g. yield or return at the top level) - """ - - def generic_visit(self, node): - func_types = (ast.FunctionDef, ast.AsyncFunctionDef) - invalid_types = (ast.Return, ast.Yield, ast.YieldFrom) - - if isinstance(node, func_types): - return # Don't recurse into functions - elif isinstance(node, invalid_types): - raise SyntaxError() - else: - super().generic_visit(node) - - -def _async_parse_cell(cell: str) -> ast.AST: - """ - This is a compatibility shim for pre-3.7 when async outside of a function - is a syntax error at the parse stage. - - It will return an abstract syntax tree parsed as if async and await outside - of a function were not a syntax error. - """ - if sys.version_info < (3, 7): - # Prior to 3.7 you need to asyncify before parse - wrapped_parse_tree = ast.parse(_asyncify(cell)) - return wrapped_parse_tree.body[0].body[0] - else: - return ast.parse(cell) - - def _should_be_async(cell: str) -> bool: - """Detect if a block of code need to be wrapped in an `async def` - - Attempt to parse the block of code, it it compile we're fine. - Otherwise we wrap if and try to compile. + """Detect if a block of code needs to be wrapped in an `async def` - If it works, assume it should be async. Otherwise Return False. - - Not handled yet: If the block of code has a return statement as the top - level, it will be seen as async. This is a know limitation. + If the code block has a top-level return statement or is otherwise + invalid, `False` will be returned. """ - try: - # we can't limit ourself to ast.parse, as it __accepts__ to parse on - # 3.7+, but just does not _compile_ - compile(cell, "<>", "exec") + code = compile( + cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) + ) + return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE + except (SyntaxError, ValueError, MemoryError): return False - except SyntaxError: - try: - parse_tree = _async_parse_cell(cell) - - # Raise a SyntaxError if there are top-level return or yields - v = _AsyncSyntaxErrorVisitor() - v.visit(parse_tree) - - except SyntaxError: - return False - return True - return False diff --git a/IPython/core/autocall.py b/IPython/core/autocall.py index bab7f859c96..a7fa93135eb 100644 --- a/IPython/core/autocall.py +++ b/IPython/core/autocall.py @@ -1,4 +1,3 @@ -# encoding: utf-8 """ Autocall capabilities for IPython.core. @@ -28,9 +27,9 @@ # Code #----------------------------------------------------------------------------- -class IPyAutocall(object): - """ Instances of this class are always autocalled - +class IPyAutocall: + """Instances of this class are always autocalled + This happens regardless of 'autocall' variable state. Use this to develop macro-like mechanisms. """ @@ -38,12 +37,12 @@ class IPyAutocall(object): rewrite = True def __init__(self, ip=None): self._ip = ip - + def set_ip(self, ip): - """ Will be used to set _ip point to current ipython instance b/f call - + """Will be used to set _ip point to current ipython instance b/f call + Override this method if you don't want this to happen. - + """ self._ip = ip @@ -52,13 +51,14 @@ class ExitAutocall(IPyAutocall): """An autocallable object which will be added to the user namespace so that exit, exit(), quit or quit() are all valid ways to close the shell.""" rewrite = False - + def __call__(self): self._ip.ask_exit() - + + class ZMQExitAutocall(ExitAutocall): """Exit IPython. Autocallable, so it needn't be explicitly called. - + Parameters ---------- keep_kernel : bool diff --git a/IPython/core/builtin_trap.py b/IPython/core/builtin_trap.py index a8ea4abcd9d..02fc2b3e3e1 100644 --- a/IPython/core/builtin_trap.py +++ b/IPython/core/builtin_trap.py @@ -3,17 +3,30 @@ """ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + import builtins as builtin_mod +from typing import Any, Literal, TYPE_CHECKING from traitlets.config.configurable import Configurable from traitlets import Instance +if TYPE_CHECKING: + from types import TracebackType + + +class __BuiltinUndefined: + pass + -class __BuiltinUndefined(object): pass BuiltinUndefined = __BuiltinUndefined() -class __HideBuiltin(object): pass + +class __HideBuiltin: + pass + + HideBuiltin = __HideBuiltin() @@ -22,35 +35,36 @@ class BuiltinTrap(Configurable): shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) - def __init__(self, shell=None): - super(BuiltinTrap, self).__init__(shell=shell, config=None) - self._orig_builtins = {} + def __init__(self, shell: Any = None) -> None: + super().__init__(shell=shell, config=None) + self._orig_builtins: dict[str, Any] = {} # We define this to track if a single BuiltinTrap is nested. # Only turn off the trap when the outermost call to __exit__ is made. self._nested_level = 0 self.shell = shell # builtins we always add - if set to HideBuiltin, they will just # be removed instead of being replaced by something else - self.auto_builtins = {'exit': HideBuiltin, - 'quit': HideBuiltin, - 'get_ipython': self.shell.get_ipython, - } + self.auto_builtins: dict[str, Any] = { + 'exit': HideBuiltin, + 'quit': HideBuiltin, + 'get_ipython': self.shell.get_ipython, + } - def __enter__(self): + def __enter__(self) -> BuiltinTrap: if self._nested_level == 0: self.activate() self._nested_level += 1 # I return self, so callers can use add_builtin in a with clause. return self - def __exit__(self, type, value, traceback): + def __exit__(self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None) -> Literal[False]: if self._nested_level == 1: self.deactivate() self._nested_level -= 1 # Returning False will cause exceptions to propagate return False - def add_builtin(self, key, value): + def add_builtin(self, key: str, value: Any) -> None: """Add a builtin and save the original.""" bdict = builtin_mod.__dict__ orig = bdict.get(key, BuiltinUndefined) @@ -62,25 +76,24 @@ def add_builtin(self, key, value): self._orig_builtins[key] = orig bdict[key] = value - def remove_builtin(self, key, orig): + def remove_builtin(self, key: str, orig: Any) -> None: """Remove an added builtin and re-set the original.""" if orig is BuiltinUndefined: del builtin_mod.__dict__[key] else: builtin_mod.__dict__[key] = orig - def activate(self): + def activate(self) -> None: """Store ipython references in the __builtin__ namespace.""" add_builtin = self.add_builtin for name, func in self.auto_builtins.items(): add_builtin(name, func) - def deactivate(self): + def deactivate(self) -> None: """Remove any builtins which might have been added by add_builtins, or restore overwritten ones to their previous values.""" remove_builtin = self.remove_builtin for key, val in self._orig_builtins.items(): remove_builtin(key, val) self._orig_builtins.clear() - self._builtins_added = False diff --git a/IPython/core/compilerop.py b/IPython/core/compilerop.py index 6a055f93361..d9052291c92 100644 --- a/IPython/core/compilerop.py +++ b/IPython/core/compilerop.py @@ -26,15 +26,19 @@ # Imports #----------------------------------------------------------------------------- +from __future__ import annotations + +import ast + # Stdlib imports import __future__ from ast import PyCF_ONLY_AST import codeop import functools -import hashlib import linecache import operator -import time +from contextlib import contextmanager +from collections.abc import Generator #----------------------------------------------------------------------------- # Constants @@ -50,16 +54,17 @@ # Local utilities #----------------------------------------------------------------------------- -def code_name(code, number=0): +def code_name(code: str, number: int = 0) -> str: """ Compute a (probably) unique name for code for caching. This now expects code to be unicode. """ - hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest() + import hashlib + hash_digest = hashlib.sha1(code.encode("utf-8"), usedforsecurity=False).hexdigest() # Include the number and 12 characters of the hash in the name. It's # pretty much impossible that in a single session we'll have collisions # even with truncated hashes, and the full one makes tracebacks too long - return ''.format(number, hash_digest[:12]) + return f'' #----------------------------------------------------------------------------- # Classes and functions @@ -72,73 +77,120 @@ class CachingCompiler(codeop.Compile): def __init__(self): codeop.Compile.__init__(self) - # This is ugly, but it must be done this way to allow multiple - # simultaneous ipython instances to coexist. Since Python itself - # directly accesses the data structures in the linecache module, and - # the cache therein is global, we must work with that data structure. - # We must hold a reference to the original checkcache routine and call - # that in our own check_cache() below, but the special IPython cache - # must also be shared by all IPython instances. If we were to hold - # separate caches (one in each CachingCompiler instance), any call made - # by Python itself to linecache.checkcache() would obliterate the - # cached data from the other IPython instances. - if not hasattr(linecache, '_ipython_cache'): - linecache._ipython_cache = {} - if not hasattr(linecache, '_checkcache_ori'): - linecache._checkcache_ori = linecache.checkcache - # Now, we must monkeypatch the linecache directly so that parts of the - # stdlib that call it outside our control go through our codepath - # (otherwise we'd lose our tracebacks). - linecache.checkcache = check_linecache_ipython - - - def ast_parse(self, source, filename='', symbol='exec'): + # Caching a dictionary { filename: execution_count } for nicely + # rendered tracebacks. The filename corresponds to the filename + # argument used for the builtins.compile function. + self._filename_map = {} + + def ast_parse(self, source: str, filename: str = '', symbol: str = 'exec') -> ast.AST: """Parse code to an AST with the current compiler flags active. Arguments are exactly the same as ast.parse (in the standard library), and are passed to the built-in compile function.""" return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1) - - def reset_compiler_flags(self): + + def reset_compiler_flags(self) -> None: """Reset compiler flags to default state.""" # This value is copied from codeop.Compile.__init__, so if that ever # changes, it will need to be updated. self.flags = codeop.PyCF_DONT_IMPLY_DEDENT @property - def compiler_flags(self): + def compiler_flags(self) -> int: """Flags currently active in the compilation process. """ return self.flags - def cache(self, code, number=0): + def get_code_name(self, raw_code: str, transformed_code: str, number: int) -> str: + """Compute filename given the code, and the cell number. + + Parameters + ---------- + raw_code : str + The raw cell code. + transformed_code : str + The executable Python source code to cache and compile. + number : int + A number which forms part of the code's name. Used for the execution + counter. + + Returns + ------- + The computed filename. + """ + return code_name(transformed_code, number) + + def format_code_name(self, name: str) -> tuple[str, str] | None: + """Return a user-friendly label and name for a code block. + + Parameters + ---------- + name : str + The name for the code block returned from get_code_name + + Returns + ------- + A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used. + """ + if name in self._filename_map: + return "Cell", "In[%s]" % self._filename_map[name] + + def cache(self, transformed_code: str, number: int = 0, raw_code: str | None = None) -> str: """Make a name for a block of code, and cache the code. Parameters ---------- - code : str - The Python source code to cache. + transformed_code : str + The executable Python source code to cache and compile. number : int - A number which forms part of the code's name. Used for the execution - counter. + A number which forms part of the code's name. Used for the execution + counter. + raw_code : str + The raw code before transformation, if None, set to `transformed_code`. Returns ------- The name of the cached code (as a string). Pass this as the filename argument to compilation, so that tracebacks are correctly hooked up. """ - name = code_name(code, number) - entry = (len(code), time.time(), - [line+'\n' for line in code.splitlines()], name) + if raw_code is None: + raw_code = transformed_code + + name = self.get_code_name(raw_code, transformed_code, number) + + # Save the execution count + self._filename_map[name] = number + + # Since Python 2.5, setting mtime to `None` means the lines will + # never be removed by `linecache.checkcache`. This means all the + # monkeypatching has *never* been necessary, since this code was + # only added in 2010, at which point IPython had already stopped + # supporting Python 2.4. + # + # Note that `linecache.clearcache` and `linecache.updatecache` may + # still remove our code from the cache, but those show explicit + # intent, and we should not try to interfere. Normally the former + # is never called except when out of memory, and the latter is only + # called for lines *not* in the cache. + entry = ( + len(transformed_code), + None, + [line + "\n" for line in transformed_code.splitlines()], + name, + ) linecache.cache[name] = entry - linecache._ipython_cache[name] = entry return name -def check_linecache_ipython(*args): - """Call linecache.checkcache() safely protecting our cached values. - """ - # First call the original checkcache as intended - linecache._checkcache_ori(*args) - # Then, update back the cache with our data, so that tracebacks related - # to our compiled codes can be produced. - linecache.cache.update(linecache._ipython_cache) + @contextmanager + def extra_flags(self, flags: int) -> Generator[None, None, None]: + ## bits that we'll set to 1 + turn_on_bits = ~self.flags & flags + + + self.flags = self.flags | flags + try: + yield + finally: + # turn off only the bits we turned on so that something like + # __future__ that set flags stays. + self.flags &= ~turn_on_bits diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 9858236905d..bb6ac000c3b 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -35,13 +35,13 @@ .. code:: - \\greek small letter alpha + \\GREEK SMALL LETTER ALPHA α Only valid Python identifiers will complete. Combining characters (like arrow or dots) are also available, unlike latex they need to be put after the their -counterpart that is to say, `F\\\\vec` is correct, not `\\\\vecF`. +counterpart that is to say, ``F\\\\vec`` is correct, not ``\\\\vecF``. Some browsers are known to display combining characters incorrectly. @@ -50,7 +50,7 @@ It is sometime challenging to know how to type a character, if you are using IPython, or any compatible frontend you can prepend backslash to the character -and press `` to expand it to its latex form. +and press :kbd:`Tab` to expand it to its latex form. .. code:: @@ -59,7 +59,8 @@ Both forward and backward completions can be deactivated by setting the -``Completer.backslash_combining_completions`` option to ``False``. +:std:configtrait:`Completer.backslash_combining_completions` option to +``False``. Experimental @@ -67,9 +68,9 @@ Starting with IPython 6.0, this module can make use of the Jedi library to generate completions both using static analysis of the code, and dynamically -inspecting multiple namespaces. The APIs attached to this new mechanism is -unstable and will raise unless use in an :any:`provisionalcompleter` context -manager. +inspecting multiple namespaces. Jedi is an autocompletion and static analysis +for Python. The APIs attached to this new mechanism is unstable and will +raise unless use in an :any:`provisionalcompleter` context manager. You will find that the following are experimental: @@ -84,9 +85,9 @@ We welcome any feedback on these new API, and we also encourage you to try this module in debug mode (start IPython with ``--Completer.debug=True``) in order -to have extra logging information is :any:`jedi` is crashing, or if current +to have extra logging information if :mod:`jedi` is crashing, or if current IPython completer pending deprecations are returning results not yet handled -by :any:`jedi` +by :mod:`jedi` Using Jedi for tab completion allow snippets like the following to work without having to execute any code: @@ -95,11 +96,78 @@ ... myvar[1].bi Tab completion will be able to infer that ``myvar[1]`` is a real number without -executing any code unlike the previously available ``IPCompleter.greedy`` +executing almost any code unlike the deprecated :any:`IPCompleter.greedy` option. -Be sure to update :any:`jedi` to the latest stable version or to try the +Be sure to update :mod:`jedi` to the latest stable version or to try the current development version to get better completions. + +Matchers +======== + +All completions routines are implemented using unified *Matchers* API. +The matchers API is provisional and subject to change without notice. + +The built-in matchers include: + +- :any:`IPCompleter.dict_key_matcher`: dictionary key completions, +- :any:`IPCompleter.magic_matcher`: completions for magics, +- :any:`IPCompleter.unicode_name_matcher`, + :any:`IPCompleter.fwd_unicode_matcher` + and :any:`IPCompleter.latex_name_matcher`: see `Forward latex/unicode completion`_, +- :any:`back_unicode_name_matcher` and :any:`back_latex_name_matcher`: see `Backward latex completion`_, +- :any:`IPCompleter.file_matcher`: paths to files and directories, +- :any:`IPCompleter.python_func_kw_matcher` - function keywords, +- :any:`IPCompleter.python_matcher` - globals and attributes, +- ``IPCompleter.jedi_matcher`` - static analysis with Jedi, +- :any:`IPCompleter.custom_completer_matcher` - pluggable completer with a default + implementation in :any:`InteractiveShell` which uses IPython hooks system + (`complete_command`) with string dispatch (including regular expressions). + Differently to other matchers, ``custom_completer_matcher`` will not suppress + Jedi results to match behaviour in earlier IPython versions. + +Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list. + +Matcher API +----------- + +Simplifying some details, the ``Matcher`` interface can described as + +.. code-block:: + + MatcherAPIv1 = Callable[[str], list[str]] + MatcherAPIv2 = Callable[[CompletionContext], SimpleMatcherResult] + + Matcher = MatcherAPIv1 | MatcherAPIv2 + +The ``MatcherAPIv1`` reflects the matcher API as available prior to IPython 8.6.0 +and remains supported as a simplest way for generating completions. This is also +currently the only API supported by the IPython hooks system `complete_command`. + +To distinguish between matcher versions ``matcher_api_version`` attribute is used. +More precisely, the API allows to omit ``matcher_api_version`` for v1 Matchers, +and requires a literal ``2`` for v2 Matchers. + +Once the API stabilises future versions may relax the requirement for specifying +``matcher_api_version`` by switching to :func:`functools.singledispatch`, therefore +please do not rely on the presence of ``matcher_api_version`` for any purposes. + +Suppression of competing matchers +--------------------------------- + +By default results from all matchers are combined, in the order determined by +their priority. Matchers can request to suppress results from subsequent +matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``. + +When multiple matchers simultaneously request suppression, the results from of +the matcher with higher priority will be returned. + +Sometimes it is desirable to suppress most but not all other matchers; +this can be achieved by adding a set of identifiers of matchers which +should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key. + +The suppression behaviour can is user-configurable via +:std:configtrait:`IPCompleter.suppress_competing_matchers`. """ @@ -109,53 +177,120 @@ # Some of this code originated from rlcompleter in the Python standard library # Copyright (C) 2001 Python Software Foundation, www.python.org - -import __main__ +from __future__ import annotations import builtins as builtin_mod +import enum import glob -import time +import importlib.util import inspect import itertools import keyword +import ast import os import re -import sys -import unicodedata import string +import sys +import tokenize +import time import warnings - +from ast import literal_eval +from collections import defaultdict from contextlib import contextmanager -from importlib import import_module -from typing import Iterator, List, Tuple, Iterable, Union -from types import SimpleNamespace - -from traitlets.config.configurable import Configurable -from IPython.core.error import TryNext -from IPython.core.inputtransformer2 import ESC_MAGIC -from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol -from IPython.core.oinspect import InspectColors +from dataclasses import dataclass +from functools import cached_property, lru_cache, partial +from types import ModuleType, SimpleNamespace +from typing import ( + Union, + Any, + TYPE_CHECKING, + TypeVar, + Literal, +) +from collections.abc import Iterable, Iterator, Sequence, Sized + +from IPython.core.error import TryNext, UsageError +from IPython.core.inputtransformer2 import ( + ESC_MAGIC, + SystemAssign, + make_tokens_by_line, +) +from IPython.testing.skipdoctest import skip_doctest from IPython.utils import generics +from IPython.utils.PyColorize import theme_table +from IPython.utils.decorators import sphinx_options from IPython.utils.dir2 import dir2, get_real_method +from IPython.utils.path import ensure_dir_exists from IPython.utils.process import arg_split -from traitlets import Bool, Enum, observe, Int +from traitlets import ( + Bool, + Enum, + Int, + List as ListTrait, + Unicode, + Dict as DictTrait, + DottedObjectName, + Union as UnionTrait, + observe, +) +from traitlets.config.configurable import Configurable +from traitlets.utils.importstring import import_item + +import __main__ + +from typing import cast, TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard + # skip module docstests -skip_doctest = True +__skip_doctest__ = True -try: + +# jedi is expensive to import (it pulls in parso, which compiles grammars), so +# only check for its presence here and import it lazily via `_get_jedi()` the +# first time a completion actually needs it. This keeps `import IPython` fast. +if TYPE_CHECKING: + import jedi + +JEDI_INSTALLED = importlib.util.find_spec("jedi") is not None + + +@lru_cache(maxsize=1) +def _get_jedi() -> ModuleType: + """Import, configure, and return the ``jedi`` module (cached).""" import jedi - jedi.settings.case_insensitive_completion = False - import jedi.api.helpers import jedi.api.classes - JEDI_INSTALLED = True -except ImportError: - JEDI_INSTALLED = False -#----------------------------------------------------------------------------- + import jedi.api.helpers + + jedi.settings.case_insensitive_completion = False + + # parso, which jedi parses with, logs copiously at DEBUG level; without + # this those records reach the user's session whenever IPython runs with + # a debug log level. This lived at the top of `IPython.core.logger` -- + # a module about `%logstart` session transcripts, nothing to do with + # jedi -- where it worked only because that module happened to be + # imported eagerly at startup. Configure it where jedi itself is + # configured instead, which is still before any parso record can be + # emitted, since parso is only reached through jedi. + import logging + + logging.getLogger("parso").setLevel(logging.WARNING) + + return jedi + + +# ----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- +# ranges where we have most of the valid unicode names. We could be more finer +# grained but is it worth it for performance While unicode have character in the +# range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I +# write this). With below range we cover them all, with a density of ~67% +# biggest next gap we consider only adds up about 1% density and there are 600 +# gaps that would need hard coding. +_UNICODE_RANGES = [(32, 0x3347A), (0xE0001, 0xE01F0)] + # Public API -__all__ = ['Completer','IPCompleter'] +__all__ = ["Completer", "IPCompleter"] if sys.platform == 'win32': PROTECTABLES = ' ' @@ -166,8 +301,17 @@ # may have trouble processing. MATCHES_LIMIT = 500 -_deprecation_readline_sentinel = object() +# Completion type reported when no type can be inferred. +_UNKNOWN_TYPE = "" + +# sentinel value to signal lack of a match +not_found = object() +# Regexes compiled once at import time; some of these are used on every +# completion request, so recompiling them per call would be wasteful. +_SNAKE_CASE_RE = re.compile(r"[^_]+(_[^_]+)+?\Z") +_LEADING_DASHES_RE = re.compile(r"^--", re.MULTILINE) +_IDENTIFIER_END_RE = re.compile(r"\w+$") class ProvisionalCompleterWarning(FutureWarning): """ @@ -180,37 +324,38 @@ class ProvisionalCompleterWarning(FutureWarning): warnings.filterwarnings('error', category=ProvisionalCompleterWarning) + +@skip_doctest @contextmanager def provisionalcompleter(action='ignore'): """ - - - This contest manager has to be used in any place where unstable completer + This context manager has to be used in any place where unstable completer behavior and API may be called. >>> with provisionalcompleter(): - ... completer.do_experimetal_things() # works + ... completer.do_experimental_things() # works >>> completer.do_experimental_things() # raises. - .. note:: Unstable + .. note:: + + Unstable By using this context manager you agree that the API in use may change without warning, and that you won't complain if they do so. - You also understand that if the API is not to you liking you should report - a bug to explain your use case upstream and improve the API and will loose - credibility if you complain after the API is make stable. + You also understand that, if the API is not to your liking, you should report + a bug to explain your use case upstream. - We'll be happy to get your feedback , feature request and improvement on - any of the unstable APIs ! + We'll be happy to get your feedback, feature requests, and improvements on + any of the unstable APIs! """ with warnings.catch_warnings(): warnings.filterwarnings(action, category=ProvisionalCompleterWarning) yield -def has_open_quotes(s): +def has_open_quotes(s: str) -> str | bool: """Return whether a string has open quotes. This simply counts whether the number of quote characters of either type in @@ -231,7 +376,7 @@ def has_open_quotes(s): return False -def protect_filename(s, protectables=PROTECTABLES): +def protect_filename(s: str, protectables: str = PROTECTABLES) -> str: """Escape a string to protect certain characters.""" if set(s) & set(protectables): if sys.platform == "win32": @@ -242,7 +387,7 @@ def protect_filename(s, protectables=PROTECTABLES): return s -def expand_user(path:str) -> Tuple[str, bool, str]: +def expand_user(path: str) -> tuple[str, bool, str]: """Expand ``~``-style usernames in strings. This is similar to :func:`os.path.expanduser`, but it computes and returns @@ -253,17 +398,17 @@ def expand_user(path:str) -> Tuple[str, bool, str]: Parameters ---------- path : str - String to be expanded. If no ~ is present, the output is the same as the - input. + String to be expanded. If no ~ is present, the output is the same as the + input. Returns ------- newpath : str - Result of ~ expansion in the input path. + Result of ~ expansion in the input path. tilde_expand : bool - Whether any expansion was performed or not. + Whether any expansion was performed or not. tilde_val : str - The value that ~ was replaced with. + The value that ~ was replaced with. """ # Default values tilde_expand = False @@ -312,11 +457,11 @@ def completions_sorting_key(word): if word.startswith('%%'): # If there's another % in there, this is something else, so leave it alone - if not "%" in word[2:]: + if "%" not in word[2:]: word = word[2:] prio2 = 2 elif word.startswith('%'): - if not "%" in word[1:]: + if "%" not in word[1:]: word = word[1:] prio2 = 1 @@ -338,24 +483,30 @@ def __init__(self, name): self.complete = name self.type = 'crashed' self.name_with_symbols = name - self.signature = '' - self._origin = 'fake' + self.signature = "" + self._origin = "fake" + self.text = "crashed" def __repr__(self): return '' +_JediCompletionLike = Union["jedi.api.Completion", _FakeJediCompletion] + + class Completion: """ - Completion object used and return by IPython completers. + Completion object used and returned by IPython completers. + + .. warning:: - .. warning:: Unstable + Unstable This function is unstable, API may change without warning. It will also raise unless use in proper context manager. This act as a middle ground :any:`Completion` object between the - :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion + :class:`jedi.api.classes.Completion` object and the Prompt Toolkit completion object. While Jedi need a lot of information about evaluator and how the code should be ran/inspected, PromptToolkit (and other frontend) mostly need user facing information. @@ -368,13 +519,25 @@ class Completion: ``IPython.python_matches``, ``IPython.magics_matches``...). """ - __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin'] - - def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None: - warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). " - "It may change without warnings. " - "Use in corresponding context manager.", - category=ProvisionalCompleterWarning, stacklevel=2) + __slots__ = ["_origin", "end", "signature", "start", "text", "type"] + + def __init__( + self, + start: int, + end: int, + text: str, + *, + type: str | None = None, + _origin="", + signature="", + ) -> None: + warnings.warn( + "``Completion`` is a provisional API (as of IPython 6.0). " + "It may change without warnings. " + "Use in corresponding context manager.", + category=ProvisionalCompleterWarning, + stacklevel=2, + ) self.start = start self.end = end @@ -387,7 +550,7 @@ def __repr__(self): return '' % \ (self.start, self.end, self.text, self.type or '?', self.signature or '?') - def __eq__(self, other)->Bool: + def __eq__(self, other) -> bool: """ Equality and hash do not hash the type (as some completer may not be able to infer the type), but are use to (partially) de-duplicate @@ -405,6 +568,248 @@ def __hash__(self): return hash((self.start, self.end, self.text)) +class SimpleCompletion: + """Completion item to be included in the dictionary returned by new-style Matcher (API v2). + + .. warning:: + + Provisional + + This class is used to describe the currently supported attributes of + simple completion items, and any additional implementation details + should not be relied on. Additional attributes may be included in + future versions, and meaning of text disambiguated from the current + dual meaning of "text to insert" and "text to used as a label". + """ + + __slots__ = ["text", "type"] + + def __init__(self, text: str, *, type: str | None = None): + self.text = text + self.type = type + + def __repr__(self): + return f"" + + +class _MatcherResultBase(TypedDict): + """Definition of dictionary to be returned by new-style Matcher (API v2).""" + + #: Suffix of the provided ``CompletionContext.token``, if not given defaults to full token. + matched_fragment: NotRequired[str] + + #: Whether to suppress results from all other matchers (True), some + #: matchers (set of identifiers) or none (False); default is False. + suppress: NotRequired[bool | set[str]] + + #: Identifiers of matchers which should NOT be suppressed when this matcher + #: requests to suppress all other matchers; defaults to an empty set. + do_not_suppress: NotRequired[set[str]] + + #: Are completions already ordered and should be left as-is? default is False. + ordered: NotRequired[bool] + + +@sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"]) +class SimpleMatcherResult(_MatcherResultBase, TypedDict): + """Result of new-style completion matcher.""" + + # note: TypedDict is added again to the inheritance chain + # in order to get __orig_bases__ for documentation + + #: List of candidate completions + completions: Sequence[SimpleCompletion] | Iterator[SimpleCompletion] + + +class _JediMatcherResult(_MatcherResultBase): + """Matching result returned by Jedi (will be processed differently)""" + + #: list of candidate completions + completions: Iterator[_JediCompletionLike] + + +AnyMatcherCompletion = _JediCompletionLike | SimpleCompletion +AnyCompletion = TypeVar("AnyCompletion", AnyMatcherCompletion, Completion) + + +@dataclass +class CompletionContext: + """Completion context provided as an argument to matchers in the Matcher API v2.""" + + # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`) + # which was not explicitly visible as an argument of the matcher, making any refactor + # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers + # from the completer, and make substituting them in sub-classes easier. + + #: Relevant fragment of code directly preceding the cursor. + #: The extraction of token is implemented via splitter heuristic + #: (following readline behaviour for legacy reasons), which is user configurable + #: (by switching the greedy mode). + token: str + + #: The full available content of the editor or buffer + full_text: str + + #: Cursor position in the line (the same for ``full_text`` and ``text``). + cursor_position: int + + #: Cursor line in ``full_text``. + cursor_line: int + + #: The maximum number of completions that will be used downstream. + #: Matchers can use this information to abort early. + #: The built-in Jedi matcher is currently excepted from this limit. + # If not given, return all possible completions. + limit: int | None + + @cached_property + def text_until_cursor(self) -> str: + return self.line_with_cursor[: self.cursor_position] + + @cached_property + def line_with_cursor(self) -> str: + return self.full_text.split("\n")[self.cursor_line] + + +#: Matcher results for API v2. +MatcherResult = SimpleMatcherResult | _JediMatcherResult + + +class _MatcherAPIv1Base(Protocol): + def __call__(self, text: str) -> list[str]: + """Call signature.""" + ... + + #: Used to construct the default matcher identifier + __qualname__: str + + +class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol): + #: API version + matcher_api_version: Literal[1] | None + + def __call__(self, text: str) -> list[str]: + """Call signature.""" + ... + + +#: Protocol describing Matcher API v1. +MatcherAPIv1: TypeAlias = _MatcherAPIv1Base | _MatcherAPIv1Total + + +class MatcherAPIv2(Protocol): + """Protocol describing Matcher API v2.""" + + #: API version + matcher_api_version: Literal[2] = 2 + + def __call__(self, context: CompletionContext) -> MatcherResult: + """Call signature.""" + ... + + #: Used to construct the default matcher identifier + __qualname__: str + + +Matcher: TypeAlias = MatcherAPIv1 | MatcherAPIv2 + + +def _is_matcher_v1(matcher: Matcher) -> TypeGuard[MatcherAPIv1]: + api_version = _get_matcher_api_version(matcher) + return api_version == 1 + + +def _is_matcher_v2(matcher: Matcher) -> TypeGuard[MatcherAPIv2]: + api_version = _get_matcher_api_version(matcher) + return api_version == 2 + + +def _is_sizable(value: Any) -> TypeGuard[Sized]: + """Determines whether objects is sizable""" + return hasattr(value, "__len__") + + +def _is_iterator(value: Any) -> TypeGuard[Iterator]: + """Determines whether objects is sizable""" + return hasattr(value, "__next__") + + +def has_any_completions(result: MatcherResult) -> bool: + """Check if any result includes any completions.""" + completions = result["completions"] + if _is_sizable(completions): + return len(completions) != 0 + if _is_iterator(completions): + try: + old_iterator = completions + first = next(old_iterator) + result["completions"] = cast( + Iterator[SimpleCompletion], + itertools.chain([first], old_iterator), + ) + return True + except StopIteration: + return False + raise ValueError( + "Completions returned by matcher need to be an Iterator or a Sizable" + ) + + +def completion_matcher( + *, + priority: float | None = None, + identifier: str | None = None, + api_version: int = 1, +) -> Callable[[Matcher], Matcher]: + """Adds attributes describing the matcher. + + Parameters + ---------- + priority : Optional[float] + The priority of the matcher, determines the order of execution of matchers. + Higher priority means that the matcher will be executed first. Defaults to 0. + identifier : Optional[str] + identifier of the matcher allowing users to modify the behaviour via traitlets, + and also used to for debugging (will be passed as ``origin`` with the completions). + + Defaults to matcher function's ``__qualname__`` (for example, + ``IPCompleter.file_matcher`` for the built-in matched defined + as a ``file_matcher`` method of the ``IPCompleter`` class). + api_version: Optional[int] + version of the Matcher API used by this matcher. + Currently supported values are 1 and 2. + Defaults to 1. + """ + + def wrapper(func: Matcher): + func.matcher_priority = priority or 0 # type: ignore + func.matcher_identifier = identifier or func.__qualname__ # type: ignore + func.matcher_api_version = api_version # type: ignore + if TYPE_CHECKING: + if api_version == 1: + func = cast(MatcherAPIv1, func) + elif api_version == 2: + func = cast(MatcherAPIv2, func) + return func + + return wrapper + + +def _get_matcher_priority(matcher: Matcher): + return getattr(matcher, "matcher_priority", 0) + + +def _get_matcher_id(matcher: Matcher): + return getattr(matcher, "matcher_identifier", matcher.__qualname__) + + +def _get_matcher_api_version(matcher): + return getattr(matcher, "matcher_api_version", 1) + + +context_matcher = partial(completion_matcher, api_version=2) + + _IC = Iterable[Completion] @@ -412,26 +817,25 @@ def _deduplicate_completions(text: str, completions: _IC)-> _IC: """ Deduplicate a set of completions. - .. warning:: Unstable + .. warning:: + + Unstable This function is unstable, API may change without warning. Parameters ---------- - text: str + text : str text that should be completed. - completions: Iterator[Completion] + completions : Iterator[Completion] iterator over the completions to deduplicate Yields ------ `Completions` objects - - Completions coming from multiple sources, may be different but end up having the same effect when applied to ``text``. If this is the case, this will consider completions as equal and only emit the first encountered. - Not folded in `completions()` yet for debugging purpose, and to detect when the IPython completer does return things that Jedi does not, but should be at some point. @@ -451,24 +855,29 @@ def _deduplicate_completions(text: str, completions: _IC)-> _IC: seen.add(new_text) -def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: +def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC: """ Rectify a set of completions to all have the same ``start`` and ``end`` - .. warning:: Unstable + .. warning:: + + Unstable This function is unstable, API may change without warning. It will also raise unless use in proper context manager. Parameters ---------- - text: str + text : str text that should be completed. - completions: Iterator[Completion] + completions : Iterator[Completion] iterator over the completions to rectify + _debug : bool + Log failed completion - - :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though + Notes + ----- + :class:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though the Jupyter Protocol requires them to behave like so. This will readjust the completion to have the same ``start`` and ``end`` by padding both extremities with surrounding text. @@ -497,7 +906,7 @@ def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: new_text = text[new_start:c.start] + c.text + text[c.end:new_end] if c._origin == 'jedi': seen_jedi.add(new_text) - elif c._origin == 'IPCompleter.python_matches': + elif c._origin == "IPCompleter.python_matcher": seen_python_matches.add(new_text) yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature) diff = seen_python_matches.difference(seen_jedi) @@ -513,7 +922,7 @@ def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: GREEDY_DELIMS = ' =\r\n' -class CompletionSplitter(object): +class CompletionSplitter: """An object to split an input line in a manner similar to readline. By having our own implementation, we can expose readline-like completion in @@ -560,25 +969,66 @@ def delims(self, delims): def split_line(self, line, cursor_pos=None): """Split a line of text with a cursor at the given position. """ - l = line if cursor_pos is None else line[:cursor_pos] - return self._delim_re.split(l)[-1] + cut_line = line if cursor_pos is None else line[:cursor_pos] + return self._delim_re.split(cut_line)[-1] + +# keywords which end the expression before them (`x if c else d`, `k in d`), +# as opposed to the constant keywords which are expressions (`None`, `True`) +_KEYWORDS_ENDING_EXPRESSION = frozenset(keyword.kwlist) - {"True", "False", "None"} class Completer(Configurable): - greedy = Bool(False, - help="""Activate greedy completion - PENDING DEPRECTION. this is now mostly taken care of with Jedi. + greedy = Bool( + False, + help="""Activate greedy completion. - This will enable completion on elements of lists, results of function calls, etc., - but can be unsafe because the code is actually evaluated on TAB. - """ + .. deprecated:: 8.8 + Use :std:configtrait:`Completer.evaluation` and :std:configtrait:`Completer.auto_close_dict_keys` instead. + + When enabled in IPython 8.8 or newer, changes configuration as follows: + + - ``Completer.evaluation = 'unsafe'`` + - ``Completer.auto_close_dict_keys = True`` + + Kept (deprecated, not yet removed) because downstream projects' + test suites still set it via ``%config``. + """, + ).tag(config=True) + + evaluation = Enum( + ("forbidden", "minimal", "limited", "unsafe", "dangerous"), + default_value="limited", + help="""Policy for code evaluation under completion. + + Successive options allow to enable more eager evaluation for better + completion suggestions, including for nested dictionaries, nested lists, + or even results of function calls. + Setting ``unsafe`` or higher can lead to evaluation of arbitrary user + code on :kbd:`Tab` with potentially unwanted or dangerous side effects. + + Allowed values are: + + - ``forbidden``: no evaluation of code is permitted, + - ``minimal``: evaluation of literals and access to built-in namespace; + no item/attribute evaluation, no access to locals/globals, + no evaluation of any operations or comparisons. + - ``limited``: access to all namespaces, evaluation of hard-coded methods + (for example: :py:meth:`dict.keys`, :py:meth:`object.__getattr__`, + :py:meth:`object.__getitem__`) on allow-listed objects (for example: + :py:class:`dict`, :py:class:`list`, :py:class:`tuple`, ``pandas.Series``), + - ``unsafe``: evaluation of all methods and function calls but not of + syntax with side-effects like `del x`, + - ``dangerous``: completely arbitrary evaluation; does not support auto-import. + + To override specific elements of the policy, you can use ``policy_overrides`` trait. + """, ).tag(config=True) - use_jedi = Bool(default_value=False, + use_jedi = Bool(default_value=JEDI_INSTALLED, help="Experimental: Use Jedi to generate autocompletions. " - "Off by default.").tag(config=True) + "Default to True if jedi is installed.").tag(config=True) jedi_compute_type_timeout = Int(default_value=400, help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types. @@ -596,7 +1046,62 @@ class Completer(Configurable): "Includes completion of latex commands, unicode names, and expanding " "unicode characters back to latex commands.").tag(config=True) + auto_close_dict_keys = Bool( + False, + help=""" + Enable auto-closing dictionary keys. + + When enabled string keys will be suffixed with a final quote + (matching the opening quote), tuple keys will also receive a + separating comma if needed, and keys which are final will + receive a closing bracket (``]``). + """, + ).tag(config=True) + + policy_overrides = DictTrait( + default_value={}, + key_trait=Unicode(), + help="""Overrides for policy evaluation. + + For example, to enable auto-import on completion specify: + + .. code-block:: + + ipython --Completer.policy_overrides='{"allow_auto_import": True}' --Completer.use_jedi=False + + """, + ).tag(config=True) + @observe("evaluation") + def _evaluation_changed(self, _change): + from IPython.core.guarded_eval import _validate_policy_overrides + + _validate_policy_overrides( + policy_name=self.evaluation, policy_overrides=self.policy_overrides + ) + + @observe("policy_overrides") + def _policy_overrides_changed(self, _change): + from IPython.core.guarded_eval import _validate_policy_overrides + + _validate_policy_overrides( + policy_name=self.evaluation, policy_overrides=self.policy_overrides + ) + + auto_import_method = DottedObjectName( + default_value="importlib.import_module", + allow_none=True, + help="""\ + Provisional: + This is a provisional API in IPython 9.3, it may change without warnings. + + A fully qualified path to an auto-import method for use by completer. + The function should take a single string and return `ModuleType` and + can raise `ImportError` exception if module is not found. + + The default auto-import implementation does not populate the user namespace with the imported module. + """, + ).tag(config=True) def __init__(self, namespace=None, global_namespace=None, **kwargs): """Create a new completer for the command line. @@ -627,7 +1132,9 @@ def __init__(self, namespace=None, global_namespace=None, **kwargs): else: self.global_namespace = global_namespace - super(Completer, self).__init__(**kwargs) + self.custom_matchers = [] + + super().__init__(**kwargs) def complete(self, text, state): """Return the next possible completion for 'text'. @@ -649,32 +1156,67 @@ def complete(self, text, state): except IndexError: return None - def global_matches(self, text): + def global_matches(self, text: str, context: CompletionContext | None = None): """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace or self.global_namespace that match. """ + from IPython.core.guarded_eval import EvaluationContext, guarded_eval + matches = [] match_append = matches.append n = len(text) - for lst in [keyword.kwlist, - builtin_mod.__dict__.keys(), - self.namespace.keys(), - self.global_namespace.keys()]: + + search_lists = [ + keyword.kwlist, + builtin_mod.__dict__.keys(), + list(self.namespace.keys()), + list(self.global_namespace.keys()), + ] + if context and context.full_text.count("\n") > 1: + # try to evaluate on full buffer + previous_lines = "\n".join( + context.full_text.split("\n")[: context.cursor_line] + ) + if previous_lines: + all_code_lines_before_cursor = ( + self._extract_code(previous_lines) + "\n" + text + ) + context = EvaluationContext( + globals=self.global_namespace, + locals=self.namespace, + evaluation=self.evaluation, + auto_import=self._auto_import, + policy_overrides=self.policy_overrides, + ) + try: + obj = guarded_eval( + all_code_lines_before_cursor, + context, + ) + except Exception as e: + if self.debug: + warnings.warn(f"Evaluation exception {e}") + + search_lists.append(list(context.transient_locals.keys())) + + for lst in search_lists: for word in lst: if word[:n] == text and word != "__builtins__": match_append(word) - snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z") - for lst in [self.namespace.keys(), - self.global_namespace.keys()]: - shortened = {"_".join([sub[0] for sub in word.split('_')]) : word - for word in lst if snake_case_re.match(word)} + for lst in [list(self.namespace.keys()), list(self.global_namespace.keys())]: + shortened = { + "_".join([sub[0] for sub in word.split("_")]): word + for word in lst + if _SNAKE_CASE_RE.match(word) + } for word in shortened.keys(): if word[:n] == text and word != "__builtins__": match_append(shortened[word]) + return matches def attr_matches(self, text): @@ -690,32 +1232,100 @@ def attr_matches(self, text): with a __getattr__ hook is evaluated. """ + return self._attr_matches(text)[0] - # Another option, seems to work great. Catches things like ''. - m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) + # we simple attribute matching with normal identifiers. + _ATTR_MATCH_RE = re.compile(r"(.+)\.(\w*)$") - if m: - expr, attr = m.group(1, 3) - elif self.greedy: - m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) - if not m2: - return [] - expr, attr = m2.group(1,2) - else: - return [] + def _strip_code_before_operator(self, code: str) -> str: + o_parens = {"(", "[", "{"} + c_parens = {")", "]", "}"} + # Dry-run tokenize to catch errors try: - obj = eval(expr, self.namespace) - except: + _ = list(tokenize.generate_tokens(iter(code.splitlines()).__next__)) + except tokenize.TokenError: + # Try trimming the expression and retrying + trimmed_code = self._trim_expr(code) try: - obj = eval(expr, self.global_namespace) - except: - return [] + _ = list( + tokenize.generate_tokens(iter(trimmed_code.splitlines()).__next__) + ) + code = trimmed_code + except tokenize.TokenError: + return code + + tokens = _parse_tokens(code) + encountered_operator = False + after_operator = [] + nesting_level = 0 + + for t in tokens: + if t.type == tokenize.OP: + if t.string in o_parens: + nesting_level += 1 + elif t.string in c_parens: + nesting_level -= 1 + elif t.string != "." and nesting_level == 0: + encountered_operator = True + after_operator = [] + continue + elif ( + t.type == tokenize.NAME + and nesting_level == 0 + and t.string in _KEYWORDS_ENDING_EXPRESSION + ): + # a keyword (`in`, `else`, `lambda`, `not`, ...) ends the + # expression before it just like an operator does + encountered_operator = True + after_operator = [] + continue + + if encountered_operator: + after_operator.append(t.string) - if self.limit_to__all__ and hasattr(obj, '__all__'): - words = get__all__entries(obj) + if encountered_operator: + return "".join(after_operator) else: - words = dir2(obj) + return code + + def _extract_code(self, line: str): + """No-op in Completer, but can be used in subclasses to customise behaviour""" + return line + + def _attr_matches( + self, + text: str, + include_prefix: bool = True, + context: CompletionContext | None = None, + ) -> tuple[Sequence[str], str]: + m2 = self._ATTR_MATCH_RE.match(text) + if not m2: + return [], "" + expr, attr = m2.group(1, 2) + try: + expr = self._strip_code_before_operator(expr) + except tokenize.TokenError: + pass + + obj = not_found + if context: + # try to evaluate on full buffer + previous_lines = "\n".join( + context.full_text.split("\n")[: context.cursor_line] + ) + if previous_lines: + all_code_lines_before_cursor = ( + self._extract_code(previous_lines) + "\n" + expr + ) + obj = self._evaluate_expr(all_code_lines_before_cursor) + + if obj is not_found: + obj = self._evaluate_expr(expr) + if obj is not_found: + return [], "" + + words = dir2(obj) try: words = generics.complete_object(obj, words) @@ -725,80 +1335,344 @@ def attr_matches(self, text): raise except Exception: # Silence errors from completion function - #raise # dbg pass # Build match list to return n = len(attr) - return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ] + + # Note: ideally we would just return words here and the prefix + # reconciliator would know that we intend to append to rather than + # replace the input text; this requires refactoring to return range + # which ought to be replaced (as does jedi). + if include_prefix: + tokens = _parse_tokens(expr) + rev_tokens = reversed(tokens) + skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE} + name_turn = True + + parts = [] + for token in rev_tokens: + if token.type in skip_over: + continue + if token.type == tokenize.NAME and name_turn: + parts.append(token.string) + name_turn = False + elif ( + token.type == tokenize.OP and token.string == "." and not name_turn + ): + parts.append(token.string) + name_turn = True + else: + # short-circuit if not empty nor name token + break + + prefix_after_space = "".join(reversed(parts)) + else: + prefix_after_space = "" + + return ( + ["{}.{}".format(prefix_after_space, w) for w in words if w[:n] == attr], + "." + attr, + ) + + def _trim_expr(self, code: str) -> str: + """ + Trim the code until it is a valid expression and not a tuple; + + return the trimmed expression for guarded_eval. + """ + while code: + code = code[1:] + try: + res = ast.parse(code) + except SyntaxError: + continue + + assert res is not None + if len(res.body) != 1: + continue + if not isinstance(res.body[0], ast.Expr): + continue + expr = res.body[0].value + if isinstance(expr, ast.Tuple) and not code[-1] == ")": + # we skip implicit tuple, like when trimming `fun(a,b` + # as `a,b` would be a tuple, and we actually expect to get only `b` + continue + return code + return "" + + def _evaluate_expr(self, expr): + from IPython.core.guarded_eval import EvaluationContext, guarded_eval + + obj = not_found + done = False + while not done and expr: + try: + obj = guarded_eval( + expr, + EvaluationContext( + globals=self.global_namespace, + locals=self.namespace, + evaluation=self.evaluation, + auto_import=self._auto_import, + policy_overrides=self.policy_overrides, + ), + ) + done = True + except (SyntaxError, TypeError) as e: + if self.debug: + warnings.warn(f"Trimming because of {e}") + # TypeError can show up with something like `+ d` + # where `d` is a dictionary. + + # trim the expression to remove any invalid prefix + # e.g. user starts `(d[`, so we get `expr = '(d'`, + # where parenthesis is not closed. + # TODO: make this faster by reusing parts of the computation? + expr = self._trim_expr(expr) + except Exception as e: + if self.debug: + warnings.warn(f"Evaluation exception {e}") + done = True + if self.debug: + warnings.warn(f"Resolved to {obj}") + return obj + + @property + def _auto_import(self): + if self.auto_import_method is None: + return None + if not hasattr(self, "_auto_import_func"): + self._auto_import_func = import_item(self.auto_import_method) + return self._auto_import_func -def get__all__entries(obj): +def get__all__entries(obj: Any) -> list[str]: """returns the strings in the __all__ attribute""" try: words = getattr(obj, '__all__') - except: + except Exception: return [] return [w for w in words if isinstance(w, str)] -def match_dict_keys(keys: List[str], prefix: str, delims: str): +class _DictKeyState(enum.Flag): + """Represent state of the key match in context of other possible matches. + + - given `d1 = {'a': 1}` completion on `d1['` will yield `{'a': END_OF_ITEM}` as there is no tuple. + - given `d2 = {('a', 'b'): 1}`: `d2['a', '` will yield `{'b': END_OF_TUPLE}` as there is no tuple members to add beyond `'b'`. + - given `d3 = {('a', 'b'): 1}`: `d3['` will yield `{'a': IN_TUPLE}` as `'a'` can be added. + - given `d4 = {'a': 1, ('a', 'b'): 2}`: `d4['` will yield `{'a': END_OF_ITEM & END_OF_TUPLE}` + """ + + BASELINE = 0 + END_OF_ITEM = enum.auto() + END_OF_TUPLE = enum.auto() + IN_TUPLE = enum.auto() + + +def _parse_tokens(c: str) -> list[tokenize.TokenInfo]: + """Parse tokens even if there is an error.""" + tokens = [] + token_generator = tokenize.generate_tokens(iter(c.splitlines()).__next__) + while True: + try: + tokens.append(next(token_generator)) + except tokenize.TokenError: + return tokens + except StopIteration: + return tokens + + +def _match_number_in_dict_key_prefix(prefix: str) -> str | None: + """Match any valid Python numeric literal in a prefix of dictionary keys. + + References: + - https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals + - https://docs.python.org/3/library/tokenize.html + """ + if prefix[-1].isspace(): + # if user typed a space we do not have anything to complete + # even if there was a valid number token before + return None + tokens = _parse_tokens(prefix) + rev_tokens = reversed(tokens) + skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE} + number = None + for token in rev_tokens: + if token.type in skip_over: + continue + if number is None: + if token.type == tokenize.NUMBER: + number = token.string + continue + else: + # we did not match a number + return None + if token.type == tokenize.OP: + if token.string == ",": + break + if token.string in {"+", "-"}: + number = token.string + number + else: + return None + return number + + +_INT_FORMATS = { + "0b": bin, + "0o": oct, + "0x": hex, +} + + +def match_dict_keys( + keys: list[str | bytes | tuple[str | bytes, ...]], + prefix: str, + delims: str, + extra_prefix: tuple[str | bytes, ...] | None = None, +) -> tuple[str, int, dict[str, _DictKeyState]]: """Used by dict_key_matches, matching the prefix to a list of keys Parameters - ========== - keys: + ---------- + keys list of keys in dictionary currently being completed. - prefix: - Part of the text already typed by the user. e.g. `mydict[b'fo` - delims: + prefix + Part of the text already typed by the user. E.g. `mydict[b'fo` + delims String of delimiters to consider when finding the current key. + extra_prefix : optional + Part of the text already typed in multi-key index cases. E.g. for + `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`. Returns - ======= - + ------- A tuple of three elements: ``quote``, ``token_start``, ``matched``, with ``quote`` being the quote that need to be used to close current string. ``token_start`` the position where the replacement should start occurring, - ``matches`` a list of replacement/completion - + ``matches`` a dictionary of replacement/completion keys on keys and values + indicating whether the state. """ + prefix_tuple = extra_prefix if extra_prefix else () + + prefix_tuple_size = sum( + [ + # for pandas, do not count slices as taking space + not isinstance(k, slice) + for k in prefix_tuple + ] + ) + text_serializable_types = (str, bytes, int, float, slice) + + def filter_prefix_tuple(key): + # Reject too short keys + if len(key) <= prefix_tuple_size: + return False + # Reject keys which cannot be serialised to text + for k in key: + if not isinstance(k, text_serializable_types): + return False + # Reject keys that do not match the prefix + for k, pt in zip(key, prefix_tuple): + if k != pt and not isinstance(pt, slice): + return False + # All checks passed! + return True + + filtered_key_is_final: dict[ + str | bytes | int | float, _DictKeyState + ] = defaultdict(lambda: _DictKeyState.BASELINE) + + for k in keys: + # If at least one of the matches is not final, mark as undetermined. + # This can happen with `d = {111: 'b', (111, 222): 'a'}` where + # `111` appears final on first match but is not final on the second. + + if isinstance(k, tuple): + if filter_prefix_tuple(k): + key_fragment = k[prefix_tuple_size] + filtered_key_is_final[key_fragment] |= ( + _DictKeyState.END_OF_TUPLE + if len(k) == prefix_tuple_size + 1 + else _DictKeyState.IN_TUPLE + ) + elif prefix_tuple_size > 0: + # we are completing a tuple but this key is not a tuple, + # so we should ignore it + pass + else: + if isinstance(k, text_serializable_types): + filtered_key_is_final[k] |= _DictKeyState.END_OF_ITEM + + filtered_keys = filtered_key_is_final.keys() + if not prefix: - return None, 0, [repr(k) for k in keys - if isinstance(k, (str, bytes))] - quote_match = re.search('["\']', prefix) - quote = quote_match.group() - try: - prefix_str = eval(prefix + quote, {}) - except Exception: - return None, 0, [] + return "", 0, {repr(k): v for k, v in filtered_key_is_final.items()} + + quote_match = re.search("(?:\"|')", prefix) + is_user_prefix_numeric = False + + if quote_match: + quote = quote_match.group() + valid_prefix = prefix + quote + try: + prefix_str = literal_eval(valid_prefix) + except Exception: + return "", 0, {} + else: + # If it does not look like a string, let's assume + # we are dealing with a number or variable. + number_match = _match_number_in_dict_key_prefix(prefix) + + # We do not want the key matcher to suggest variable names so we yield: + if number_match is None: + # The alternative would be to assume that user forgort the quote + # and if the substring matches, suggest adding it at the start. + return "", 0, {} + + prefix_str = number_match + is_user_prefix_numeric = True + quote = "" pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$' token_match = re.search(pattern, prefix, re.UNICODE) + assert token_match is not None # silence mypy token_start = token_match.start() token_prefix = token_match.group() - matched = [] - for key in keys: + matched: dict[str, _DictKeyState] = {} + + str_key: str | bytes + + for key in filtered_keys: + if isinstance(key, (int, float)): + # User typed a number but this key is not a number. + if not is_user_prefix_numeric: + continue + str_key = str(key) + if isinstance(key, int): + int_base = prefix_str[:2].lower() + # if user typed integer using binary/oct/hex notation: + if int_base in _INT_FORMATS: + int_format = _INT_FORMATS[int_base] + str_key = int_format(key) + else: + # User typed a string but this key is a number. + if is_user_prefix_numeric: + continue + str_key = key try: - if not key.startswith(prefix_str): + if not str_key.startswith(prefix_str): continue except (AttributeError, TypeError, UnicodeError): # Python 3+ TypeError on b'a'.startswith('a') or vice-versa continue # reformat remainder of key to begin with prefix - rem = key[len(prefix_str):] + rem = str_key[len(prefix_str) :] # force repr wrapped in ' rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"') - if rem_repr.startswith('u') and prefix[0] not in 'uU': - # Found key is unicode, but prefix is Py2 string. - # Therefore attempt to interpret key as string. - try: - rem_repr = repr(rem.encode('ascii') + '"') - except UnicodeEncodeError: - continue - rem_repr = rem_repr[1 + rem_repr.index("'"):-2] if quote == '"': # The entered prefix is quoted with ", @@ -807,19 +1681,19 @@ def match_dict_keys(keys: List[str], prefix: str, delims: str): rem_repr = rem_repr.replace('"', '\\"') # then reinsert prefix from start of token - matched.append('%s%s' % (token_prefix, rem_repr)) + match = "{}{}".format(token_prefix, rem_repr) + + matched[match] = filtered_key_is_final[key] return quote, token_start, matched def cursor_to_position(text:str, line:int, column:int)->int: """ - Convert the (line,column) position of the cursor in text to an offset in a string. Parameters ---------- - text : str The text in which to calculate the cursor offset line : int @@ -827,21 +1701,22 @@ def cursor_to_position(text:str, line:int, column:int)->int: column : int Column of the cursor 0-indexed - Return - ------ - Position of the cursor in ``text``, 0-indexed. + Returns + ------- + Position of the cursor in ``text``, 0-indexed. See Also -------- - position_to_cursor: reciprocal of this function + position_to_cursor : reciprocal of this function """ lines = text.split('\n') - assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines))) + assert line <= len(lines), f'{str(line)} <= {str(len(lines))}' + + return sum(len(line) + 1 for line in lines[:line]) + column - return sum(len(l) + 1 for l in lines[:line]) + column -def position_to_cursor(text:str, offset:int)->Tuple[int, int]: +def position_to_cursor(text: str, offset: int) -> tuple[int, int]: """ Convert the position of the cursor in text (0 indexed) to a line number(0-indexed) and a column number (0-indexed) pair @@ -850,26 +1725,23 @@ def position_to_cursor(text:str, offset:int)->Tuple[int, int]: Parameters ---------- - text : str The text in which to calculate the cursor offset offset : int Position of the cursor in ``text``, 0-indexed. - Return - ------ + Returns + ------- (line, column) : (int, int) Line of the cursor; 0-indexed, column of the cursor 0-indexed - See Also -------- cursor_to_position : reciprocal of this function - """ - assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text)) + assert 0 <= offset <= len(text) , "0 <= {} <= {}".format(offset , len(text)) before = text[:offset] blines = before.split('\n') # ! splitnes trim trailing \n @@ -878,15 +1750,30 @@ def position_to_cursor(text:str, offset:int)->Tuple[int, int]: return line, col -def _safe_isinstance(obj, module, class_name): +def _safe_isinstance(obj, module, class_name, *attrs): """Checks if obj is an instance of module.class_name if loaded """ - return (module in sys.modules and - isinstance(obj, getattr(import_module(module), class_name))) + if module in sys.modules: + m = sys.modules[module] + for attr in [class_name, *attrs]: + m = getattr(m, attr) + return isinstance(obj, m) + + +@context_matcher() +def back_unicode_name_matcher(context: CompletionContext): + """Match Unicode characters back to Unicode name + + Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API. + """ + fragment, matches = back_unicode_name_matches(context.text_until_cursor) + return _convert_matcher_v1_result_to_v2( + matches, type="unicode", fragment=fragment, suppress_if_matches=True + ) -def back_unicode_name_matches(text): - u"""Match unicode characters back to unicode name +def back_unicode_name_matches(text: str) -> tuple[str, Sequence[str]]: + """Match Unicode characters back to Unicode name This does ``☃`` -> ``\\snowman`` @@ -895,53 +1782,78 @@ def back_unicode_name_matches(text): This will not either back-complete standard sequences like \\n, \\b ... - Used on Python 3 only. + .. deprecated:: 8.6 + You can use :meth:`back_unicode_name_matcher` instead. + + Returns + ======= + + Return a tuple with two elements: + + - The Unicode character that was matched (preceded with a backslash), or + empty string, + - a sequence (of 1), name for the match Unicode character, preceded by + backslash, or empty if no match. """ + import unicodedata + if len(text)<2: - return u'', () + return '', () maybe_slash = text[-2] if maybe_slash != '\\': - return u'', () + return '', () char = text[-1] # no expand on quote for completion in strings. # nor backcomplete standard ascii keys - if char in string.ascii_letters or char in ['"',"'"]: - return u'', () + if char in string.ascii_letters or char in ('"',"'"): + return '', () try : unic = unicodedata.name(char) - return '\\'+char,['\\'+unic] + return '\\'+char,('\\'+unic,) except KeyError: pass - return u'', () + return '', () -def back_latex_name_matches(text:str): + +@context_matcher() +def back_latex_name_matcher(context: CompletionContext) -> SimpleMatcherResult: """Match latex characters back to unicode name This does ``\\ℵ`` -> ``\\aleph`` - - Used on Python 3 only. """ + from IPython.core.latex_symbols import reverse_latex_symbol + + + text = context.text_until_cursor + no_match = { + "completions": [], + "suppress": False, + } + if len(text)<2: - return u'', () + return no_match maybe_slash = text[-2] if maybe_slash != '\\': - return u'', () - + return no_match char = text[-1] # no expand on quote for completion in strings. # nor backcomplete standard ascii keys - if char in string.ascii_letters or char in ['"',"'"]: - return u'', () + if char in string.ascii_letters or char in ('"',"'"): + return no_match try : latex = reverse_latex_symbol[char] # '\\' replace the \ as well - return '\\'+char,[latex] + return { + "completions": [SimpleCompletion(text=latex, type="latex")], + "suppress": True, + "matched_fragment": "\\" + char, + } except KeyError: pass - return u'', () + return no_match def _formatparamchildren(parameter) -> str: """ @@ -949,18 +1861,15 @@ def _formatparamchildren(parameter) -> str: Jedi does not expose a simple way to get `param=value` from its API. - Parameter - ========= - - parameter: + Parameters + ---------- + parameter Jedi's function `Param` Returns - ======= - + ------- A string like 'a', 'b=1', '*args', '**kwargs' - """ description = parameter.description if not description.startswith('param '): @@ -972,45 +1881,183 @@ def _make_signature(completion)-> str: """ Make the signature from a jedi completion - Parameter - ========= - - completion: jedi.Completion + Parameters + ---------- + completion : jedi.Completion object does not complete a function type Returns - ======= - + ------- a string consisting of the function signature, with the parenthesis but without the function name. example: `(a, *args, b=1, **kwargs)` """ - return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for p in completion.params) if f]) + # it looks like this might work on jedi 0.17 + if hasattr(completion, 'get_signatures'): + signatures = completion.get_signatures() + if not signatures: + return '(?)' + + c0 = completion.get_signatures()[0] + return '('+c0.to_string().split('(', maxsplit=1)[1] + + return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures() + for p in signature.defined_names()) if f]) + + +_CompleteResult = dict[str, MatcherResult] + + +DICT_MATCHER_REGEX = re.compile( + r"""(?x) +( # match dict-referring - or any get item object - expression + .+ +) +\[ # open bracket +\s* # and optional whitespace +# Capture any number of serializable objects (e.g. "a", "b", 'c') +# and slices +((?:(?: + (?: # closed string + [uUbB]? # string prefix (r not handled) + (?: + '(?:[^']|(? SimpleMatcherResult: + """same as _convert_matcher_v1_result_to_v2 but fragment=None, and suppress_if_matches is False by construction""" + return SimpleMatcherResult( + completions=[SimpleCompletion(text=match, type=type) for match in matches], + suppress=False, + ) + + +def _convert_matcher_v1_result_to_v2( + matches: Sequence[str], + type: str, + fragment: str | None = None, + suppress_if_matches: bool = False, +) -> SimpleMatcherResult: + """Utility to help with transition""" + result = { + "completions": [SimpleCompletion(text=match, type=type) for match in matches], + "suppress": (True if matches else False) if suppress_if_matches else False, + } + if fragment is not None: + result["matched_fragment"] = fragment + return cast(SimpleMatcherResult, result) + class IPCompleter(Completer): """Extension of the completer class with IPython-specific features""" - @observe('greedy') + @observe("greedy") def _greedy_changed(self, change): """update the splitter and readline delims when greedy is changed""" - if change['new']: + if change["new"]: + self.evaluation = "unsafe" + self.auto_close_dict_keys = True self.splitter.delims = GREEDY_DELIMS else: + self.evaluation = "limited" + self.auto_close_dict_keys = False self.splitter.delims = DELIMS - dict_keys_only = Bool(False, - help="""Whether to show dict key matches only""") + dict_keys_only = Bool( + False, + help=""" + Whether to show dict key matches only. - merge_completions = Bool(True, + (disables all matchers except for `IPCompleter.dict_key_matcher`). + """, + ) + + suppress_competing_matchers = UnionTrait( + [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))], + default_value=None, + help=""" + Whether to suppress completions from other *Matchers*. + + When set to ``None`` (default) the matchers will attempt to auto-detect + whether suppression of other matchers is desirable. For example, at + the beginning of a line followed by `%` we expect a magic completion + to be the only applicable option, and after ``my_dict['`` we usually + expect a completion with an existing dictionary key. + + If you want to disable this heuristic and see completions from all matchers, + set ``IPCompleter.suppress_competing_matchers = False``. + To disable the heuristic for specific matchers provide a dictionary mapping: + ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``. + + Set ``IPCompleter.suppress_competing_matchers = True`` to limit + completions to the set of matchers with the highest priority; + this is equivalent to ``IPCompleter.merge_completions`` and + can be beneficial for performance, but will sometimes omit relevant + candidates from matchers further down the priority list. + """, + ).tag(config=True) + + merge_completions = Bool( + True, help="""Whether to merge completion results into a single list If False, only the completion results from the first non-empty completer will be returned. - """ + + As of version 8.6.0, setting the value to ``False`` is an alias for: + ``IPCompleter.suppress_competing_matchers = True.``. + """, ).tag(config=True) - omit__names = Enum((0,1,2), default_value=2, + + disable_matchers = ListTrait( + Unicode(), + help="""List of matchers to disable. + + The list should contain matcher identifiers (see :any:`completion_matcher`). + """, + ).tag(config=True) + + omit__names = Enum( + (0, 1, 2), + default_value=2, help="""Instruct the completer to omit private method names Specifically, when completing on ``object.``. @@ -1022,63 +2069,51 @@ def _greedy_changed(self, change): When 0: nothing will be excluded. """ ).tag(config=True) - limit_to__all__ = Bool(False, - help=""" - DEPRECATED as of version 5.0. - - Instruct the completer to use __all__ for the completion - - Specifically, when completing on ``object.``. - - When True: only those names in obj.__all__ will be included. - - When False [default]: the __all__ attribute is ignored - """, + profile_completions = Bool( + default_value=False, + help="If True, emit profiling data for completion subsystem using cProfile." ).tag(config=True) - @observe('limit_to__all__') - def _limit_to_all_changed(self, change): - warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration ' - 'value has been deprecated since IPython 5.0, will be made to have ' - 'no effects and then removed in future version of IPython.', - UserWarning) + profiler_output_dir = Unicode( + default_value=".completion_profiles", + help="Template for path at which to output profile data for completions." + ).tag(config=True) - def __init__(self, shell=None, namespace=None, global_namespace=None, - use_readline=_deprecation_readline_sentinel, config=None, **kwargs): + def __init__( + self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs + ): """IPCompleter() -> completer Return a completer object. Parameters ---------- - shell a pointer to the ipython shell itself. This is needed because this completer knows about magic functions, and those can only be accessed via the ipython instance. - namespace : dict, optional an optional dict where completions are performed. - global_namespace : dict, optional secondary optional dict for completions, to handle cases (such as IPython embedded inside functions) where both Python scopes are visible. - - use_readline : bool, optional - DEPRECATED, ignored since IPython 6.0, will have no effects + config : Config + traitlet's config object + **kwargs + passed to super class unmodified. """ self.magic_escape = ESC_MAGIC self.splitter = CompletionSplitter() - if use_readline is not _deprecation_readline_sentinel: - warnings.warn('The `use_readline` parameter is deprecated and ignored since IPython 6.0.', - DeprecationWarning, stacklevel=2) - # _greedy_changed() depends on splitter and readline being defined: - Completer.__init__(self, namespace=namespace, global_namespace=global_namespace, - config=config, **kwargs) + super().__init__( + namespace=namespace, + global_namespace=global_namespace, + config=config, + **kwargs, + ) # List where completion matches will be stored self.matches = [] @@ -1106,48 +2141,84 @@ def __init__(self, shell=None, namespace=None, global_namespace=None, #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') self.magic_arg_matchers = [ - self.magic_config_matches, - self.magic_color_matches, + self.magic_config_matcher, + self.magic_color_matcher, ] # This is set externally by InteractiveShell self.custom_completers = None + # This is a list of names of unicode characters that can be completed + # into their corresponding unicode value. The list is large, so we + # lazily initialize it on first use. Consuming code should access this + # attribute through the `@unicode_names` property. + self._unicode_names = None + + self._backslash_combining_matchers = [ + self.latex_name_matcher, + self.unicode_name_matcher, + back_latex_name_matcher, + back_unicode_name_matcher, + self.fwd_unicode_matcher, + ] + + if not self.backslash_combining_completions: + for matcher in self._backslash_combining_matchers: + self.disable_matchers.append(_get_matcher_id(matcher)) + + if not self.merge_completions: + self.suppress_competing_matchers = True + @property - def matchers(self): + def matchers(self) -> list[Matcher]: """All active matcher routines for completion""" if self.dict_keys_only: - return [self.dict_key_matches] + return [self.dict_key_matcher] if self.use_jedi: return [ - self.file_matches, - self.magic_matches, - self.dict_key_matches, + *self.custom_matchers, + *self._backslash_combining_matchers, + *self.magic_arg_matchers, + self.custom_completer_matcher, + self.magic_matcher, + self._jedi_matcher, + self.dict_key_matcher, + self.file_matcher, ] else: return [ - self.python_matches, - self.file_matches, - self.magic_matches, - self.python_func_kw_matches, - self.dict_key_matches, + *self.custom_matchers, + *self._backslash_combining_matchers, + *self.magic_arg_matchers, + self.custom_completer_matcher, + self.dict_key_matcher, + self.magic_matcher, + self.python_matcher, + self.file_matcher, + self.python_func_kw_matcher, ] - def all_completions(self, text): + def all_completions(self, text: str) -> list[str]: """ - Wrapper around the complete method for the benefit of emacs. + Wrapper around the completion methods for the benefit of emacs. """ + prefix = text.rpartition('.')[0] + with provisionalcompleter(): + return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text + for c in self.completions(text, len(text))] + return self.complete(text)[1] - def _clean_glob(self, text): + def _clean_glob(self, text:str): return self.glob("%s*" % text) - def _clean_glob_win32(self,text): + def _clean_glob_win32(self, text:str): return [f.replace("\\","/") for f in self.glob("%s*" % text)] - def file_matches(self, text): + @context_matcher() + def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult: """Match filenames, expanding ~USER type strings. Most of the seemingly convoluted logic in this completer is an @@ -1159,7 +2230,36 @@ def file_matches(self, text): only the parts after what's already been typed (instead of the full completions, as is normally done). I don't think with the current (as of Python 2.3) Python readline it's possible to do - better.""" + better. + """ + # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter, + # starts with `/home/`, `C:\`, etc) + + text = context.token + raw_text_until_cursor = context.text_until_cursor + code_until_cursor = self._extract_code(raw_text_until_cursor) + in_cli_context = self._is_completing_in_cli_context( + raw_text_until_cursor + ) or self._is_completing_in_cli_context(code_until_cursor) + if ( + not in_cli_context + and not self._is_completing_in_string(code_until_cursor) + and not self._looks_like_path(text) + ): + return { + "completions": [], + "suppress": False, + } + + completion_type = self._determine_completion_context(code_until_cursor) + if ( + completion_type == self._CompletionContextType.ATTRIBUTE + and not in_cli_context + ): + return { + "completions": [], + "suppress": False, + } # chars that require escaping with backslash - i.e. chars # that readline treats incorrectly as delimiters, but we @@ -1167,9 +2267,9 @@ def file_matches(self, text): # when escaped with backslash if text.startswith('!'): text = text[1:] - text_prefix = u'!' + text_prefix = '!' else: - text_prefix = u'' + text_prefix = '' text_until_cursor = self.text_until_cursor # track strings with open quotes @@ -1186,7 +2286,10 @@ def file_matches(self, text): if open_quotes: lsplit = text_until_cursor.split(open_quotes)[-1] else: - return [] + return { + "completions": [], + "suppress": False, + } except IndexError: # tab pressed on empty line lsplit = "" @@ -1200,7 +2303,15 @@ def file_matches(self, text): text = os.path.expanduser(text) if text == "": - return [text_prefix + protect_filename(f) for f in self.glob("*")] + return { + "completions": [ + SimpleCompletion( + text=text_prefix + protect_filename(f), type="path" + ) + for f in self.glob("*") + ], + "suppress": False, + } # Compute the matches from the filesystem if sys.platform == 'win32': @@ -1227,17 +2338,77 @@ def file_matches(self, text): protect_filename(f) for f in m0] # Mark directories in input list by appending '/' to their names. - return [x+'/' if os.path.isdir(x) else x for x in matches] + return { + "completions": [ + SimpleCompletion(text=x + "/" if os.path.isdir(x) else x, type="path") + for x in matches + ], + "suppress": False, + } + + def _extract_code(self, line: str) -> str: + """Extract code from magics if any.""" + + if not line: + return line + maybe_magic, *rest = line.split(maxsplit=1) + if not rest: + return line + args = rest[0] + known_magics = self.shell.magics_manager.lsmagic() + line_magics = known_magics["line"] + magic_name = maybe_magic.lstrip(self.magic_escape) + if magic_name not in line_magics: + return line + + if not maybe_magic.startswith(self.magic_escape): + all_variables = [*self.namespace.keys(), *self.global_namespace.keys()] + if magic_name in all_variables: + # short circuit if we see a line starting with say `time` + # but time is defined as a variable (in addition to being + # a magic). In these cases users need to use explicit `%time`. + return line + + magic_method = line_magics[magic_name] + + try: + if magic_name == "timeit": + opts, stmt = magic_method.__self__.parse_options( + args, + "n:r:tcp:qov:", + posix=False, + strict=False, + preserve_non_opts=True, + ) + return stmt + elif magic_name == "prun": + opts, stmt = magic_method.__self__.parse_options( + args, "D:l:rs:T:q", list_all=True, posix=False + ) + return stmt + elif hasattr(magic_method, "parser") and getattr( + magic_method, "has_arguments", False + ): + # e.g. %debug, %time + args, extra = magic_method.parser.parse_argstring(args, partial=True) + return " ".join(extra) + except UsageError: + return line + + return line + + @context_matcher() + def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match magics.""" - def magic_matches(self, text): - """Match magics""" # Get all shell magics now rather than statically, so magics loaded at # runtime show up too. + text = context.token lsm = self.shell.magics_manager.lsmagic() line_magics = lsm['line'] cell_magics = lsm['cell'] pre = self.magic_escape - pre2 = pre+pre + pre2 = pre + pre explicit_magic = text.startswith(pre) @@ -1265,21 +2436,41 @@ def matches(magic): def matches(magic): return magic.startswith(bare_text) - comp = [ pre2+m for m in cell_magics if matches(m)] + completions = [pre2 + m for m in cell_magics if matches(m)] if not text.startswith(pre2): - comp += [ pre+m for m in line_magics if matches(m)] + completions += [pre + m for m in line_magics if matches(m)] + + is_magic_prefix = len(text) > 0 and text[0] == "%" - return comp + return { + "completions": [ + SimpleCompletion(text=comp, type="magic") for comp in completions + ], + "suppress": is_magic_prefix and len(completions) > 0, + } - def magic_config_matches(self, text:str) -> List[str]: - """ Match class names and attributes for %config magic """ + @context_matcher() + def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match class names and attributes for %config magic.""" + # NOTE: uses `line_buffer` equivalent for compatibility + matches = self.magic_config_matches(context.line_with_cursor) + return _convert_matcher_v1_result_to_v2_no_no(matches, type="param") + + def magic_config_matches(self, text: str) -> list[str]: + """Match class names and attributes for %config magic. + + .. deprecated:: 8.6 + You can use :meth:`magic_config_matcher` instead. + """ texts = text.strip().split() if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): + # Only instantiated magics are configurable; load the lazy ones. + self.shell.magics_manager.load_all_lazy_magics() # get all configuration classes - classes = sorted(set([ c for c in self.shell.configurables + classes = sorted({ c for c in self.shell.configurables if c.__class__.class_traits(config=True) - ]), key=lambda x: x.__class__.__name__) + }, key=lambda x: x.__class__.__name__) classnames = [ c.__class__.__name__ for c in classes ] # return all classnames if config or %config is given @@ -1300,14 +2491,16 @@ def magic_config_matches(self, text:str) -> List[str]: cls = classes[classnames.index(classname)].__class__ help = cls.class_get_help() # strip leading '--' from cl-args: - help = re.sub(re.compile(r'^--', re.MULTILINE), '', help) + help = _LEADING_DASHES_RE.sub("", help) return [ attr.split('=')[0] for attr in help.strip().splitlines() if attr.startswith(texts[1]) ] return [] - def magic_color_matches(self, text:str) -> List[str] : - """ Match color schemes for %colors magic""" + @context_matcher() + def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match color schemes for %colors magic.""" + text = context.line_with_cursor texts = text.split() if text.endswith(' '): # .split() strips off the trailing whitespace. Add '' back @@ -1316,14 +2509,38 @@ def magic_color_matches(self, text:str) -> List[str] : if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'): prefix = texts[1] - return [ color for color in InspectColors.keys() - if color.startswith(prefix) ] - return [] - - def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str): + return SimpleMatcherResult( + completions=[ + SimpleCompletion(color, type="param") + for color in theme_table.keys() + if color.startswith(prefix) + ], + suppress=False, + ) + return SimpleMatcherResult( + completions=[], + suppress=False, + ) + + @context_matcher(identifier="IPCompleter.jedi_matcher") + def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult: + matches = self._jedi_matches( + cursor_column=context.cursor_position, + cursor_line=context.cursor_line, + text=context.full_text, + ) + return { + "completions": matches, + # static analysis should not suppress other matcher + # NOTE: file_matcher is automatically suppressed on attribute completions + "suppress": False, + } + + def _jedi_matches( + self, cursor_column: int, cursor_line: int, text: str + ) -> Iterator[_JediCompletionLike]: """ - - Return a list of :any:`jedi.api.Completions` object from a ``text`` and + Return a list of :any:`jedi.api.Completion`\\s object from a ``text`` and cursor position. Parameters @@ -1335,11 +2552,13 @@ def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str): text : str text to complete - Debugging - --------- - + Notes + ----- If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` object containing a string with the Jedi debug information attached. + + .. deprecated:: 8.6 + You can use :meth:`_jedi_matcher` instead. """ namespaces = [self.namespace] if self.global_namespace is not None: @@ -1358,25 +2577,24 @@ def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str): elif self.omit__names == 0: completion_filter = lambda x:x else: - raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names)) + raise ValueError(f"Don't understand self.omit__names == {self.omit__names}") - interpreter = jedi.Interpreter( - text[:offset], namespaces, column=cursor_column, line=cursor_line + 1) + interpreter = _get_jedi().Interpreter(text[:offset], namespaces) try_jedi = True try: - # should we check the type of the node is Error ? + # find the first token in the current tree -- if it is a ' or " then we are in a string + completing_string = False try: - # jedi < 0.11 - from jedi.parser.tree import ErrorLeaf - except ImportError: - # jedi >= 0.11 - from parso.tree import ErrorLeaf + first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value')) + except StopIteration: + pass + else: + # note the value may be ', ", or it may also be ''' or """, or + # in some cases, """what/you/typed..., but all of these are + # strings. + completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'} - next_to_last_tree = interpreter._get_module().tree_node.children[-2] - completing_string = False - if isinstance(next_to_last_tree, ErrorLeaf): - completing_string = next_to_last_tree.value.lstrip()[0] in {'"', "'"} # if we are in a string jedi is likely not the right candidate for # now. Skip it. try_jedi = not completing_string @@ -1386,36 +2604,385 @@ def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str): print("Error detecting if completing a non-finished string :", e, '|') if not try_jedi: - return [] + return iter([]) try: - return filter(completion_filter, interpreter.completions()) + return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1)) except Exception as e: if self.debug: - return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))] + return iter( + [ + _FakeJediCompletion( + 'Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' + % (e) + ) + ] + ) else: - return [] + return iter([]) + + class _CompletionContextType(enum.Enum): + ATTRIBUTE = "attribute" # For attribute completion + GLOBAL = "global" # For global completion + + def _determine_completion_context(self, line): + """ + Determine whether the cursor is in an attribute or global completion context. + """ + # Cursor in string/comment → GLOBAL. + is_string, is_in_expression = self._is_in_string_or_comment(line) + if is_string and not is_in_expression: + return self._CompletionContextType.GLOBAL + + # If we're in a template string expression, handle specially. + # Note that ``is_string`` may be False here for nested template strings + # (``f'{f'a.``) as the naive quote tracking sees the inner opening quote + # as closing the outer one; the recursion below sorts this out. + if is_in_expression: + # Extract the expression part - look for the last { that isn't closed + expr_start = line.rfind("{") + if expr_start >= 0: + # We're looking at the expression inside a template string + expr = line[expr_start + 1 :] + # Recursively determine the context of the expression + return self._determine_completion_context(expr) + + # Handle plain number literals - should be global context + # Ex: 3. -42.14 but not 3.1. + if re.search(r"(? str | None: + """Extract the expression a trailing ``.attr`` would be looked up on. + + Scan *line* backwards from the last dot (which must be followed by at + most an identifier and nothing else) and return the primary expression + preceding it, or ``None`` if there is no trailing attribute access or + no plausible expression in front of it. + """ + match = re.search(r"\.(?:[a-zA-Z_]\w*)?$", line) + if match is None: + return None + dot = match.start() + i = dot - 1 + while i >= 0: + char = line[i] + if char in ")]": + # A call or subscript trailer, or a parenthesized/list atom; + # whatever precedes it may still be part of the expression. + i = self._skip_backwards_over_brackets(line, i) + if i is None: + return None + continue + if char == "}": + # A dict/set display; nothing may precede it. + i = self._skip_backwards_over_brackets(line, i) + if i is None: + return None + break + if char in "\"'": + # A string literal; nothing but its prefix may precede it. + i = self._skip_backwards_over_string(line, i) + if i is None: + return None + break + if char.isalnum() or char == "_": + while i >= 0 and (line[i].isalnum() or line[i] == "_"): + i -= 1 + # a name or number may itself be an attribute of something else + if i >= 0 and line[i] == ".": + i -= 1 + continue + break + # anything else (operator, bracket, whitespace, ...) ends the + # expression + break + return line[i + 1 : dot] or None + + def _skip_backwards_over_brackets(self, line: str, i: int) -> int | None: + """Skip a bracketed group ending at index *i* (a closing bracket). + + Return the index just before the matching opening bracket, or ``None`` + if the brackets are unbalanced. + """ + depth = 0 + while i >= 0: + char = line[i] + if char in "\"'": + skipped = self._skip_backwards_over_string(line, i) + if skipped is None: + return None + i = skipped + continue + if char in ")]}": + depth += 1 + elif char in "([{": + depth -= 1 + if depth == 0: + return i - 1 + if depth < 0: + return None + i -= 1 + return None + + def _skip_backwards_over_string(self, line: str, i: int) -> int | None: + """Skip a string literal ending at index *i* (its closing quote). + + Return the index just before the opening quote (and any string prefix), + or ``None`` if no opening quote was found. + """ + quote = line[i] + j = i - 1 + while j >= 0: + if line[j] == quote: + backslashes = 0 + k = j - 1 + while k >= 0 and line[k] == "\\": + backslashes += 1 + k -= 1 + if backslashes % 2 == 0: + break + j -= 1 + else: + return None + j -= 1 + # skip any string prefix (r, b, u, f, t and combinations thereof) + while j >= 0 and line[j] in "bBrRuUfFtT": + j -= 1 + return j + + def _is_completing_in_cli_context(self, text: str) -> bool: + """ + Determine if we are completing in a CLI alias, line magic, or bang expression context. + """ + stripped = text.lstrip() + if stripped.startswith("!") or stripped.startswith("%"): + return True + if self._is_completing_in_system_assignment(text): + return True + # Check for CLI aliases + try: + tokens = stripped.split(None, 1) + if not tokens: + return False + first_token = tokens[0] + + # Must have arguments after the command for this to apply + if len(tokens) < 2: + return False + + # Check if first token is a known alias + if not any( + alias[0] == first_token for alias in self.shell.alias_manager.aliases + ): + return False + + try: + if first_token in self.shell.user_ns: + # There's a variable defined, so the alias is overshadowed + return False + except (AttributeError, KeyError): + pass + + return True + except Exception: + return False + + def _is_completing_in_system_assignment(self, text: str) -> bool: + """Return True for IPython ``name = !command`` syntax.""" + try: + transform = SystemAssign.find(make_tokens_by_line([text + "\n"])) + except Exception: + return False + return transform is not None and transform.start_col < len(text) + + def _is_completing_in_string(self, text: str) -> bool: + """Return True if the cursor is in a string literal, not a comment.""" + is_string, is_in_expression = self._is_in_string_or_comment(text) + if not is_string or is_in_expression: + return False + return not any(token.type == tokenize.COMMENT for token in _parse_tokens(text)) + + def _looks_like_path(self, text: str) -> bool: + if text.startswith(("~", "/", "./", "../", ".\\", "..\\")): + return True + return bool(sys.platform == "win32" and re.match(r"^[a-zA-Z]:[\\/]", text)) + + def _is_in_string_or_comment(self, text): + """ + Determine if the cursor is inside a string or comment. + Returns (is_string, is_in_expression) tuple: + - is_string: True if in any kind of string + - is_in_expression: True if inside an f-string/t-string expression + """ + in_single_quote = False + in_double_quote = False + in_triple_single = False + in_triple_double = False + in_template_string = False # Covers both f-strings and t-strings + in_expression = False # For expressions in f/t-strings + expression_depth = 0 # Track nested braces in expressions + i = 0 + + while i < len(text): + # Check for f-string or t-string start + if ( + i + 1 < len(text) + and text[i] in ("f", "t") + and (text[i + 1] == '"' or text[i + 1] == "'") + and not ( + in_single_quote + or in_double_quote + or in_triple_single + or in_triple_double + ) + ): + in_template_string = True + i += 1 # Skip the 'f' or 't' + + # Handle triple quotes + if i + 2 < len(text): + if ( + text[i : i + 3] == '"""' + and not in_single_quote + and not in_triple_single + ): + in_triple_double = not in_triple_double + if not in_triple_double: + in_template_string = False + i += 3 + continue + if ( + text[i : i + 3] == "'''" + and not in_double_quote + and not in_triple_double + ): + in_triple_single = not in_triple_single + if not in_triple_single: + in_template_string = False + i += 3 + continue + + # Handle escapes + if text[i] == "\\" and i + 1 < len(text): + i += 2 + continue + + # Handle nested braces within f-strings + if in_template_string: + # Special handling for consecutive opening braces + if i + 1 < len(text) and text[i : i + 2] == "{{": + i += 2 + continue + + # Detect start of an expression + if text[i] == "{": + # Only increment depth and mark as expression if not already in an expression + # or if we're at a top-level nested brace + if not in_expression or (in_expression and expression_depth == 0): + in_expression = True + expression_depth += 1 + i += 1 + continue + + # Detect end of an expression + if text[i] == "}": + expression_depth -= 1 + if expression_depth <= 0: + in_expression = False + expression_depth = 0 + i += 1 + continue - def python_matches(self, text): + in_triple_quote = in_triple_single or in_triple_double + + # Handle quotes - also reset template string when closing quotes are encountered + if text[i] == '"' and not in_single_quote and not in_triple_quote: + in_double_quote = not in_double_quote + if not in_double_quote and not in_triple_quote: + in_template_string = False + elif text[i] == "'" and not in_double_quote and not in_triple_quote: + in_single_quote = not in_single_quote + if not in_single_quote and not in_triple_quote: + in_template_string = False + + # Check for comment + if text[i] == "#" and not ( + in_single_quote or in_double_quote or in_triple_quote + ): + return True, False + + i += 1 + + is_string = ( + in_single_quote or in_double_quote or in_triple_single or in_triple_double + ) + + # Return tuple (is_string, is_in_expression) + return ( + is_string or (in_template_string and not in_expression), + in_expression and expression_depth > 0, + ) + + @context_matcher() + def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult: """Match attributes or global python names""" - if "." in text: + text = context.text_until_cursor + text = self._extract_code(text) + in_cli_context = self._is_completing_in_cli_context(text) + if in_cli_context: + completion_type = self._CompletionContextType.GLOBAL + else: + completion_type = self._determine_completion_context(text) + if completion_type == self._CompletionContextType.ATTRIBUTE: try: - matches = self.attr_matches(text) - if text.endswith('.') and self.omit__names: + matches, fragment = self._attr_matches( + text, include_prefix=False, context=context + ) + if text.endswith(".") and self.omit__names: if self.omit__names == 1: # true if txt is _not_ a __ name, false otherwise: - no__name = (lambda txt: - re.match(r'.*\.__.*?__',txt) is None) + no__name = lambda txt: re.match(r".*\.__.*?__", txt) is None else: # true if txt is _not_ a _ name, false otherwise: - no__name = (lambda txt: - re.match(r'\._.*?',txt[txt.rindex('.'):]) is None) + no__name = ( + lambda txt: re.match(r"\._.*?", txt[txt.rindex(".") :]) + is None + ) matches = filter(no__name, matches) + matches = _convert_matcher_v1_result_to_v2( + matches, type="attribute", fragment=fragment + ) + return matches except NameError: # catches . - matches = [] + return SimpleMatcherResult(completions=[], suppress=False) else: - matches = self.global_matches(text) - return matches + try: + matches = self.global_matches(context.token, context=context) + except TypeError: + matches = self.global_matches(context.token) + # TODO: maybe distinguish between functions, modules and just "variables" + return SimpleMatcherResult( + completions=[ + SimpleCompletion(text=match, type="variable") for match in matches + ], + suppress=False, + ) def _default_arguments_from_docstring(self, doc): """Parse the first line of docstring for call signature. @@ -1469,7 +3036,7 @@ def _default_arguments(self, obj): inspect.Parameter.POSITIONAL_OR_KEYWORD) try: - sig = inspect.signature(call_obj) + sig = inspect.signature(obj) ret.extend(k for k, v in sig.parameters.items() if v.kind in _keeps) except ValueError: @@ -1477,8 +3044,18 @@ def _default_arguments(self, obj): return list(set(ret)) - def python_func_kw_matches(self,text): - """Match named parameters (kwargs) of the last open function""" + @context_matcher() + def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match named parameters (kwargs) of the last open function.""" + matches = self.python_func_kw_matches(context.token) + return _convert_matcher_v1_result_to_v2_no_no(matches, type="param") + + def python_func_kw_matches(self, text: str) -> list[str]: + """Match named parameters (kwargs) of the last open function. + + .. deprecated:: 8.6 + You can use :meth:`python_func_kw_matcher` instead. + """ if "." in text: # a parameter cannot be dotted return [] @@ -1494,7 +3071,8 @@ def python_func_kw_matches(self,text): # parenthesis before the cursor # e.g. for "foo (1+bar(x), pa,a=1)", the candidate is "foo" tokens = regexp.findall(self.text_until_cursor) - iterTokens = reversed(tokens); openPar = 0 + iterTokens = reversed(tokens) + openPar = 0 for token in iterTokens: if token == ')': @@ -1508,13 +3086,14 @@ def python_func_kw_matches(self,text): return [] # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" ) ids = [] - isId = re.compile(r'\w+$').match + isId = _IDENTIFIER_END_RE.match while True: try: ids.append(next(iterTokens)) if not isId(ids[-1]): - ids.pop(); break + ids.pop() + break if not next(iterTokens) == '.': break except StopIteration: @@ -1524,7 +3103,7 @@ def python_func_kw_matches(self,text): # them again usedNamedArgs = set() par_level = -1 - for token, next_token in zip(tokens, tokens[1:]): + for token, next_token in itertools.pairwise(tokens): if token == '(': par_level += 1 elif token == ')': @@ -1538,103 +3117,132 @@ def python_func_kw_matches(self,text): usedNamedArgs.add(token) - # lookup the candidate callable matches either using global_matches - # or attr_matches for dotted names - if len(ids) == 1: - callableMatches = self.global_matches(ids[0]) - else: - callableMatches = self.attr_matches('.'.join(ids[::-1])) + from IPython.core.guarded_eval import EvaluationContext, guarded_eval + argMatches = [] - for callableMatch in callableMatches: - try: - namedArgs = self._default_arguments(eval(callableMatch, - self.namespace)) - except: - continue + try: + callableObj = '.'.join(ids[::-1]) + namedArgs = self._default_arguments( + guarded_eval( + callableObj, + EvaluationContext( + globals=self.global_namespace, + locals=self.namespace, + evaluation=self.evaluation, + auto_import=self._auto_import, + policy_overrides=self.policy_overrides, + ), + ) + ) # Remove used named arguments from the list, no need to show twice for namedArg in set(namedArgs) - usedNamedArgs: if namedArg.startswith(text): - argMatches.append(u"%s=" %namedArg) + argMatches.append("%s=" %namedArg) + except Exception: + pass + return argMatches - def dict_key_matches(self, text): - "Match string keys in a dictionary, after e.g. 'foo[' " - def get_keys(obj): - # Objects can define their own completions by defining an - # _ipy_key_completions_() method. - method = get_real_method(obj, '_ipython_key_completions_') - if method is not None: - return method() - - # Special case some common in-memory dict-like types - if isinstance(obj, dict) or\ - _safe_isinstance(obj, 'pandas', 'DataFrame'): - try: - return list(obj.keys()) - except Exception: - return [] - elif _safe_isinstance(obj, 'numpy', 'ndarray') or\ - _safe_isinstance(obj, 'numpy', 'void'): - return obj.dtype.names or [] + @staticmethod + def _get_keys(obj: Any) -> list[Any]: + # Objects can define their own completions by defining an + # _ipy_key_completions_() method. + method = get_real_method(obj, '_ipython_key_completions_') + if method is not None: + return method() + + # Special case some common in-memory dict-like types + if isinstance(obj, dict) or _safe_isinstance(obj, "pandas", "DataFrame"): + try: + return list(obj.keys()) + except Exception: + return [] + elif _safe_isinstance(obj, "pandas", "core", "indexing", "_LocIndexer"): + try: + return list(obj.obj.keys()) + except Exception: + return [] + elif _safe_isinstance(obj, 'numpy', 'ndarray') or\ + _safe_isinstance(obj, 'numpy', 'void'): + return obj.dtype.names or [] + return [] + + @context_matcher() + def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match string keys in a dictionary, after e.g. ``foo[``.""" + matches = self.dict_key_matches(context.token) + return _convert_matcher_v1_result_to_v2( + matches, type="dict key", suppress_if_matches=True + ) + + def dict_key_matches(self, text: str) -> list[str]: + """Match string keys in a dictionary, after e.g. ``foo[``. + + .. deprecated:: 8.6 + You can use :meth:`dict_key_matcher` instead. + """ + from IPython.core.guarded_eval import EvaluationContext, guarded_eval + + # Short-circuit on closed dictionary (regular expression would + # not match anyway, but would take quite a while). + if self.text_until_cursor.strip().endswith("]"): return [] - try: - regexps = self.__dict_key_regexps - except AttributeError: - dict_key_re_fmt = r'''(?x) - ( # match dict-referring expression wrt greedy setting - %s - ) - \[ # open bracket - \s* # and optional whitespace - ([uUbB]? # string prefix (r not handled) - (?: # unclosed string - '(?:[^']|(? text_start and closing_quote: - # quotes were opened inside text, maybe close them - if continuation.startswith(closing_quote): - continuation = continuation[len(closing_quote):] - else: - suf += closing_quote - if bracket_idx > text_start: - # brackets were opened inside text, maybe close them - if not continuation.startswith(']'): - suf += ']' + # the text given to this method, e.g. `d["""a\nt + can_close_quote = False + can_close_bracket = False + + continuation = self.line_buffer[len(self.text_until_cursor) :].strip() + + if continuation.startswith(closing_quote): + # do not close if already closed, e.g. `d['a'` + continuation = continuation[len(closing_quote) :] + else: + can_close_quote = True + + continuation = continuation.strip() + + # e.g. `pandas.DataFrame` has different tuple indexer behaviour, + # handling it is out of scope, so let's avoid appending suffixes. + has_known_tuple_handling = isinstance(obj, dict) + + can_close_bracket = ( + not continuation.startswith("]") and self.auto_close_dict_keys + ) + can_close_tuple_item = ( + not continuation.startswith(",") + and has_known_tuple_handling + and self.auto_close_dict_keys + ) + can_close_quote = can_close_quote and self.auto_close_dict_keys + + # fast path if closing quote should be appended but not suffix is allowed + if not can_close_quote and not can_close_bracket and closing_quote: + return [leading + k for k in matches] + + results = [] + + end_of_tuple_or_item = _DictKeyState.END_OF_TUPLE | _DictKeyState.END_OF_ITEM + + for k, state_flag in matches.items(): + result = leading + k + if can_close_quote and closing_quote: + result += closing_quote - return [leading + k + suf for k in matches] + if state_flag == end_of_tuple_or_item: + # We do not know which suffix to add, + # e.g. both tuple item and string + # match this item. + pass + + if state_flag in end_of_tuple_or_item and can_close_bracket: + result += "]" + if state_flag == _DictKeyState.IN_TUPLE and can_close_tuple_item: + result += ", " + results.append(result) + return results - def unicode_name_matches(self, text): - u"""Match Latex-like syntax for unicode characters base + @context_matcher() + def unicode_name_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match Latex-like syntax for unicode characters base on the name of the character. This does ``\\GREEK SMALL LETTER ETA`` -> ``η`` Works only on valid python 3 identifier, or on combining characters that will combine to form a valid identifier. - - Used on Python 3 only. """ + import unicodedata + + + text = context.text_until_cursor + slashpos = text.rfind('\\') if slashpos > -1: s = text[slashpos+1:] @@ -1684,19 +3331,39 @@ def unicode_name_matches(self, text): unic = unicodedata.lookup(s) # allow combining chars if ('a'+unic).isidentifier(): - return '\\'+s,[unic] + return { + "completions": [SimpleCompletion(text=unic, type="unicode")], + "suppress": True, + "matched_fragment": "\\" + s, + } except KeyError: pass - return u'', [] + return { + "completions": [], + "suppress": False, + } + @context_matcher() + def latex_name_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match Latex syntax for unicode characters. + + This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` + """ + fragment, matches = self.latex_matches(context.text_until_cursor) + return _convert_matcher_v1_result_to_v2( + matches, type="latex", fragment=fragment, suppress_if_matches=True + ) - def latex_matches(self, text): - u"""Match Latex syntax for unicode characters. + def latex_matches(self, text: str) -> tuple[str, Sequence[str]]: + """Match Latex syntax for unicode characters. This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` - Used on Python 3 only. + .. deprecated:: 8.6 + You can use :meth:`latex_name_matcher` instead. """ + from IPython.core.latex_symbols import latex_symbols + slashpos = text.rfind('\\') if slashpos > -1: s = text[slashpos:] @@ -1708,10 +3375,29 @@ def latex_matches(self, text): # If a user has partially typed a latex symbol, give them # a full list of options \al -> [\aleph, \alpha] matches = [k for k in latex_symbols if k.startswith(s)] - return s, matches - return u'', [] + if matches: + return s, matches + return '', () - def dispatch_custom_completer(self, text): + @context_matcher() + def custom_completer_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Dispatch custom completer. + + If a match is found, suppresses all other matchers except for Jedi. + """ + matches = self.dispatch_custom_completer(context.token) or [] + result = _convert_matcher_v1_result_to_v2( + matches, type=_UNKNOWN_TYPE, suppress_if_matches=True + ) + result["ordered"] = True + result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)} + return result + + def dispatch_custom_completer(self, text: str) -> list[str] | None: + """ + .. deprecated:: 8.6 + You can use :meth:`custom_completer_matcher` instead. + """ if not self.custom_completers: return @@ -1763,25 +3449,27 @@ def completions(self, text: str, offset: int)->Iterator[Completion]: """ Returns an iterator over the possible completions - .. warning:: Unstable + .. warning:: + + Unstable This function is unstable, API may change without warning. It will also raise unless use in proper context manager. Parameters ---------- - - text:str + text : str Full text of the current input, multi line string. - offset:int + offset : int Integer representing the position of the cursor in ``text``. Offset is 0-based indexed. Yields ------ - :any:`Completion` object - + Completion + Notes + ----- The cursor on a text can either be seen as being "in between" characters or "On" a character depending on the interface visible to the user. For consistency the cursor being on "in between" characters X @@ -1791,10 +3479,9 @@ def completions(self, text: str, offset: int)->Iterator[Completion]: Combining characters may span more that one position in the text. - .. note:: - If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--`` + If ``IPCompleter.debug`` is :py:data:`True` will yield a ``--jedi/ipython--`` fake Completion token to distinguish completion returned by Jedi and usual IPython completion. @@ -1804,13 +3491,23 @@ def completions(self, text: str, offset: int)->Iterator[Completion]: completions are coming from different sources this function does not ensure that each completion object will only be present once. """ + import uuid + warnings.warn("_complete is a provisional API (as of IPython 6.0). " "It may change without warnings. " "Use in corresponding context manager.", category=ProvisionalCompleterWarning, stacklevel=2) seen = set() + profiler:cProfile.Profile | None try: + if self.profile_completions: + import cProfile + profiler = cProfile.Profile() + profiler.enable() + else: + profiler = None + for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): if c and (c in seen): continue @@ -1820,13 +3517,19 @@ def completions(self, text: str, offset: int)->Iterator[Completion]: """if completions take too long and users send keyboard interrupt, do not crash and return ASAP. """ pass - - def _completions(self, full_text: str, offset: int, *, _timeout)->Iterator[Completion]: + finally: + if profiler is not None: + profiler.disable() + ensure_dir_exists(self.profiler_output_dir) + output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4())) + print("Writing profiler output to", output_path) + profiler.dump_stats(output_path) + + def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]: """ Core completion module.Same signature as :any:`completions`, with the extra `timeout` parameter (in seconds). - Computing jedi's completion ``.type`` can be quite expensive (it is a lazy property) and can require some warm-up, more warm up than just computing the ``name`` of a completion. The warm-up can be : @@ -1850,12 +3553,31 @@ def _completions(self, full_text: str, offset: int, *, _timeout)->Iterator[Compl """ deadline = time.monotonic() + _timeout - before = full_text[:offset] cursor_line, cursor_column = position_to_cursor(full_text, offset) - matched_text, matches, matches_origin, jedi_matches = self._complete( - full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column) + jedi_matcher_id = _get_matcher_id(self._jedi_matcher) + + def is_non_jedi_result( + result: MatcherResult, identifier: str + ) -> TypeGuard[SimpleMatcherResult]: + return identifier != jedi_matcher_id + + results = self._complete( + full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column + ) + + non_jedi_results: dict[str, SimpleMatcherResult] = { + identifier: result + for identifier, result in results.items() + if is_non_jedi_result(result, identifier) + } + + jedi_matches = ( + cast(_JediMatcherResult, results[jedi_matcher_id])["completions"] + if jedi_matcher_id in results + else () + ) iter_jm = iter(jedi_matches) if _timeout: @@ -1883,30 +3605,61 @@ def _completions(self, full_text: str, offset: int, *, _timeout)->Iterator[Compl for jm in iter_jm: delta = len(jm.name_with_symbols) - len(jm.complete) - yield Completion(start=offset - delta, - end=offset, - text=jm.name_with_symbols, - type='', # don't compute type for speed - _origin='jedi', - signature='') - - - start_offset = before.rfind(matched_text) + yield Completion( + start=offset - delta, + end=offset, + text=jm.name_with_symbols, + type=_UNKNOWN_TYPE, # don't compute type for speed + _origin="jedi", + signature="", + ) # TODO: # Suppress this, right now just for debug. - if jedi_matches and matches and self.debug: - yield Completion(start=start_offset, end=offset, text='--jedi/ipython--', - _origin='debug', type='none', signature='') + if jedi_matches and non_jedi_results and self.debug: + some_start_offset = before.rfind( + next(iter(non_jedi_results.values()))["matched_fragment"] + ) + yield Completion( + start=some_start_offset, + end=offset, + text="--jedi/ipython--", + _origin="debug", + type="none", + signature="", + ) - # I'm unsure if this is always true, so let's assert and see if it - # crash - assert before.endswith(matched_text) - for m, t in zip(matches, matches_origin): - yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='') + ordered: list[Completion] = [] + sortable: list[Completion] = [] + + for origin, result in non_jedi_results.items(): + matched_text = result["matched_fragment"] + start_offset = before.rfind(matched_text) + is_ordered = result.get("ordered", False) + container = ordered if is_ordered else sortable + + # I'm unsure if this is always true, so let's assert and see if it + # crash + assert before.endswith(matched_text) + + for simple_completion in result["completions"]: + completion = Completion( + start=start_offset, + end=offset, + text=simple_completion.text, + _origin=origin, + signature="", + type=simple_completion.type or _UNKNOWN_TYPE, + ) + container.append(completion) + yield from list(self._deduplicate(ordered + self._sort(sortable)))[ + :MATCHES_LIMIT + ] - def complete(self, text=None, line_buffer=None, cursor_pos=None): + def complete( + self, text=None, line_buffer=None, cursor_pos=None + ) -> tuple[str, Sequence[str]]: """Find completions for the given text and line context. Note that both the text and the line_buffer are optional, but at least @@ -1914,57 +3667,122 @@ def complete(self, text=None, line_buffer=None, cursor_pos=None): Parameters ---------- - text : string, optional + text : string, optional Text to perform the completion on. If not given, the line buffer is split using the instance's CompletionSplitter object. - - line_buffer : string, optional + line_buffer : string, optional If not given, the completer attempts to obtain the current line buffer via readline. This keyword allows clients which are requesting for text completions in non-readline contexts to inform the completer of the entire text. - - cursor_pos : int, optional + cursor_pos : int, optional Index of the cursor in the full line buffer. Should be provided by remote frontends where kernel has no access to frontend state. Returns ------- + Tuple of two items: text : str - Text that was actually used in the completion. - + Text that was actually used in the completion. matches : list - A list of completion matches. - - - .. note:: + A list of completion matches. + Notes + ----- This API is likely to be deprecated and replaced by :any:`IPCompleter.completions` in the future. - """ warnings.warn('`Completer.complete` is pending deprecation since ' 'IPython 6.0 and will be replaced by `Completer.completions`.', PendingDeprecationWarning) # potential todo, FOLD the 3rd throw away argument of _complete # into the first 2 one. - return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2] + # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?) + # TODO: should we deprecate now, or does it stay? + + results = self._complete( + line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0 + ) + + jedi_matcher_id = _get_matcher_id(self._jedi_matcher) + + return self._arrange_and_extract( + results, + # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version? + skip_matchers={jedi_matcher_id}, + # this API does not support different start/end positions (fragments of token). + abort_if_offset_changes=True, + ) + + def _arrange_and_extract( + self, + results: dict[str, MatcherResult], + skip_matchers: set[str], + abort_if_offset_changes: bool, + ): + sortable: list[AnyMatcherCompletion] = [] + ordered: list[AnyMatcherCompletion] = [] + most_recent_fragment = None + for identifier, result in results.items(): + if identifier in skip_matchers: + continue + if not result["completions"]: + continue + if not most_recent_fragment: + most_recent_fragment = result["matched_fragment"] + if ( + abort_if_offset_changes + and result["matched_fragment"] != most_recent_fragment + ): + break + if result.get("ordered", False): + ordered.extend(result["completions"]) + else: + sortable.extend(result["completions"]) + + if not most_recent_fragment: + most_recent_fragment = "" # to satisfy typechecker (and just in case) + + return most_recent_fragment, [ + m.text for m in self._deduplicate(ordered + self._sort(sortable)) + ] def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, - full_text=None) -> Tuple[str, List[str], List[str], Iterable[_FakeJediCompletion]]: + full_text=None) -> _CompleteResult: """ - Like complete but can also returns raw jedi completions as well as the origin of the completion text. This could (and should) be made much cleaner but that will be simpler once we drop the old (and stateful) :any:`complete` API. - With current provisional API, cursor_pos act both (depending on the caller) as the offset in the ``text`` or ``line_buffer``, or as the ``column`` when passing multiline strings this could/should be renamed but would add extra noise. + + Parameters + ---------- + cursor_line + Index of the line the cursor is on. 0 indexed. + cursor_pos + Position of the cursor in the current line/line_buffer/text. 0 + indexed. + line_buffer : optional, str + The current line the cursor is in, this is mostly due to legacy + reason that readline could only give a us the single current line. + Prefer `full_text`. + text : str + The current "token" the cursor is in, mostly also for historical + reasons. as the completer would trigger only after the current line + was parsed. + full_text : str + Full text of the current cell. + + Returns + ------- + An ordered dictionary where keys are identifiers of completion + matchers and values are ``MatcherResult``s. """ # if the cursor position isn't given, the only sane assumption we can @@ -1978,89 +3796,241 @@ def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, # if text is either None or an empty string, rely on the line buffer if (not line_buffer) and full_text: line_buffer = full_text.split('\n')[cursor_line] - if not text: - text = self.splitter.split_line(line_buffer, cursor_pos) - - if self.backslash_combining_completions: - # allow deactivation of these on windows. - base_text = text if not line_buffer else line_buffer[:cursor_pos] - latex_text, latex_matches = self.latex_matches(base_text) - if latex_matches: - return latex_text, latex_matches, ['latex_matches']*len(latex_matches), () - name_text = '' - name_matches = [] - for meth in (self.unicode_name_matches, back_latex_name_matches, back_unicode_name_matches): - name_text, name_matches = meth(base_text) - if name_text: - return name_text, name_matches[:MATCHES_LIMIT], \ - [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), () - + if not text: # issue #11508: check line_buffer before calling split_line + text = ( + self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else "" + ) # If no line buffer is given, assume the input text is all there was if line_buffer is None: line_buffer = text + # deprecated - do not use `line_buffer` in new code. self.line_buffer = line_buffer self.text_until_cursor = self.line_buffer[:cursor_pos] - # Do magic arg matches - for matcher in self.magic_arg_matchers: - matches = list(matcher(line_buffer))[:MATCHES_LIMIT] - if matches: - origins = [matcher.__qualname__] * len(matches) - return text, matches, origins, () + if not full_text: + full_text = line_buffer + + context = CompletionContext( + full_text=full_text, + cursor_position=cursor_pos, + cursor_line=cursor_line, + token=self._extract_code(text), + limit=MATCHES_LIMIT, + ) # Start with a clean slate of completions - matches = [] - custom_res = self.dispatch_custom_completer(text) - # FIXME: we should extend our api to return a dict with completions for - # different types of objects. The rlcomplete() method could then - # simply collapse the dict into a list for readline, but we'd have - # richer completion semantics in other environments. - completions = () - if self.use_jedi: - if not full_text: - full_text = line_buffer - completions = self._jedi_matches( - cursor_pos, cursor_line, full_text) - if custom_res is not None: - # did custom completers produce something? - matches = [(m, 'custom') for m in custom_res] + results: dict[str, MatcherResult] = {} + + jedi_matcher_id = _get_matcher_id(self._jedi_matcher) + + suppressed_matchers: set[str] = set() + + matchers = { + _get_matcher_id(matcher): matcher + for matcher in sorted( + self.matchers, key=_get_matcher_priority, reverse=True + ) + } + + for matcher_id, matcher in matchers.items(): + matcher_id = _get_matcher_id(matcher) + + if matcher_id in self.disable_matchers: + continue + + if matcher_id in results: + warnings.warn(f"Duplicate matcher ID: {matcher_id}.") + + if matcher_id in suppressed_matchers: + continue + + result: MatcherResult + try: + if _is_matcher_v1(matcher): + result = _convert_matcher_v1_result_to_v2_no_no( + matcher(text), type=_UNKNOWN_TYPE + ) + elif _is_matcher_v2(matcher): + result = matcher(context) + else: + api_version = _get_matcher_api_version(matcher) + raise ValueError(f"Unsupported API version {api_version}") + except BaseException: + # Show the ugly traceback if the matcher causes an + # exception, but do NOT crash the kernel! + sys.excepthook(*sys.exc_info()) + continue + + # set default value for matched fragment if suffix was not selected. + result["matched_fragment"] = result.get("matched_fragment", context.token) + + if not suppressed_matchers: + suppression_recommended: bool | set[str] = result.get( + "suppress", False + ) + + suppression_config = ( + self.suppress_competing_matchers.get(matcher_id, None) + if isinstance(self.suppress_competing_matchers, dict) + else self.suppress_competing_matchers + ) + should_suppress = ( + (suppression_config is True) + or (suppression_recommended and (suppression_config is not False)) + ) and has_any_completions(result) + + if should_suppress: + suppression_exceptions: set[str] = result.get( + "do_not_suppress", set() + ) + if isinstance(suppression_recommended, Iterable): + to_suppress = set(suppression_recommended) + else: + to_suppress = set(matchers) + suppressed_matchers = to_suppress - suppression_exceptions + + new_results = {} + for previous_matcher_id, previous_result in results.items(): + if previous_matcher_id not in suppressed_matchers: + new_results[previous_matcher_id] = previous_result + results = new_results + + results[matcher_id] = result + + _, matches = self._arrange_and_extract( + results, + # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission? + # if it was omission, we can remove the filtering step, otherwise remove this comment. + skip_matchers={jedi_matcher_id}, + abort_if_offset_changes=False, + ) + + # populate legacy stateful API + self.matches = matches + + return results + + @staticmethod + def _deduplicate( + matches: Sequence[AnyCompletion], + ) -> Iterable[AnyCompletion]: + filtered_matches: dict[str, AnyCompletion] = {} + for match in matches: + text = match.text + if ( + text not in filtered_matches + or filtered_matches[text].type == _UNKNOWN_TYPE + ): + filtered_matches[text] = match + + return filtered_matches.values() + + @staticmethod + def _sort(matches: Sequence[AnyCompletion]): + return sorted(matches, key=lambda x: completions_sorting_key(x.text)) + + @context_matcher() + def fwd_unicode_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API.""" + # TODO: use `context.limit` to terminate early once we matched the maximum + # number that will be used downstream; can be added as an optional to + # `fwd_unicode_match(text: str, limit: int = None)` or we could re-implement here. + fragment, matches = self.fwd_unicode_match(context.text_until_cursor) + return _convert_matcher_v1_result_to_v2( + matches, type="unicode", fragment=fragment, suppress_if_matches=True + ) + + def fwd_unicode_match(self, text: str) -> tuple[str, Sequence[str]]: + """ + Forward match a string starting with a backslash with a list of + potential Unicode completions. + + Will compute list of Unicode character names on first call and cache it. + + .. deprecated:: 8.6 + You can use :meth:`fwd_unicode_matcher` instead. + + Returns + ------- + At tuple with: + - matched text (empty if no matches) + - list of potential completions, empty tuple otherwise) + """ + # TODO: self.unicode_names is here a list we traverse each time with ~100k elements. + # We could do a faster match using a Trie. + + # Using pygtrie the following seem to work: + + # s = PrefixSet() + + # for c in range(0,0x10FFFF + 1): + # try: + # s.add(unicodedata.name(chr(c))) + # except ValueError: + # pass + # [''.join(k) for k in s.iter(prefix)] + + # But need to be timed and adds an extra dependency. + + slashpos = text.rfind('\\') + # if text starts with slash + if slashpos > -1: + # PERF: It's important that we don't access self._unicode_names + # until we're inside this if-block. _unicode_names is lazily + # initialized, and it takes a user-noticeable amount of time to + # initialize it, so we don't want to initialize it unless we're + # actually going to use it. + s = text[slashpos + 1 :] + sup = s.upper() + candidates = [x for x in self.unicode_names if x.startswith(sup)] + if candidates: + return s, candidates + candidates = [x for x in self.unicode_names if sup in x] + if candidates: + return s, candidates + splitsup = sup.split(" ") + candidates = [ + x for x in self.unicode_names if all(u in x for u in splitsup) + ] + if candidates: + return s, candidates + + return "", () + + # if text does not start with slash else: - # Extend the list of completions with the results of each - # matcher, so we return results to the user from all - # namespaces. - if self.merge_completions: - matches = [] - for matcher in self.matchers: - try: - matches.extend([(m, matcher.__qualname__) - for m in matcher(text)]) - except: - # Show the ugly traceback if the matcher causes an - # exception, but do NOT crash the kernel! - sys.excepthook(*sys.exc_info()) - else: - for matcher in self.matchers: - matches = [(m, matcher.__qualname__) - for m in matcher(text)] - if matches: - break - seen = set() - filtered_matches = set() - for m in matches: - t, c = m - if t not in seen: - filtered_matches.add(m) - seen.add(t) + return '', () + + @property + def unicode_names(self) -> list[str]: + """List of names of unicode code points that can be completed. + + The list is lazily initialized on first access. + """ + import unicodedata + + if self._unicode_names is None: + names = [] + for c in range(0,0x10FFFF + 1): + try: + names.append(unicodedata.name(chr(c))) + except ValueError: + pass + self._unicode_names = _unicode_name_compute(_UNICODE_RANGES) - _filtered_matches = sorted( - set(filtered_matches), key=lambda x: completions_sorting_key(x[0]))\ - [:MATCHES_LIMIT] + return self._unicode_names - _matches = [m[0] for m in _filtered_matches] - origins = [m[1] for m in _filtered_matches] - self.matches = _matches +def _unicode_name_compute(ranges: list[tuple[int, int]]) -> list[str]: + import unicodedata - return text, _matches, origins, completions + names = [] + for start,stop in ranges: + for c in range(start, stop) : + try: + names.append(unicodedata.name(chr(c))) + except ValueError: + pass + return names diff --git a/IPython/core/completerlib.py b/IPython/core/completerlib.py index eeb39a48b68..a2fbeab0c25 100644 --- a/IPython/core/completerlib.py +++ b/IPython/core/completerlib.py @@ -1,4 +1,3 @@ -# encoding: utf-8 """Implementations for various useful completers. These are all loaded by default by IPython. @@ -30,14 +29,12 @@ from zipimport import zipimporter # Our own imports -from IPython.core.completer import expand_user, compress_user -from IPython.core.error import TryNext -from IPython.utils._process_common import arg_split +from .completer import expand_user, compress_user +from .error import TryNext +from ..utils._process_common import arg_split # FIXME: this should be pulled in with the right call via the component system -from IPython import get_ipython - -from typing import List +from IPython.core.getipython import get_ipython #----------------------------------------------------------------------------- # Globals and constants @@ -52,7 +49,7 @@ TIMEOUT_GIVEUP = 20 # Regular expression for the python import statement -import_re = re.compile(r'(?P[a-zA-Z_][a-zA-Z0-9_]*?)' +import_re = re.compile(r'(?P[^\W\d]\w*?)' r'(?P[/\\]__init__)?' r'(?P%s)$' % r'|'.join(re.escape(s) for s in _suffixes)) @@ -64,7 +61,8 @@ # Local utilities #----------------------------------------------------------------------------- -def module_list(path): + +def module_list(path: str) -> list[str]: """ Return the list containing the names of the modules available in the given folder. @@ -80,7 +78,7 @@ def module_list(path): # Build a list of all files in the directory and all files # in its subdirectories. For performance reasons, do not # recurse more than one level into subdirectories. - files = [] + files: list[str] = [] for root, dirs, nondirs in os.walk(path, followlinks=True): subdir = root[len(path)+1:] if subdir: @@ -91,8 +89,8 @@ def module_list(path): else: try: - files = list(zipimporter(path)._files.keys()) - except: + files = list(zipimporter(path)._files.keys()) # type: ignore + except Exception: files = [] # Build a list of modules which match the import_re regex. @@ -117,7 +115,10 @@ def get_root_modules(): # Don't try to scan for modules every time. return list(sys.builtin_module_names) - rootmodules_cache = ip.db.get('rootmodules_cache', {}) + if getattr(ip.db, "_mock", False): + rootmodules_cache = {} + else: + rootmodules_cache = ip.db.get("rootmodules_cache", {}) rootmodules = list(sys.builtin_module_names) start_time = time() store = False @@ -148,21 +149,37 @@ def get_root_modules(): return rootmodules -def is_importable(module, attr, only_modules): +def is_importable(module, attr: str, only_modules) -> bool: if only_modules: - return inspect.ismodule(getattr(module, attr)) + try: + mod = getattr(module, attr) + except ModuleNotFoundError: + # See gh-14434 + return False + return inspect.ismodule(mod) else: return not(attr[:2] == '__' and attr[-2:] == '__') +def is_possible_submodule(module, attr): + try: + obj = getattr(module, attr) + except AttributeError: + # Is possibly an unimported submodule + return True + except TypeError: + # https://github.com/ipython/ipython/issues/9678 + return False + return inspect.ismodule(obj) -def try_import(mod: str, only_modules=False) -> List[str]: + +def try_import(mod: str, only_modules=False) -> list[str]: """ Try to import given module and return list of potential completions. """ mod = mod.rstrip('.') try: m = import_module(mod) - except: + except ImportError: return [] m_is_init = '__init__' in (getattr(m, '__file__', '') or '') @@ -172,9 +189,17 @@ def try_import(mod: str, only_modules=False) -> List[str]: completions.extend( [attr for attr in dir(m) if is_importable(m, attr, only_modules)]) - completions.extend(getattr(m, '__all__', [])) + m_all = getattr(m, "__all__", []) + if only_modules: + completions.extend(attr for attr in m_all if is_possible_submodule(m, attr)) + else: + completions.extend(m_all) + if m_is_init: - completions.extend(module_list(os.path.dirname(m.__file__))) + file_ = m.__file__ + file_path = os.path.dirname(file_) # type: ignore + if file_path is not None: + completions.extend(module_list(file_path)) completions_set = {c for c in completions if isinstance(c, str)} completions_set.discard('__init__') return list(completions_set) @@ -185,7 +210,7 @@ def try_import(mod: str, only_modules=False) -> List[str]: #----------------------------------------------------------------------------- def quick_completer(cmd, completions): - """ Easily create a trivial completer for a command. + r""" Easily create a trivial completer for a command. Takes either a list of completions, or all completions in string (that will be split on whitespace). diff --git a/IPython/core/crashhandler.py b/IPython/core/crashhandler.py index f3abc1c6fe0..b96a7a83ba6 100644 --- a/IPython/core/crashhandler.py +++ b/IPython/core/crashhandler.py @@ -1,4 +1,3 @@ -# encoding: utf-8 """sys.excepthook for IPython itself, leaves a detailed report on disk. Authors: @@ -19,15 +18,28 @@ # Imports #----------------------------------------------------------------------------- -import os +from __future__ import annotations + import sys import traceback from pprint import pformat +from pathlib import Path + +import builtins as builtin_mod + +from typing import TYPE_CHECKING from IPython.core import ultratb from IPython.core.release import author_email from IPython.utils.sysinfo import sys_info -from IPython.utils.py3compat import input + +from IPython.core.release import __version__ as version + +import types + +if TYPE_CHECKING: + # avoid a circular import: application imports crashhandler at module load + from IPython.core.application import Application #----------------------------------------------------------------------------- # Code @@ -68,7 +80,7 @@ """ _lite_message_template = """ -If you suspect this is an IPython bug, please report it at: +If you suspect this is an IPython {version} bug, please report it at: https://github.com/ipython/ipython/issues or send an email to the mailing list at {email} @@ -80,7 +92,7 @@ """ -class CrashHandler(object): +class CrashHandler: """Customizable crash handlers for IPython applications. Instances of this class provide a :meth:`__call__` method which can be @@ -91,35 +103,42 @@ def __call__(self, etype, evalue, etb) message_template = _default_message_template section_sep = '\n\n'+'*'*75+'\n\n' - - def __init__(self, app, contact_name=None, contact_email=None, - bug_tracker=None, show_crash_traceback=True, call_pdb=False): + info: dict[str, str | None] + + def __init__( + self, + app: Application, + contact_name: str | None = None, + contact_email: str | None = None, + bug_tracker: str | None = None, + show_crash_traceback: bool = True, + call_pdb: bool = False, + ): """Create a new crash handler Parameters ---------- - app : Application + app : Application A running :class:`Application` instance, which will be queried at crash time for internal information. - contact_name : str A string with the name of the person to contact. - contact_email : str A string with the email address of the contact. - bug_tracker : str A string with the URL for your project's bug tracker. - show_crash_traceback : bool If false, don't print the crash traceback on stderr, only generate the on-disk report + call_pdb + Whether to call pdb on crash - Non-argument instance attributes: - + Attributes + ---------- These instances contain some non-argument attributes which allow for further customization of the crash handler's behavior. Please see the source for further details. + """ self.crash_report_fname = "Crash_report_%s.txt" % app.name self.app = app @@ -132,34 +151,36 @@ def __init__(self, app, contact_name=None, contact_email=None, bug_tracker = bug_tracker, crash_report_fname = self.crash_report_fname) - - def __call__(self, etype, evalue, etb): + def __call__( + self, + etype: type[BaseException], + evalue: BaseException, + etb: types.TracebackType, + ) -> None: """Handle an exception, call for compatible with sys.excepthook""" - + # do not allow the crash handler to be called twice without reinstalling it # this prevents unlikely errors in the crash handling from entering an # infinite loop. sys.excepthook = sys.__excepthook__ - - # Report tracebacks shouldn't use color in general (safer for users) - color_scheme = 'NoColor' # Use this ONLY for developer debugging (keep commented out for release) - #color_scheme = 'Linux' # dbg - try: - rptdir = self.app.ipython_dir - except: - rptdir = os.getcwd() - if rptdir is None or not os.path.isdir(rptdir): - rptdir = os.getcwd() - report_name = os.path.join(rptdir,self.crash_report_fname) + ipython_dir = getattr(self.app, "ipython_dir", None) + if ipython_dir is not None: + assert isinstance(ipython_dir, str) + rptdir = Path(ipython_dir) + else: + rptdir = Path.cwd() + if not rptdir.is_dir(): + rptdir = Path.cwd() + report_name = rptdir / self.crash_report_fname # write the report filename into the instance dict so it can get # properly expanded out in the user message template - self.crash_report_fname = report_name - self.info['crash_report_fname'] = report_name + self.crash_report_fname = str(report_name) + self.info["crash_report_fname"] = str(report_name) TBhandler = ultratb.VerboseTB( - color_scheme=color_scheme, - long_header=1, + theme_name="nocolor", + long_header=True, call_pdb=self.call_pdb, ) if self.call_pdb: @@ -174,21 +195,22 @@ def __call__(self, etype, evalue, etb): # and generate a complete report on disk try: - report = open(report_name,'w') - except: + report = open(report_name, "w", encoding="utf-8") + except OSError: print('Could not create crash report on disk.', file=sys.stderr) return - # Inform user on stderr of what happened - print('\n'+'*'*70+'\n', file=sys.stderr) - print(self.message_template.format(**self.info), file=sys.stderr) + with report: + # Inform user on stderr of what happened + print('\n'+'*'*70+'\n', file=sys.stderr) + print(self.message_template.format(**self.info), file=sys.stderr) + + # Construct report on disk + report.write(self.make_report(str(traceback))) - # Construct report on disk - report.write(self.make_report(traceback)) - report.close() - input("Hit to quit (your terminal may close):") + builtin_mod.input("Hit to quit (your terminal may close):") - def make_report(self,traceback): + def make_report(self, traceback: str) -> str: """Return a string containing a crash report.""" sec_sep = self.section_sep @@ -200,20 +222,22 @@ def make_report(self,traceback): try: config = pformat(self.app.config) rpt_add(sec_sep) - rpt_add('Application name: %s\n\n' % self.app_name) - rpt_add('Current user configuration structure:\n\n') + rpt_add("Application name: %s\n\n" % self.app.name) + rpt_add("Current user configuration structure:\n\n") rpt_add(config) - except: + except Exception: pass rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) return ''.join(report) -def crash_handler_lite(etype, evalue, tb): +def crash_handler_lite( + etype: type[BaseException], evalue: BaseException, tb: types.TracebackType +) -> None: """a light excepthook, adding a small message to the usual traceback""" traceback.print_exception(etype, evalue, tb) - + from IPython.core.interactiveshell import InteractiveShell if InteractiveShell.initialized(): # we are in a Shell environment, give %magic example @@ -221,5 +245,4 @@ def crash_handler_lite(etype, evalue, tb): else: # we are not in a shell, show generic config config = "c." - print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr) - + print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 4ece380bf9c..ca02d0b38e3 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -1,7 +1,97 @@ -# -*- coding: utf-8 -*- """ Pdb debugger class. + +This is an extension to PDB which adds a number of new features. +Note that there is also the `IPython.terminal.debugger` class which provides UI +improvements. + +We also strongly recommend to use this via the `ipdb` package, which provides +extra configuration options. + +Among other things, this subclass of PDB: + - supports many IPython magics like pdef/psource + - hide frames in tracebacks based on `__tracebackhide__` + - allows to skip frames based on `__debuggerskip__` + + +Global Configuration +-------------------- + +The IPython debugger will by read the global ``~/.pdbrc`` file. +That is to say you can list all commands supported by ipdb in your `~/.pdbrc` +configuration file, to globally configure pdb. + +Example:: + + # ~/.pdbrc + skip_predicates debuggerskip false + skip_hidden false + context 25 + +Features +-------- + +The IPython debugger can hide and skip frames when printing or moving through +the stack. This can have a performance impact, so can be configures. + +The skipping and hiding frames are configurable via the `skip_predicates` +command. + +By default, frames from readonly files will be hidden, frames containing +``__tracebackhide__ = True`` will be hidden. + +Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent +frames value of ``__debuggerskip__`` is ``True`` will also be skipped. + + >>> def helpers_helper(): + ... pass + ... + ... def helper_1(): + ... print("don't step in me") + ... helpers_helpers() # will be stepped over unless breakpoint set. + ... + ... + ... def helper_2(): + ... print("in me neither") + ... + +One can define a decorator that wraps a function between the two helpers: + + >>> def pdb_skipped_decorator(function): + ... + ... + ... def wrapped_fn(*args, **kwargs): + ... __debuggerskip__ = True + ... helper_1() + ... __debuggerskip__ = False + ... result = function(*args, **kwargs) + ... __debuggerskip__ = True + ... helper_2() + ... # setting __debuggerskip__ to False again is not necessary + ... return result + ... + ... return wrapped_fn + +When decorating a function, ipdb will directly step into ``bar()`` by +default: + + >>> @foo_decorator + ... def bar(x, y): + ... return x * y + + +You can toggle the behavior with + + ipdb> skip_predicates debuggerskip false + +or configure it in your ``.pdbrc`` + + + +License +------- + Modified from the standard pdb.Pdb class to avoid including readline, so that the command line completion of other programs which include this isn't damaged. @@ -9,14 +99,19 @@ In the future, this class will be expanded with improvements over the standard pdb. -The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor -changes. Licensing should therefore be under the standard Python terms. For -details on the PSF (Python Software Foundation) standard license, see: +The original code in this file is mainly lifted out of cmd.py in Python 2.2, +with minor changes. Licensing should therefore be under the standard Python +terms. For details on the PSF (Python Software Foundation) standard license, +see: https://docs.python.org/2/license.html + + +All the changes since then are under the same license as IPython. + """ -#***************************************************************************** +# ***************************************************************************** # # This file is licensed under the PSF license. # @@ -24,163 +119,77 @@ # Copyright (C) 2005-2006 Fernando Perez. # # -#***************************************************************************** +# ***************************************************************************** + +from __future__ import annotations -import bdb -import functools import inspect import linecache +import os +import re import sys import warnings -import re +from contextlib import contextmanager -from IPython import get_ipython +from IPython.core.getipython import get_ipython +from IPython.core.debugger_backport import PdbClosureBackport from IPython.utils import PyColorize -from IPython.utils import coloransi, py3compat -from IPython.core.excolors import exception_colors -from IPython.testing.skipdoctest import skip_doctest +from IPython.utils.PyColorize import TokenStream +from typing import TYPE_CHECKING +from types import FrameType -prompt = 'ipdb> ' +# We have to check this directly from sys.argv, config struct not yet available +from pdb import Pdb as _OldPdb +from pygments.token import Token -#We have to check this directly from sys.argv, config struct not yet available -from pdb import Pdb as OldPdb -# Allow the set_trace code to operate outside of an ipython instance, even if -# it does so with some limitations. The rest of this support is implemented in -# the Tracer constructor. +if sys.version_info < (3, 13): -def make_arrow(pad): - """generate the leading arrow in front of traceback or debugger""" - if pad >= 2: - return '-'*(pad-2) + '> ' - elif pad == 1: - return '>' - return '' + class OldPdb(PdbClosureBackport, _OldPdb): + pass +else: + OldPdb = _OldPdb -def BdbQuit_excepthook(et, ev, tb, excepthook=None): - """Exception hook which handles `BdbQuit` exceptions. +if TYPE_CHECKING: + # otherwise circular import + from IPython.core.interactiveshell import InteractiveShell - All other exceptions are processed using the `excepthook` - parameter. - """ - warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1", - DeprecationWarning, stacklevel=2) - if et==bdb.BdbQuit: - print('Exiting Debugger.') - elif excepthook is not None: - excepthook(et, ev, tb) - else: - # Backwards compatibility. Raise deprecation warning? - BdbQuit_excepthook.excepthook_ori(et,ev,tb) - - -def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None): - warnings.warn( - "`BdbQuit_IPython_excepthook` is deprecated since version 5.1", - DeprecationWarning, stacklevel=2) - print('Exiting Debugger.') - - -class Tracer(object): - """ - DEPRECATED +# skip module docstests +__skip_doctest__ = True - Class for local debugging, similar to pdb.set_trace. +prompt = "ipdb> " - Instances of this class, when called, behave like pdb.set_trace, but - providing IPython's enhanced capabilities. - This is implemented as a class which must be initialized in your own code - and not as a standalone function because we need to detect at runtime - whether IPython is already active or not. That detection is done in the - constructor, ensuring that this code plays nicely with a running IPython, - while functioning acceptably (though with limitations) if outside of it. - """ - - @skip_doctest - def __init__(self, colors=None): - """ - DEPRECATED - - Create a local debugger instance. - - Parameters - ---------- - - colors : str, optional - The name of the color scheme to use, it must be one of IPython's - valid color schemes. If not given, the function will default to - the current IPython scheme when running inside IPython, and to - 'NoColor' otherwise. +# Allow the set_trace code to operate outside of an ipython instance, even if +# it does so with some limitations. The rest of this support is implemented in +# the Tracer constructor. - Examples - -------- - :: +DEBUGGERSKIP = "__debuggerskip__" - from IPython.core.debugger import Tracer; debug_here = Tracer() - Later in your code:: +# this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676 +# on lower python versions, we backported the feature. +CHAIN_EXCEPTIONS = sys.version_info < (3, 13) - debug_here() # -> will open up the debugger at that point. - Once the debugger activates, you can use all of its regular commands to - step through code, set breakpoints, etc. See the pdb documentation - from the Python standard library for usage details. - """ - warnings.warn("`Tracer` is deprecated since version 5.1, directly use " - "`IPython.core.debugger.Pdb.set_trace()`", - DeprecationWarning, stacklevel=2) - - ip = get_ipython() - if ip is None: - # Outside of ipython, we set our own exception hook manually - sys.excepthook = functools.partial(BdbQuit_excepthook, - excepthook=sys.excepthook) - def_colors = 'NoColor' - else: - # In ipython, we use its custom exception handler mechanism - def_colors = ip.colors - ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook) - - if colors is None: - colors = def_colors - - # The stdlib debugger internally uses a modified repr from the `repr` - # module, that limits the length of printed strings to a hardcoded - # limit of 30 characters. That much trimming is too aggressive, let's - # at least raise that limit to 80 chars, which should be enough for - # most interactive uses. - try: - try: - from reprlib import aRepr # Py 3 - except ImportError: - from repr import aRepr # Py 2 - aRepr.maxstring = 80 - except: - # This is only a user-facing convenience, so any error we encounter - # here can be warned about but can be otherwise ignored. These - # printouts will tell us about problems if this API changes - import traceback - traceback.print_exc() - - self.debugger = Pdb(colors) - - def __call__(self): - """Starts an interactive debugger at the point where called. - - This is similar to the pdb.set_trace() function from the std lib, but - using IPython's enhanced debugger.""" +def BdbQuit_excepthook(et, ev, tb, excepthook=None): + """Exception hook which handles `BdbQuit` exceptions. - self.debugger.set_trace(sys._getframe().f_back) + All other exceptions are processed using the `excepthook` + parameter. + """ + raise ValueError( + "`BdbQuit_excepthook` is deprecated since version 5.1. It is still around only because it is still imported by ipdb.", + ) -RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+') +RGX_EXTRA_INDENT = re.compile(r"(?<=\n)\s+") def strip_indentation(multiline_string): - return RGX_EXTRA_INDENT.sub('', multiline_string) + return RGX_EXTRA_INDENT.sub("", multiline_string) def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): @@ -188,256 +197,620 @@ def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): for the ``do_...`` commands that hook into the help system. Adapted from from a comp.lang.python posting by Duncan Booth.""" + def wrapper(*args, **kw): return new_fn(*args, **kw) + if old_fn.__doc__: wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text return wrapper -def _file_lines(fname): - """Return the contents of a named file as a list of lines. - - This function never raises an IOError exception: if the file can't be - read, it simply returns an empty list.""" - - try: - outfile = open(fname) - except IOError: - return [] - else: - out = outfile.readlines() - outfile.close() - return out - - class Pdb(OldPdb): """Modified Pdb class, does not load readline. for a standalone version that uses prompt_toolkit, see `IPython.terminal.debugger.TerminalPdb` and `IPython.terminal.debugger.set_trace()` + + + This debugger can hide and skip frames that are tagged according to some predicates. + See the `skip_predicates` commands. + """ - def __init__(self, color_scheme=None, completekey=None, - stdin=None, stdout=None, context=5): + shell: InteractiveShell + _theme_name: str + _context: int + + _chained_exceptions: tuple[Exception, ...] + _chained_exception_index: int + + if CHAIN_EXCEPTIONS: + MAX_CHAINED_EXCEPTION_DEPTH = 999 + + default_predicates = { + "tbhide": True, + "readonly": False, + "ipython_internal": True, + "debuggerskip": True, + } + + def __init__( + self, + completekey=None, + stdin=None, + stdout=None, + context: int | None | str = 5, + *, + mode: str | None = None, + **kwargs, + ): + """Create a new IPython debugger. - # Parent constructor: - try: - self.context = int(context) - if self.context <= 0: - raise ValueError("Context must be a positive integer") - except (TypeError, ValueError): - raise ValueError("Context must be a positive integer") + Parameters + ---------- + completekey : default None + Passed to pdb.Pdb. + stdin : default None + Passed to pdb.Pdb. + stdout : default None + Passed to pdb.Pdb. + context : int + Number of lines of source code context to show when + displaying stacktrace information. + mode : str, optional + How the debugger was invoked, one of ``'inline'`` (used by the + ``breakpoint()`` builtin), ``'cli'`` (used by the command line + invocation) or ``None`` (backwards compatible behaviour). This + argument was added to stdlib's ``pdb.Pdb`` in Python 3.14; it is + accepted on every supported Python version here but only forwarded + to the underlying ``pdb.Pdb`` when it is actually supported. + **kwargs + Passed to pdb.Pdb. + + Notes + ----- + The possibilities are python version dependent, see the python + docs for more info. + """ + # ipdb issue, see https://github.com/ipython/ipython/issues/14811 + if context is None: + context = 5 + if isinstance(context, str): + context = int(context) + self.context = context + + # The `mode` argument was added to `pdb.Pdb` in Python 3.14. We accept + # it on every supported Python version so that callers written against + # 3.14+ keep working, but only forward it to the underlying `pdb.Pdb` + # when it understands it. + if sys.version_info >= (3, 14): + kwargs["mode"] = mode + else: + self.mode = mode - OldPdb.__init__(self, completekey, stdin, stdout) + # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`. + OldPdb.__init__(self, completekey, stdin, stdout, **kwargs) + # Python 3.15+ should define this, so no need to initialize + # this avoids some getattr(self, 'curframe') + if sys.version_info < (3, 15): + self.curframe = None # IPython changes... - self.shell = get_ipython() + shell = get_ipython() - if self.shell is None: - save_main = sys.modules['__main__'] + if shell is None: + save_main = sys.modules["__main__"] # No IPython instance running, we must create one - from IPython.terminal.interactiveshell import \ - TerminalInteractiveShell - self.shell = TerminalInteractiveShell.instance() + from IPython.terminal.interactiveshell import TerminalInteractiveShell + + shell = TerminalInteractiveShell.instance() # needed by any code which calls __import__("__main__") after # the debugger was entered. See also #9941. - sys.modules['__main__'] = save_main - - if color_scheme is not None: - warnings.warn( - "The `color_scheme` argument is deprecated since version 5.1", - DeprecationWarning, stacklevel=2) - else: - color_scheme = self.shell.colors + sys.modules["__main__"] = save_main + self.shell = shell self.aliases = {} - # Create color table: we copy the default one from the traceback - # module and add a few attributes needed for debugging - self.color_scheme_table = exception_colors() - - # shorthands - C = coloransi.TermColors - cst = self.color_scheme_table - - cst['NoColor'].colors.prompt = C.NoColor - cst['NoColor'].colors.breakpoint_enabled = C.NoColor - cst['NoColor'].colors.breakpoint_disabled = C.NoColor - - cst['Linux'].colors.prompt = C.Green - cst['Linux'].colors.breakpoint_enabled = C.LightRed - cst['Linux'].colors.breakpoint_disabled = C.Red - - cst['LightBG'].colors.prompt = C.Blue - cst['LightBG'].colors.breakpoint_enabled = C.LightRed - cst['LightBG'].colors.breakpoint_disabled = C.Red - - cst['Neutral'].colors.prompt = C.Blue - cst['Neutral'].colors.breakpoint_enabled = C.LightRed - cst['Neutral'].colors.breakpoint_disabled = C.Red - + theme_name = self.shell.colors + assert isinstance(theme_name, str) + assert theme_name.lower() == theme_name # Add a python parser so we can syntax highlight source while # debugging. - self.parser = PyColorize.Parser(style=color_scheme) - self.set_colors(color_scheme) + self.parser = PyColorize.Parser(theme_name=theme_name) + self.set_theme_name(theme_name) # Set the prompt - the default prompt is '(Pdb)' self.prompt = prompt - + self.skip_hidden = True + self.report_skipped = True + + # list of predicates we use to skip frames + self._predicates = self.default_predicates + + # Per-instance caches for the DEBUGGERSKIP frame checks (see + # `_cachable_skip`). Keyed by frame objects, so they must not outlive + # the debugger stop they were computed for: they are cleared on every + # `interaction` (and size-bounded) to avoid pinning frames — and + # transitively their locals and whole back-chains — in memory. + self._skip_cache: dict[FrameType, bool] = {} + self._parent_skip_cache: dict[FrameType, bool | None] = {} + + if CHAIN_EXCEPTIONS: + self._chained_exceptions = tuple() + self._chained_exception_index = 0 + + @property + def context(self) -> int: + return self._context + + @context.setter + def context(self, value: int | str) -> None: + # ipdb issue see https://github.com/ipython/ipython/issues/14811 + if not isinstance(value, int): + value = int(value) + assert isinstance(value, int) + assert value >= 0 + self._context = value + + def set_theme_name(self, name): + assert name.lower() == name + assert isinstance(name, str) + self._theme_name = name + self.parser.theme_name = name + + @property + def theme(self): + return PyColorize.theme_table[self._theme_name] + + # def set_colors(self, scheme): """Shorthand access to the color table scheme selector method.""" - self.color_scheme_table.set_active_scheme(scheme) - self.parser.style = scheme + warnings.warn( + "set_colors is deprecated since IPython 9.0, use set_theme_name instead", + DeprecationWarning, + stacklevel=2, + ) + assert scheme == scheme.lower() + self._theme_name = scheme.lower() + self.parser.theme_name = scheme.lower() + + def set_trace(self, frame=None, **kwargs): + if frame is None: + frame = sys._getframe().f_back + self.initial_frame = frame + return super().set_trace(frame, **kwargs) + + def get_stack(self, *args, **kwargs): + stack, pos = super().get_stack(*args, **kwargs) + if len(stack) >= 0 and self._is_internal_frame(stack[0][0]): + stack.pop(0) + pos -= 1 + return stack, pos + + def _is_internal_frame(self, frame): + """Determine if this frame should be skipped as internal""" + filename = frame.f_code.co_filename + + # Skip bdb.py runcall and internal operations + if filename.endswith("bdb.py"): + func_name = frame.f_code.co_name + # Skip internal bdb operations but allow breakpoint hits + if func_name in ("runcall", "run", "runeval"): + return True - def interaction(self, frame, traceback): + return False + + def _hidden_predicate(self, frame): + """ + Given a frame return whether it it should be hidden or not by IPython. + """ + + if self._predicates["readonly"]: + fname = frame.f_code.co_filename + # we need to check for file existence and interactively define + # function would otherwise appear as RO. + if os.path.isfile(fname) and not os.access(fname, os.W_OK): + return True + + if self._predicates["tbhide"]: + if frame in (self.curframe, getattr(self, "initial_frame", None)): + return False + frame_locals = self._get_frame_locals(frame) + if "__tracebackhide__" not in frame_locals: + return False + return frame_locals["__tracebackhide__"] + return False + + def hidden_frames(self, stack): + """ + Given an index in the stack return whether it should be skipped. + + This is used in up/down and where to skip frames. + """ + # The f_locals dictionary is updated from the actual frame + # locals whenever the .f_locals accessor is called, so we + # avoid calling it here to preserve self._curframe_locals. + # Furthermore, there is no good reason to hide the current frame. + ip_hide = [self._hidden_predicate(s[0]) for s in stack] + ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] + if ip_start and self._predicates["ipython_internal"]: + ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)] + return ip_hide + + if CHAIN_EXCEPTIONS: + + def _get_tb_and_exceptions(self, tb_or_exc): + """ + Given a tracecack or an exception, return a tuple of chained exceptions + and current traceback to inspect. + This will deal with selecting the right ``__cause__`` or ``__context__`` + as well as handling cycles, and return a flattened list of exceptions we + can jump to with do_exceptions. + """ + _exceptions = [] + if isinstance(tb_or_exc, BaseException): + traceback, current = tb_or_exc.__traceback__, tb_or_exc + + while current is not None: + if current in _exceptions: + break + _exceptions.append(current) + if current.__cause__ is not None: + current = current.__cause__ + elif ( + current.__context__ is not None + and not current.__suppress_context__ + ): + current = current.__context__ + + if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH: + self.message( + f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}" + " chained exceptions found, not all exceptions" + " will be browsable with `exceptions`." + ) + break + else: + traceback = tb_or_exc + return tuple(reversed(_exceptions)), traceback + + @contextmanager + def _hold_exceptions(self, exceptions): + """ + Context manager to ensure proper cleaning of exceptions references + When given a chained exception instead of a traceback, + pdb may hold references to many objects which may leak memory. + We use this context manager to make sure everything is properly cleaned + """ + try: + self._chained_exceptions = exceptions + self._chained_exception_index = len(exceptions) - 1 + yield + finally: + # we can't put those in forget as otherwise they would + # be cleared on exception change + self._chained_exceptions = tuple() + self._chained_exception_index = 0 + + def do_exceptions(self, arg): + """exceptions [number] + List or change current exception in an exception chain. + Without arguments, list all the current exception in the exception + chain. Exceptions will be numbered, with the current exception indicated + with an arrow. + If given an integer as argument, switch to the exception at that index. + ``exception`` can be used as an alias for this command. + """ + if not self._chained_exceptions: + self.message( + "Did not find chained exceptions. To move between" + " exceptions, pdb/post_mortem must be given an exception" + " object rather than a traceback." + ) + return + if not arg: + for ix, exc in enumerate(self._chained_exceptions): + prompt = ">" if ix == self._chained_exception_index else " " + rep = repr(exc) + if len(rep) > 80: + rep = rep[:77] + "..." + indicator = ( + " -" + if self._chained_exceptions[ix].__traceback__ is None + else f"{ix:>3}" + ) + self.message(f"{prompt} {indicator} {rep}") + else: + try: + number = int(arg) + except ValueError: + self.error("Argument must be an integer") + return + if 0 <= number < len(self._chained_exceptions): + if self._chained_exceptions[number].__traceback__ is None: + self.error( + "This exception does not have a traceback, cannot jump to it" + ) + return + + self._chained_exception_index = number + self.setup(None, self._chained_exceptions[number].__traceback__) + self.print_stack_entry(self.stack[self.curindex]) + else: + self.error("No exception with that number") + + def do_exception(self, arg): + """exception [number] + Alias for the ``exceptions`` command. + """ + return self.do_exceptions(arg) + + def _cmdloop(self): + # Override to bypass Python 3.15's _maybe_use_pyrepl_as_stdin(), which + # sets use_rawinput=False and conflicts with IPython's own input handling. + while True: + try: + self.allow_kbdint = True + self.cmdloop() + self.allow_kbdint = False + break + except KeyboardInterrupt: + self.message("--KeyboardInterrupt--") + + def interaction(self, frame, tb_or_exc): + # The DEBUGGERSKIP caches are only valid for a single stop: frame + # locals may change while the program runs, and keeping frame keys + # alive across stops would leak memory (see `_cachable_skip`). + self._skip_cache.clear() + self._parent_skip_cache.clear() try: - OldPdb.interaction(self, frame, traceback) + if CHAIN_EXCEPTIONS: + # this context manager is part of interaction in 3.13 + _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) + if isinstance(tb_or_exc, BaseException): + assert tb is not None, "main exception must have a traceback" + with self._hold_exceptions(_chained_exceptions): + OldPdb.interaction(self, frame, tb) + else: + OldPdb.interaction(self, frame, tb_or_exc) + except KeyboardInterrupt: - sys.stdout.write('\n' + self.shell.get_exception_only()) + self.stdout.write("\n" + self.shell.get_exception_only()) - def new_do_up(self, arg): - OldPdb.do_up(self, arg) - do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up) + def precmd(self, line): + """Perform useful escapes on the command before it is executed.""" - def new_do_down(self, arg): - OldPdb.do_down(self, arg) + if line.endswith("??"): + line = "pinfo2 " + line[:-2] + elif line.endswith("?"): + line = "pinfo " + line[:-1] - do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down) + line = super().precmd(line) - def new_do_frame(self, arg): - OldPdb.do_frame(self, arg) + return line def new_do_quit(self, arg): - - if hasattr(self, 'old_all_completions'): - self.shell.Completer.all_completions=self.old_all_completions - return OldPdb.do_quit(self, arg) do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit) - def new_do_restart(self, arg): - """Restart command. In the context of ipython this is exactly the same - thing as 'quit'.""" - self.msg("Restart doesn't make sense here. Using 'quit' instead.") - return self.do_quit(arg) - - def print_stack_trace(self, context=None): + def print_stack_trace(self, context: int | None = None): if context is None: context = self.context try: - context=int(context) - if context <= 0: - raise ValueError("Context must be a positive integer") - except (TypeError, ValueError): - raise ValueError("Context must be a positive integer") - try: - for frame_lineno in self.stack: - self.print_stack_entry(frame_lineno, context=context) + skipped = 0 + to_print = "" + for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack): + if hidden and self.skip_hidden: + skipped += 1 + continue + if skipped: + to_print += self.theme.format( + [ + ( + Token.ExcName, + f" [... skipping {skipped} hidden frame(s)]", + ), + (Token, "\n"), + ] + ) + + skipped = 0 + to_print += self.format_stack_entry(frame_lineno) + if skipped: + to_print += self.theme.format( + [ + ( + Token.ExcName, + f" [... skipping {skipped} hidden frame(s)]", + ), + (Token, "\n"), + ] + ) + print(to_print, file=self.stdout) except KeyboardInterrupt: pass - def print_stack_entry(self,frame_lineno, prompt_prefix='\n-> ', - context=None): - if context is None: - context = self.context - try: - context=int(context) - if context <= 0: - raise ValueError("Context must be a positive integer") - except (TypeError, ValueError): - raise ValueError("Context must be a positive integer") - print(self.format_stack_entry(frame_lineno, '', context)) + def print_stack_entry( + self, frame_lineno: tuple[FrameType, int], prompt_prefix: str = "\n-> " + ) -> None: + """ + Overwrite print_stack_entry from superclass (PDB) + """ + print(self.format_stack_entry(frame_lineno, ""), file=self.stdout) - # vds: >> frame, lineno = frame_lineno filename = frame.f_code.co_filename self.shell.hooks.synchronize_with_editor(filename, lineno, 0) - # vds: << - def format_stack_entry(self, frame_lineno, lprefix=': ', context=None): - if context is None: - context = self.context + def _pdbcmd_print_frame_status(self, arg): + """Use print_stack_entry to print frames in Python 3.14+.""" + if sys.version_info[:2] >= (3, 14): + # This is the only line changed from the base class. + self.print_stack_entry(self.stack[self.curindex]) + + # Same as in 3.14 + self._validate_file_mtime() + self._show_display() + else: + # 3.13 and 3.12 don't need any changes. + super()._pdbcmd_print_frame_status(arg) # type: ignore[misc] + + @property + def _curframe_locals(self): + """Locals of the frame the debugger currently points at. + + On Python 3.13+, ``frame.f_locals`` is a write-through proxy (PEP 667) + which pdb no longer caches, and ``Pdb.curframe_locals`` is deprecated + in 3.14 in favor of ``curframe.f_locals``. On older versions + ``curframe_locals`` is a snapshot which must be reused, see + `_get_frame_locals`. + """ + assert self.curframe is not None + if sys.version_info >= (3, 13): + return self.curframe.f_locals + return self.curframe_locals + + def _get_frame_locals(self, frame): + """ " + Accessing f_local of current frame reset the namespace, so we want to avoid + that or the following can happen + + ipdb> foo + "old" + ipdb> foo = "new" + ipdb> foo + "new" + ipdb> where + ipdb> foo + "old" + + So if frame is self.current_frame we instead return self._curframe_locals + + """ + if frame is self.curframe: + return self._curframe_locals + else: + return frame.f_locals + + def format_stack_entry( + self, + frame_lineno: tuple[FrameType, int], + lprefix: str = ": ", + ) -> str: + """ + overwrite from super class so must -> str + """ + context = self.context try: - context=int(context) + context = int(context) if context <= 0: - print("Context must be a positive integer") + print("Context must be a positive integer", file=self.stdout) except (TypeError, ValueError): - print("Context must be a positive integer") - try: - import reprlib # Py 3 - except ImportError: - import repr as reprlib # Py 2 + print("Context must be a positive integer", file=self.stdout) - ret = [] + import reprlib - Colors = self.color_scheme_table.active_colors - ColorsNormal = Colors.Normal - tpl_link = u'%s%%s%s' % (Colors.filenameEm, ColorsNormal) - tpl_call = u'%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal) - tpl_line = u'%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal) - tpl_line_em = u'%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, - ColorsNormal) + ret_tok = [] frame, lineno = frame_lineno - return_value = '' - if '__return__' in frame.f_locals: - rv = frame.f_locals['__return__'] - #return_value += '->' - return_value += reprlib.repr(rv) + '\n' - ret.append(return_value) + return_value = "" + loc_frame = self._get_frame_locals(frame) + if "__return__" in loc_frame: + rv = loc_frame["__return__"] + # return_value += '->' + return_value += reprlib.repr(rv) + "\n" + ret_tok.extend([(Token, return_value)]) - #s = filename + '(' + `lineno` + ')' + # s = filename + '(' + `lineno` + ')' filename = self.canonic(frame.f_code.co_filename) - link = tpl_link % py3compat.cast_unicode(filename) + link_tok = (Token.FilenameEm, filename) if frame.f_code.co_name: func = frame.f_code.co_name else: func = "" - call = '' - if func != '?': - if '__args__' in frame.f_locals: - args = reprlib.repr(frame.f_locals['__args__']) + call_toks = [] + if func != "?": + if "__args__" in loc_frame: + args = reprlib.repr(loc_frame["__args__"]) else: - args = '()' - call = tpl_call % (func, args) + args = "()" + call_toks = [(Token.VName, func), (Token.ValEm, args)] # The level info should be generated in the same format pdb uses, to # avoid breaking the pdbtrack functionality of python-mode in *emacs. if frame is self.curframe: - ret.append('> ') + ret_tok.append((Token.CurrentFrame, self.theme.make_arrow(2))) else: - ret.append(' ') - ret.append(u'%s(%s)%s\n' % (link,lineno,call)) - - start = lineno - 1 - context//2 + ret_tok.append((Token, " ")) + + ret_tok.extend( + [ + link_tok, + (Token, "("), + (Token.Lineno, str(lineno)), + (Token, ")"), + *call_toks, + (Token, "\n"), + ] + ) + + start = lineno - 1 - context // 2 lines = linecache.getlines(filename) start = min(start, len(lines) - context) start = max(start, 0) lines = lines[start : start + context] - for i,line in enumerate(lines): - show_arrow = (start + 1 + i == lineno) - linetpl = (frame is self.curframe or show_arrow) \ - and tpl_line_em \ - or tpl_line - ret.append(self.__format_line(linetpl, filename, - start + 1 + i, line, - arrow = show_arrow) ) - return ''.join(ret) - - def __format_line(self, tpl_line, filename, lineno, line, arrow = False): + for i, line in enumerate(lines): + show_arrow = start + 1 + i == lineno + + bp, num, colored_line = self.__line_content( + filename, + start + 1 + i, + line, + arrow=show_arrow, + ) + if frame is self.curframe or show_arrow: + rlt = [ + bp, + (Token.LinenoEm, num), + (Token, " "), + # TODO: investigate Toke.Line here, likely LineEm, + # Token is problematic here as line is already colored, a + # and this changes the full style of the colored line. + # ideally, __line_content returns the token and we modify the style. + (Token, colored_line), + ] + else: + rlt = [ + bp, + (Token.Lineno, num), + (Token, " "), + # TODO: investigate Toke.Line here, likely Line + # Token is problematic here as line is already colored, a + # and this changes the full style of the colored line. + # ideally, __line_content returns the token and we modify the style. + (Token.Line, colored_line), + ] + ret_tok.extend(rlt) + + return self.theme.format(ret_tok) + + def __line_content( + self, filename: str, lineno: int, line: str, arrow: bool = False + ): bp_mark = "" - bp_mark_color = "" + BreakpointToken = Token.Breakpoint - new_line, err = self.parser.format2(line, 'str') + new_line, err = self.parser.format2(line, "str") if not err: + assert new_line is not None line = new_line bp = None @@ -446,111 +819,190 @@ def __format_line(self, tpl_line, filename, lineno, line, arrow = False): bp = bps[-1] if bp: - Colors = self.color_scheme_table.active_colors bp_mark = str(bp.number) - bp_mark_color = Colors.breakpoint_enabled + BreakpointToken = Token.Breakpoint.Enabled if not bp.enabled: - bp_mark_color = Colors.breakpoint_disabled - + BreakpointToken = Token.Breakpoint.Disabled numbers_width = 7 if arrow: # This is the line with the error pad = numbers_width - len(str(lineno)) - len(bp_mark) - num = '%s%s' % (make_arrow(pad), str(lineno)) + num = "{}{}".format(self.theme.make_arrow(pad), str(lineno)) else: - num = '%*s' % (numbers_width - len(bp_mark), str(lineno)) - - return tpl_line % (bp_mark_color + bp_mark, num, line) + num = "%*s" % (numbers_width - len(bp_mark), str(lineno)) + bp_str = (BreakpointToken, bp_mark) + return (bp_str, num, line) - - def print_list_lines(self, filename, first, last): + def print_list_lines(self, filename: str, first: int, last: int) -> None: """The printing (as opposed to the parsing part of a 'list' command.""" + toks: TokenStream = [] try: - Colors = self.color_scheme_table.active_colors - ColorsNormal = Colors.Normal - tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal) - tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal) - src = [] if filename == "" and hasattr(self, "_exec_filename"): filename = self._exec_filename - for lineno in range(first, last+1): + for lineno in range(first, last + 1): line = linecache.getline(filename, lineno) if not line: break + assert self.curframe is not None + if lineno == self.curframe.f_lineno: - line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True) + bp, num, colored_line = self.__line_content( + filename, lineno, line, arrow=True + ) + toks.extend( + [ + bp, + (Token.LinenoEm, num), + (Token, " "), + # TODO: investigate Token.Line here + (Token, colored_line), + ] + ) else: - line = self.__format_line(tpl_line, filename, lineno, line, arrow = False) + bp, num, colored_line = self.__line_content( + filename, lineno, line, arrow=False + ) + toks.extend( + [ + bp, + (Token.Lineno, num), + (Token, " "), + (Token, colored_line), + ] + ) - src.append(line) self.lineno = lineno - print(''.join(src)) + print(self.theme.format(toks), file=self.stdout) except KeyboardInterrupt: pass - def do_list(self, arg): - """Print lines of code from the current stack frame + def do_skip_predicates(self, args): + """ + Turn on/off individual predicates as to whether a frame should be hidden/skip. + + The global option to skip (or not) hidden frames is set with skip_hidden + + To change the value of a predicate + + skip_predicates key [true|false] + + Call without arguments to see the current values. + + To permanently change the value of an option add the corresponding + command to your ``~/.pdbrc`` file. If you are programmatically using the + Pdb instance you can also change the ``default_predicates`` class + attribute. """ - self.lastcmd = 'list' + if not args.strip(): + print("current predicates:") + for p, v in self._predicates.items(): + print(" ", p, ":", v) + return + type_value = args.strip().split(" ") + if len(type_value) != 2: + print( + f"Usage: skip_predicates , with one of {set(self._predicates.keys())}" + ) + return + + type_, value = type_value + if type_ not in self._predicates: + print(f"{type_!r} not in {set(self._predicates.keys())}") + return + if value.lower() not in ("true", "yes", "1", "no", "false", "0"): + print( + f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')" + ) + return + + self._predicates[type_] = value.lower() in ("true", "yes", "1") + if not any(self._predicates.values()): + print( + "Warning, all predicates set to False, skip_hidden may not have any effects." + ) + + def do_skip_hidden(self, arg): + """ + Change whether or not we should skip frames with the + __tracebackhide__ attribute. + """ + if not arg.strip(): + print( + f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change." + ) + elif arg.strip().lower() in ("true", "yes"): + self.skip_hidden = True + elif arg.strip().lower() in ("false", "no"): + self.skip_hidden = False + if not any(self._predicates.values()): + print( + "Warning, all predicates set to False, skip_hidden may not have any effects." + ) + + def do_list(self, arg): + """Print lines of code from the current stack frame""" + self.lastcmd = "list" last = None - if arg: + if arg and arg != ".": try: x = eval(arg, {}, {}) if type(x) == type(()): - first, last = x + first, last = x # type: ignore[misc] first = int(first) - last = int(last) + last = int(last) # type: ignore[call-overload] if last < first: # Assume it's a count last = first + last else: first = max(1, int(x) - 5) - except: - print('*** Error in argument:', repr(arg)) + except ValueError: + print("*** Error in argument:", repr(arg), file=self.stdout) return - elif self.lineno is None: + elif self.lineno is None or arg == ".": + assert self.curframe is not None first = max(1, self.curframe.f_lineno - 5) else: first = self.lineno + 1 if last is None: last = first + 10 + assert self.curframe is not None self.print_list_lines(self.curframe.f_code.co_filename, first, last) - # vds: >> lineno = first filename = self.curframe.f_code.co_filename self.shell.hooks.synchronize_with_editor(filename, lineno, 0) - # vds: << do_l = do_list def getsourcelines(self, obj): lines, lineno = inspect.findsource(obj) - if inspect.isframe(obj) and obj.f_globals is obj.f_locals: + if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj): # must be a module frame: do not try to cut a block out of it return lines, 1 elif inspect.ismodule(obj): return lines, 1 - return inspect.getblock(lines[lineno:]), lineno+1 + return inspect.getblock(lines[lineno:]), lineno + 1 def do_longlist(self, arg): """Print lines of code from the current stack frame. Shows more lines than 'list' does. """ - self.lastcmd = 'longlist' + self.lastcmd = "longlist" try: lines, lineno = self.getsourcelines(self.curframe) except OSError as err: - self.error(err) + self.error(str(err)) return last = lineno + len(lines) + assert self.curframe is not None self.print_list_lines(self.curframe.f_code.co_filename, lineno, last) + do_ll = do_longlist def do_debug(self, arg): @@ -559,67 +1011,88 @@ def do_debug(self, arg): argument (which is an arbitrary expression or statement to be executed in the current environment). """ + trace_function = sys.gettrace() sys.settrace(None) + assert self.curframe is not None globals = self.curframe.f_globals - locals = self.curframe_locals - p = self.__class__(completekey=self.completekey, - stdin=self.stdin, stdout=self.stdout) + locals = self._curframe_locals + p = self.__class__( + completekey=self.completekey, stdin=self.stdin, stdout=self.stdout + ) p.use_rawinput = self.use_rawinput p.prompt = "(%s) " % self.prompt.strip() self.message("ENTERING RECURSIVE DEBUGGER") sys.call_tracing(p.run, (arg, globals, locals)) self.message("LEAVING RECURSIVE DEBUGGER") - sys.settrace(self.trace_dispatch) + sys.settrace(trace_function) self.lastcmd = p.lastcmd def do_pdef(self, arg): """Print the call signature for any callable object. The debugger interface to %pdef""" - namespaces = [('Locals', self.curframe.f_locals), - ('Globals', self.curframe.f_globals)] - self.shell.find_line_magic('pdef')(arg, namespaces=namespaces) + assert self.curframe is not None + namespaces = [ + ("Locals", self._curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pdef")(arg, namespaces=namespaces) def do_pdoc(self, arg): """Print the docstring for an object. The debugger interface to %pdoc.""" - namespaces = [('Locals', self.curframe.f_locals), - ('Globals', self.curframe.f_globals)] - self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces) + assert self.curframe is not None + namespaces = [ + ("Locals", self._curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces) def do_pfile(self, arg): """Print (or run through pager) the file where an object is defined. The debugger interface to %pfile. """ - namespaces = [('Locals', self.curframe.f_locals), - ('Globals', self.curframe.f_globals)] - self.shell.find_line_magic('pfile')(arg, namespaces=namespaces) + assert self.curframe is not None + namespaces = [ + ("Locals", self._curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pfile")(arg, namespaces=namespaces) def do_pinfo(self, arg): """Provide detailed information about an object. The debugger interface to %pinfo, i.e., obj?.""" - namespaces = [('Locals', self.curframe.f_locals), - ('Globals', self.curframe.f_globals)] - self.shell.find_line_magic('pinfo')(arg, namespaces=namespaces) + assert self.curframe is not None + namespaces = [ + ("Locals", self._curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces) def do_pinfo2(self, arg): """Provide extra detailed information about an object. The debugger interface to %pinfo2, i.e., obj??.""" - namespaces = [('Locals', self.curframe.f_locals), - ('Globals', self.curframe.f_globals)] - self.shell.find_line_magic('pinfo2')(arg, namespaces=namespaces) + assert self.curframe is not None + namespaces = [ + ("Locals", self._curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces) def do_psource(self, arg): """Print (or run through pager) the source code for an object.""" - namespaces = [('Locals', self.curframe.f_locals), - ('Globals', self.curframe.f_globals)] - self.shell.find_line_magic('psource')(arg, namespaces=namespaces) - - def do_where(self, arg): + assert self.curframe is not None + namespaces = [ + ("Locals", self._curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("psource")(arg, namespaces=namespaces) + + def do_where(self, arg: str): """w(here) Print a stack trace, with the most recent frame at the bottom. An arrow indicates the "current frame", which determines the @@ -628,18 +1101,372 @@ def do_where(self, arg): Take a number as argument as an (optional) number of context line to print""" if arg: - context = int(arg) + try: + context = int(arg) + except ValueError as err: + self.error(str(err)) + return self.print_stack_trace(context) else: self.print_stack_trace() do_w = do_where + def break_anywhere(self, frame): + """ + _stop_in_decorator_internals is overly restrictive, as we may still want + to trace function calls, so we need to also update break_anywhere so + that is we don't `stop_here`, because of debugger skip, we may still + stop at any point inside the function + + """ + + sup = super().break_anywhere(frame) + if sup: + return sup + if self._predicates["debuggerskip"]: + if DEBUGGERSKIP in frame.f_code.co_varnames: + return True + if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): + return True + return False + + def _is_in_decorator_internal_and_should_skip(self, frame): + """ + Utility to tell us whether we are in a decorator internal and should stop. + + """ + # if we are disabled don't skip + if not self._predicates["debuggerskip"]: + return False + + return self._cachable_skip(frame) + + def _cached_one_parent_frame_debuggerskip(self, frame): + """ + Cache looking up for DEBUGGERSKIP on parent frame. + + This should speedup walking through deep frame when one of the highest + one does have a debugger skip. + + This is likely to introduce fake positive though. + """ + try: + return self._parent_skip_cache[frame] + except KeyError: + pass + result = None + current = frame + while getattr(current, "f_back", None): + current = current.f_back + if self._get_frame_locals(current).get(DEBUGGERSKIP): + result = True + break + self._parent_skip_cache[frame] = result + return result + + def _cachable_skip(self, frame): + # These caches used to be class-level ``lru_cache``\ s, which kept + # every debugger instance and up to 1024 frames (plus their locals and + # back-chains) alive for the lifetime of the process. They are now + # per-instance, size-bounded here, and cleared on each `interaction`. + if len(self._skip_cache) >= 1024: + self._skip_cache.clear() + self._parent_skip_cache.clear() + try: + return self._skip_cache[frame] + except KeyError: + pass + + # if frame is tagged, skip by default. + if DEBUGGERSKIP in frame.f_code.co_varnames: + result = True + else: + # if one of the parent frame value set to True skip as well. + result = bool(self._cached_one_parent_frame_debuggerskip(frame)) + + self._skip_cache[frame] = result + return result + + def stop_here(self, frame): + if self._is_in_decorator_internal_and_should_skip(frame) is True: + return False + + hidden = False + if self.skip_hidden: + hidden = self._hidden_predicate(frame) + if hidden: + if self.report_skipped: + print( + self.theme.format( + [ + ( + Token.ExcName, + " [... skipped 1 hidden frame(s)]", + ), + (Token, "\n"), + ] + ) + ) + if self.skip and self.is_skipped_module(frame.f_globals.get("__name__", "")): + print( + self.theme.format( + [ + ( + Token.ExcName, + " [... skipped 1 ignored module(s)]", + ), + (Token, "\n"), + ] + ) + ) + + return False + + return super().stop_here(frame) + + def do_up(self, arg): + """u(p) [count] + Move the current frame count (default one) levels up in the + stack trace (to an older frame). + + Will skip hidden frames and ignored modules. + """ + # modified version of upstream that skips + # frames with __tracebackhide__ and ignored modules + if self.curindex == 0: + self.error("Oldest frame") + return + try: + count = int(arg or 1) + except ValueError: + self.error("Invalid frame count (%s)" % arg) + return + + hidden_skipped = 0 + module_skipped = 0 + + if count < 0: + _newframe = 0 + else: + counter = 0 + hidden_frames = self.hidden_frames(self.stack) + + for i in range(self.curindex - 1, -1, -1): + should_skip_hidden = hidden_frames[i] and self.skip_hidden + should_skip_module = self.skip and self.is_skipped_module( + self.stack[i][0].f_globals.get("__name__", "") + ) + + if should_skip_hidden or should_skip_module: + if should_skip_hidden: + hidden_skipped += 1 + if should_skip_module: + module_skipped += 1 + continue + counter += 1 + if counter >= count: + break + else: + # if no break occurred. + self.error( + "all frames above skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules." + ) + return + + _newframe = i + self._select_frame(_newframe) + + total_skipped = hidden_skipped + module_skipped + if total_skipped: + print( + self.theme.format( + [ + ( + Token.ExcName, + f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]", + ), + (Token, "\n"), + ] + ) + ) + + def do_down(self, arg): + """d(own) [count] + Move the current frame count (default one) levels down in the + stack trace (to a newer frame). + + Will skip hidden frames and ignored modules. + """ + if self.curindex + 1 == len(self.stack): + self.error("Newest frame") + return + try: + count = int(arg or 1) + except ValueError: + self.error("Invalid frame count (%s)" % arg) + return + if count < 0: + _newframe = len(self.stack) - 1 + else: + counter = 0 + hidden_skipped = 0 + module_skipped = 0 + hidden_frames = self.hidden_frames(self.stack) + + for i in range(self.curindex + 1, len(self.stack)): + should_skip_hidden = hidden_frames[i] and self.skip_hidden + should_skip_module = self.skip and self.is_skipped_module( + self.stack[i][0].f_globals.get("__name__", "") + ) + + if should_skip_hidden or should_skip_module: + if should_skip_hidden: + hidden_skipped += 1 + if should_skip_module: + module_skipped += 1 + continue + counter += 1 + if counter >= count: + break + else: + self.error( + "all frames below skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules." + ) + return -def set_trace(frame=None): + total_skipped = hidden_skipped + module_skipped + if total_skipped: + print( + self.theme.format( + [ + ( + Token.ExcName, + f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]", + ), + (Token, "\n"), + ] + ) + ) + _newframe = i + + self._select_frame(_newframe) + + do_d = do_down + do_u = do_up + + def _show_ignored_modules(self): + """Display currently ignored modules.""" + if self.skip: + print(f"Currently ignored modules: {sorted(self.skip)}") + else: + print("No modules are currently ignored.") + + def do_ignore_module(self, arg): + """ignore_module + + Add a module to the list of modules to skip when navigating frames. + When a module is ignored, the debugger will automatically skip over + frames from that module. + + Supports wildcard patterns using fnmatch syntax: + + Usage: + ignore_module threading # Skip threading module frames + ignore_module asyncio.\\* # Skip all asyncio submodules + ignore_module \\*.tests # Skip all test modules + ignore_module # List currently ignored modules + """ + + if self.skip is None: + self.skip = set() + + module_name = arg.strip() + + if not module_name: + self._show_ignored_modules() + return + + self.skip.add(module_name) + + def do_unignore_module(self, arg): + """unignore_module + + Remove a module from the list of modules to skip when navigating frames. + This will allow the debugger to step into frames from the specified module. + + Usage: + unignore_module threading # Stop ignoring threading module frames + unignore_module asyncio.\\* # Remove asyncio.* pattern + unignore_module # List currently ignored modules + """ + + if self.skip is None: + self.skip = set() + + module_name = arg.strip() + + if not module_name: + self._show_ignored_modules() + return + + try: + self.skip.remove(module_name) + except KeyError: + print(f"Module {module_name} is not currently ignored") + self._show_ignored_modules() + + def do_context(self, context: str): + """context number_of_lines + Set the number of lines of source code to show when displaying + stacktrace information. + """ + try: + new_context = int(context) + if new_context <= 0: + raise ValueError() + self.context = new_context + except ValueError: + self.error( + f"The 'context' command requires a positive integer argument (current value {self.context})." + ) + + +class InterruptiblePdb(Pdb): + """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" + + def cmdloop(self, intro=None): + """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" + try: + return OldPdb.cmdloop(self, intro=intro) + except KeyboardInterrupt: + self.stop_here = lambda frame: False # type: ignore[method-assign] + self.do_quit("") + sys.settrace(None) + self.quitting = False + raise + + def _cmdloop(self): + while True: + try: + # keyboard interrupts allow for an easy way to cancel + # the current command, so allow them during interactive input + self.allow_kbdint = True + self.cmdloop() + self.allow_kbdint = False + break + except KeyboardInterrupt: + self.message("--KeyboardInterrupt--") + raise + + +def set_trace(frame=None, header=None): """ Start debugging from `frame`. If frame is not specified, debugging starts from caller's frame. """ - Pdb().set_trace(frame or sys._getframe().f_back) + pdb = Pdb() + if header is not None: + pdb.message(header) + pdb.set_trace(frame or sys._getframe().f_back) diff --git a/IPython/core/debugger_backport.py b/IPython/core/debugger_backport.py new file mode 100644 index 00000000000..cdc2167e627 --- /dev/null +++ b/IPython/core/debugger_backport.py @@ -0,0 +1,206 @@ +""" +The code in this module is a backport of cPython changes in Pdb +that were introduced in Python 3.13 by gh-83151: Make closure work on pdb +https://github.com/python/cpython/pull/111094. +This file should be removed once IPython drops supports for Python 3.12. + +The only changes are: +- reformatting by darker (black) formatter +- addition of type-ignore comments to satisfy mypy + +Copyright (c) 2001 Python Software Foundation; All Rights Reserved + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001 Python Software Foundation; All Rights Reserved" +are retained in Python alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. +""" + +import sys +import types +import codeop +import textwrap +from types import CodeType + + +class PdbClosureBackport: + def _exec_in_closure(self, source, globals, locals): # type: ignore[no-untyped-def] + """Run source code in closure so code object created within source + can find variables in locals correctly + returns True if the source is executed, False otherwise + """ + + # Determine if the source should be executed in closure. Only when the + # source compiled to multiple code objects, we should use this feature. + # Otherwise, we can just raise an exception and normal exec will be used. + + code = compile(source, "", "exec") + if not any(isinstance(const, CodeType) for const in code.co_consts): + return False + + # locals could be a proxy which does not support pop + # copy it first to avoid modifying the original locals + locals_copy = dict(locals) + + locals_copy["__pdb_eval__"] = {"result": None, "write_back": {}} + + # If the source is an expression, we need to print its value + try: + compile(source, "", "eval") + except SyntaxError: + pass + else: + source = "__pdb_eval__['result'] = " + source + + # Add write-back to update the locals + source = ( + "try:\n" + + textwrap.indent(source, " ") + + "\n" + + "finally:\n" + + " __pdb_eval__['write_back'] = locals()" + ) + + # Build a closure source code with freevars from locals like: + # def __pdb_outer(): + # var = None + # def __pdb_scope(): # This is the code object we want to execute + # nonlocal var + # + # return __pdb_scope.__code__ + source_with_closure = ( + "def __pdb_outer():\n" + + "\n".join(f" {var} = None" for var in locals_copy) + + "\n" + + " def __pdb_scope():\n" + + "\n".join(f" nonlocal {var}" for var in locals_copy) + + "\n" + + textwrap.indent(source, " ") + + "\n" + + " return __pdb_scope.__code__" + ) + + # Get the code object of __pdb_scope() + # The exec fills locals_copy with the __pdb_outer() function and we can call + # that to get the code object of __pdb_scope() + ns = {} + try: + exec(source_with_closure, {}, ns) + except Exception: + return False + code = ns["__pdb_outer"]() + + cells = tuple(types.CellType(locals_copy.get(var)) for var in code.co_freevars) + + try: + exec(code, globals, locals_copy, closure=cells) + except Exception: + return False + + # get the data we need from the statement + pdb_eval = locals_copy["__pdb_eval__"] + + # __pdb_eval__ should not be updated back to locals + pdb_eval["write_back"].pop("__pdb_eval__") + + # Write all local variables back to locals + locals.update(pdb_eval["write_back"]) + eval_result = pdb_eval["result"] + if eval_result is not None: + print(repr(eval_result)) + + return True + + def default(self, line): # type: ignore[no-untyped-def] + if line[:1] == "!": + line = line[1:].strip() + locals = self.curframe_locals + globals = self.curframe.f_globals + try: + buffer = line + if ( + code := codeop.compile_command(line + "\n", "", "single") + ) is None: + # Multi-line mode + with self._disable_command_completion(): + buffer = line + continue_prompt = "... " + while ( + code := codeop.compile_command(buffer, "", "single") + ) is None: + if self.use_rawinput: + try: + line = input(continue_prompt) + except (EOFError, KeyboardInterrupt): + self.lastcmd = "" + print("\n") + return + else: + self.stdout.write(continue_prompt) + self.stdout.flush() + line = self.stdin.readline() + if not len(line): + self.lastcmd = "" + self.stdout.write("\n") + self.stdout.flush() + return + else: + line = line.rstrip("\r\n") + buffer += "\n" + line + save_stdout = sys.stdout + save_stdin = sys.stdin + save_displayhook = sys.displayhook + try: + sys.stdin = self.stdin + sys.stdout = self.stdout + sys.displayhook = self.displayhook + if not self._exec_in_closure(buffer, globals, locals): + exec(code, globals, locals) + finally: + sys.stdout = save_stdout + sys.stdin = save_stdin + sys.displayhook = save_displayhook + except Exception: + self._error_exc() diff --git a/IPython/core/display.py b/IPython/core/display.py index ca5c3aa5b86..83098f55c55 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -1,29 +1,55 @@ -# -*- coding: utf-8 -*- """Top-level display functions for displaying object in different formats.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations -from binascii import b2a_hex, b2a_base64, hexlify -import json -import mimetypes +from enum import Enum +from dataclasses import dataclass, KW_ONLY +from binascii import b2a_base64, hexlify import os -import struct -import sys import warnings from copy import deepcopy +from os.path import splitext +from pathlib import Path, PurePath -from IPython.utils.py3compat import cast_unicode -from IPython.testing.skipdoctest import skip_doctest +from typing import TYPE_CHECKING, Self -__all__ = ['display', 'display_pretty', 'display_html', 'display_markdown', -'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json', -'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', -'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON', -'GeoJSON', 'Javascript', 'Image', 'clear_output', 'set_matplotlib_formats', -'set_matplotlib_close', 'publish_display_data', 'update_display', 'DisplayHandle', -'Video'] +from IPython.testing.skipdoctest import skip_doctest +from . import display_functions + +if TYPE_CHECKING: + from collections.abc import Callable + + +__all__ = [ + "display_pretty", + "display_html", + "display_markdown", + "display_svg", + "display_png", + "display_jpeg", + "display_webp", + "display_latex", + "display_json", + "display_javascript", + "display_pdf", + "DisplayObject", + "TextDisplayObject", + "Pretty", + "HTML", + "Markdown", + "Math", + "Latex", + "SVG", + "ProgressBar", + "JSON", + "GeoJSON", + "Javascript", + "Image", + "Video", +] #----------------------------------------------------------------------------- # utility functions @@ -36,17 +62,6 @@ def _safe_exists(path): except Exception: return False -def _merge(d1, d2): - """Like update, but merges sub-dicts instead of clobbering at the top level. - - Updates d1 in-place - """ - - if not isinstance(d2, dict) or not isinstance(d1, dict): - return d2 - for key, value in d2.items(): - d1[key] = _merge(d1.get(key), value) - return d1 def _display_mimetype(mimetype, objs, raw=False, metadata=None): """internal implementation of all display_foo methods @@ -55,7 +70,7 @@ def _display_mimetype(mimetype, objs, raw=False, metadata=None): ---------- mimetype : str The mimetype to be published (e.g. 'image/png') - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw text data to display. raw : bool @@ -69,321 +84,19 @@ def _display_mimetype(mimetype, objs, raw=False, metadata=None): if raw: # turn list of pngdata into list of { 'image/png': pngdata } objs = [ {mimetype: obj} for obj in objs ] - display(*objs, raw=raw, metadata=metadata, include=[mimetype]) + display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype]) #----------------------------------------------------------------------------- # Main functions #----------------------------------------------------------------------------- -# use * to indicate transient is keyword-only -def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs): - """Publish data and metadata to all frontends. - - See the ``display_data`` message in the messaging documentation for - more details about this message type. - - Keys of data and metadata can be any mime-type. - - Parameters - ---------- - data : dict - A dictionary having keys that are valid MIME types (like - 'text/plain' or 'image/svg+xml') and values that are the data for - that MIME type. The data itself must be a JSON'able data - structure. Minimally all data should have the 'text/plain' data, - which can be displayed by all frontends. If more than the plain - text is given, it is up to the frontend to decide which - representation to use. - metadata : dict - A dictionary for metadata related to the data. This can contain - arbitrary key, value pairs that frontends can use to interpret - the data. mime-type keys matching those in data can be used - to specify metadata about particular representations. - source : str, deprecated - Unused. - transient : dict, keyword-only - A dictionary of transient data, such as display_id. - """ - from IPython.core.interactiveshell import InteractiveShell - - display_pub = InteractiveShell.instance().display_pub - - # only pass transient if supplied, - # to avoid errors with older ipykernel. - # TODO: We could check for ipykernel version and provide a detailed upgrade message. - if transient: - kwargs['transient'] = transient - - display_pub.publish( - data=data, - metadata=metadata, - **kwargs - ) - - -def _new_id(): - """Generate a new random text id with urandom""" - return b2a_hex(os.urandom(16)).decode('ascii') - - -def display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, **kwargs): - """Display a Python object in all frontends. - - By default all representations will be computed and sent to the frontends. - Frontends can decide which representation is used and how. - - In terminal IPython this will be similar to using :func:`print`, for use in richer - frontends see Jupyter notebook examples with rich display logic. - - Parameters - ---------- - objs : tuple of objects - The Python objects to display. - raw : bool, optional - Are the objects to be displayed already mimetype-keyed dicts of raw display data, - or Python objects that need to be formatted before display? [default: False] - include : list, tuple or set, optional - A list of format type strings (MIME types) to include in the - format data dict. If this is set *only* the format types included - in this list will be computed. - exclude : list, tuple or set, optional - A list of format type strings (MIME types) to exclude in the format - data dict. If this is set all format types will be computed, - except for those included in this argument. - metadata : dict, optional - A dictionary of metadata to associate with the output. - mime-type keys in this dictionary will be associated with the individual - representation formats, if they exist. - transient : dict, optional - A dictionary of transient data to associate with the output. - Data in this dict should not be persisted to files (e.g. notebooks). - display_id : str, bool optional - Set an id for the display. - This id can be used for updating this display area later via update_display. - If given as `True`, generate a new `display_id` - kwargs: additional keyword-args, optional - Additional keyword-arguments are passed through to the display publisher. - - Returns - ------- - - handle: DisplayHandle - Returns a handle on updatable displays for use with :func:`update_display`, - if `display_id` is given. Returns :any:`None` if no `display_id` is given - (default). - - Examples - -------- - - >>> class Json(object): - ... def __init__(self, json): - ... self.json = json - ... def _repr_pretty_(self, pp, cycle): - ... import json - ... pp.text(json.dumps(self.json, indent=2)) - ... def __repr__(self): - ... return str(self.json) - ... - - >>> d = Json({1:2, 3: {4:5}}) - - >>> print(d) - {1: 2, 3: {4: 5}} - - >>> display(d) - { - "1": 2, - "3": { - "4": 5 - } - } - - >>> def int_formatter(integer, pp, cycle): - ... pp.text('I'*integer) - - >>> plain = get_ipython().display_formatter.formatters['text/plain'] - >>> plain.for_type(int, int_formatter) - - >>> display(7-5) - II - - >>> del plain.type_printers[int] - >>> display(7-5) - 2 - - See Also - -------- - - :func:`update_display` - - Notes - ----- - - In Python, objects can declare their textual representation using the - `__repr__` method. IPython expands on this idea and allows objects to declare - other, rich representations including: - - - HTML - - JSON - - PNG - - JPEG - - SVG - - LaTeX - - A single object can declare some or all of these representations; all are - handled by IPython's display system. - - The main idea of the first approach is that you have to implement special - display methods when you define your class, one for each representation you - want to use. Here is a list of the names of the special methods and the - values they must return: - - - `_repr_html_`: return raw HTML as a string - - `_repr_json_`: return a JSONable dict - - `_repr_jpeg_`: return raw JPEG data - - `_repr_png_`: return raw PNG data - - `_repr_svg_`: return raw SVG data as a string - - `_repr_latex_`: return LaTeX commands in a string surrounded by "$". - - `_repr_mimebundle_`: return a full mimebundle containing the mapping - from all mimetypes to data. - Use this for any mime-type not listed above. - - When you are directly writing your own classes, you can adapt them for - display in IPython by following the above approach. But in practice, you - often need to work with existing classes that you can't easily modify. - - You can refer to the documentation on integrating with the display system in - order to register custom formatters for already existing types - (:ref:`integrating_rich_display`). - - .. versionadded:: 5.4 display available without import - .. versionadded:: 6.1 display available without import - - Since IPython 5.4 and 6.1 :func:`display` is automatically made available to - the user without import. If you are using display in a document that might - be used in a pure python context or with older version of IPython, use the - following import at the top of your file:: - - from IPython.display import display - - """ - from IPython.core.interactiveshell import InteractiveShell - - if not InteractiveShell.initialized(): - # Directly print objects. - print(*objs) - return - - raw = kwargs.pop('raw', False) - if transient is None: - transient = {} - if metadata is None: - metadata={} - if display_id: - if display_id is True: - display_id = _new_id() - transient['display_id'] = display_id - if kwargs.get('update') and 'display_id' not in transient: - raise TypeError('display_id required for update_display') - if transient: - kwargs['transient'] = transient - - if not raw: - format = InteractiveShell.instance().display_formatter.format - - for obj in objs: - if raw: - publish_display_data(data=obj, metadata=metadata, **kwargs) - else: - format_dict, md_dict = format(obj, include=include, exclude=exclude) - if not format_dict: - # nothing to display (e.g. _ipython_display_ took over) - continue - if metadata: - # kwarg-specified metadata gets precedence - _merge(md_dict, metadata) - publish_display_data(data=format_dict, metadata=md_dict, **kwargs) - if display_id: - return DisplayHandle(display_id) - - -# use * for keyword-only display_id arg -def update_display(obj, *, display_id, **kwargs): - """Update an existing display by id - - Parameters - ---------- - - obj: - The object with which to update the display - display_id: keyword-only - The id of the display to update - - See Also - -------- - - :func:`display` - """ - kwargs['update'] = True - display(obj, display_id=display_id, **kwargs) - - -class DisplayHandle(object): - """A handle on an updatable display - - Call `.update(obj)` to display a new object. - - Call `.display(obj`) to add a new instance of this display, - and update existing instances. - - See Also - -------- - - :func:`display`, :func:`update_display` - - """ - - def __init__(self, display_id=None): - if display_id is None: - display_id = _new_id() - self.display_id = display_id - - def __repr__(self): - return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id) - - def display(self, obj, **kwargs): - """Make a new display with my id, updating existing instances. - - Parameters - ---------- - - obj: - object to display - **kwargs: - additional keyword arguments passed to display - """ - display(obj, display_id=self.display_id, **kwargs) - - def update(self, obj, **kwargs): - """Update existing displays with my id - - Parameters - ---------- - - obj: - object to display - **kwargs: - additional keyword arguments passed to update_display - """ - update_display(obj, display_id=self.display_id, **kwargs) - def display_pretty(*objs, **kwargs): """Display the pretty (default) representation of an object. Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw text data to display. raw : bool @@ -403,7 +116,7 @@ def display_html(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw HTML data to display. raw : bool @@ -420,7 +133,7 @@ def display_markdown(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw markdown data to display. raw : bool @@ -438,7 +151,7 @@ def display_svg(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw svg data to display. raw : bool @@ -455,7 +168,7 @@ def display_png(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw png data to display. raw : bool @@ -472,7 +185,7 @@ def display_jpeg(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw JPEG data to display. raw : bool @@ -484,12 +197,29 @@ def display_jpeg(*objs, **kwargs): _display_mimetype('image/jpeg', objs, **kwargs) +def display_webp(*objs, **kwargs): + """Display the WEBP representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw JPEG data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype("image/webp", objs, **kwargs) + + def display_latex(*objs, **kwargs): """Display the LaTeX representation of an object. Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw latex data to display. raw : bool @@ -508,7 +238,7 @@ def display_json(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw json data to display. raw : bool @@ -525,7 +255,7 @@ def display_javascript(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw javascript data to display. raw : bool @@ -542,7 +272,7 @@ def display_pdf(*objs, **kwargs): Parameters ---------- - objs : tuple of objects + *objs : object The Python objects to display, or if raw=True raw javascript data to display. raw : bool @@ -559,7 +289,7 @@ def display_pdf(*objs, **kwargs): #----------------------------------------------------------------------------- -class DisplayObject(object): +class DisplayObject: """An object that wraps data to be displayed.""" _read_flags = 'r' @@ -574,7 +304,7 @@ def __init__(self, data=None, url=None, filename=None, metadata=None): in the frontend. The MIME type of the data should match the subclasses used, so the Png subclass should be used for 'image/png' data. If the data is a URL, the data will first be downloaded - and then displayed. If + and then displayed. Parameters ---------- @@ -587,6 +317,9 @@ def __init__(self, data=None, url=None, filename=None, metadata=None): metadata : dict Dict of metadata associated to be the object when displayed """ + if isinstance(data, (Path, PurePath)): + data = str(data) + if data is not None and isinstance(data, str): if data.startswith('http') and url is None: url = data @@ -597,9 +330,12 @@ def __init__(self, data=None, url=None, filename=None, metadata=None): filename = data data = None - self.data = data self.url = url self.filename = filename + # because of @data.setter methods in + # subclasses ensure url and filename are set + # before assigning to self.data + self.data = data if metadata is not None: self.metadata = metadata @@ -612,9 +348,9 @@ def __init__(self, data=None, url=None, filename=None, metadata=None): def __repr__(self): if not self._show_mem_addr: cls = self.__class__ - r = "<%s.%s object>" % (cls.__module__, cls.__name__) + r = "<{}.{} object>".format(cls.__module__, cls.__name__) else: - r = super(DisplayObject, self).__repr__() + r = super().__repr__() return r def _check_data(self): @@ -631,32 +367,61 @@ def _data_and_metadata(self): def reload(self): """Reload the raw data from file or URL.""" if self.filename is not None: - with open(self.filename, self._read_flags) as f: + encoding = None if "b" in self._read_flags else "utf-8" + with open(self.filename, self._read_flags, encoding=encoding) as f: self.data = f.read() elif self.url is not None: - try: - # Deferred import - from urllib.request import urlopen - response = urlopen(self.url) - self.data = response.read() - # extract encoding from header, if there is one: - encoding = None + # Deferred import + from urllib.request import urlopen + response = urlopen(self.url) + data = response.read() + # extract encoding from header, if there is one: + encoding = None + if 'content-type' in response.headers: for sub in response.headers['content-type'].split(';'): sub = sub.strip() if sub.startswith('charset'): encoding = sub.split('=')[-1].strip() break - # decode data, if an encoding was specified - if encoding: - self.data = self.data.decode(encoding, 'replace') - except: - self.data = None + if 'content-encoding' in response.headers: + if 'gzip' in response.headers['content-encoding']: + import gzip + from io import BytesIO + + # assume utf-8 if encoding is not specified + with gzip.open( + BytesIO(data), "rt", encoding=encoding or "utf-8" + ) as fp: + encoding = None + data = fp.read() + + # decode data, if an encoding was specified + # We only touch self.data once since + # subclasses such as SVG have @data.setter methods + # that transform self.data into ... well svg. + if encoding: + self.data = data.decode(encoding, 'replace') + else: + self.data = data + class TextDisplayObject(DisplayObject): - """Validate that display data is text""" + """Create a text display object given raw data. + + Parameters + ---------- + data : str or unicode + The raw data or a URL or file to load the data from. + url : unicode + A URL to download the data from. + filename : unicode + Path to a local file to load the data from. + metadata : dict + Dict of metadata associated to be the object when displayed + """ def _check_data(self): if self.data is not None and not isinstance(self.data, str): - raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data)) + raise TypeError("{} expects text, not {!r}".format(self.__class__.__name__, self.data)) class Pretty(TextDisplayObject): @@ -666,6 +431,23 @@ def _repr_pretty_(self, pp, cycle): class HTML(TextDisplayObject): + def __init__(self, data=None, url=None, filename=None, metadata=None): + def warn(): + if not data: + return False + + # + # Avoid calling lower() on the entire data, because it could be a + # long string and we're only interested in its beginning and end. + # + prefix = data[:10].lower() + suffix = data[-10:].lower() + return prefix.startswith(" """ - def __init__(self, src, width, height, **kwargs): + def __init__( + self, src, width, height, extras: Iterable[str] | None = None, **kwargs + ): + if extras is None: + extras = [] + self.src = src self.width = width self.height = height + self.extras = extras self.params = kwargs def _repr_html_(self): """return the embed iframe""" + from html import escape as html_escape + if self.params: - try: - from urllib.parse import urlencode # Py 3 - except ImportError: - from urllib import urlencode + from urllib.parse import urlencode params = "?" + urlencode(self.params) else: params = "" - return self.iframe.format(src=self.src, - width=self.width, - height=self.height, - params=params) + return self.iframe.format( + src=html_escape(str(self.src)), + width=html_escape(str(self.width)), + height=html_escape(str(self.height)), + params=params, + extras=" ".join(self.extras), + ) + class YouTubeVideo(IFrame): """Class for embedding a YouTube Video in an IPython session, based on its video id. @@ -253,23 +327,26 @@ class YouTubeVideo(IFrame): Other parameters can be provided as documented at https://developers.google.com/youtube/player_parameters#Parameters - + When converting the notebook using nbconvert, a jpeg representation of the video will be inserted in the document. """ - def __init__(self, id, width=400, height=300, **kwargs): + def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs): self.id=id - src = "https://www.youtube.com/embed/{0}".format(id) - super(YouTubeVideo, self).__init__(src, width, height, **kwargs) - + src = f"https://www.youtube.com/embed/{id}" + if allow_autoplay: + extras = list(kwargs.get("extras", [])) + ['allow="autoplay"'] + kwargs.update(autoplay=1, extras=extras) + super().__init__(src, width, height, **kwargs) + def _repr_jpeg_(self): # Deferred import from urllib.request import urlopen try: - return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read() - except IOError: + return urlopen(f"https://img.youtube.com/vi/{self.id}/hqdefault.jpg").read() + except OSError: return None class VimeoVideo(IFrame): @@ -278,8 +355,8 @@ class VimeoVideo(IFrame): """ def __init__(self, id, width=400, height=300, **kwargs): - src="https://player.vimeo.com/video/{0}".format(id) - super(VimeoVideo, self).__init__(src, width, height, **kwargs) + src=f"https://player.vimeo.com/video/{id}" + super().__init__(src, width, height, **kwargs) class ScribdDocument(IFrame): """ @@ -294,10 +371,10 @@ class ScribdDocument(IFrame): """ def __init__(self, id, width=400, height=300, **kwargs): - src="https://www.scribd.com/embeds/{0}/content".format(id) - super(ScribdDocument, self).__init__(src, width, height, **kwargs) + src=f"https://www.scribd.com/embeds/{id}/content" + super().__init__(src, width, height, **kwargs) -class FileLink(object): +class FileLink: """Class for embedding a local file link in an IPython session, based on path e.g. to embed a link that was generated in the IPython notebook as my/data.txt @@ -322,7 +399,7 @@ def __init__(self, """ Parameters ---------- - path : str + path : str or os.PathLike path to the file or directory that should be formatted url_prefix : str prefix to be prepended to all files to form a working link [default: @@ -341,6 +418,8 @@ def __init__(self, self.result_html_suffix = result_html_suffix def _format_path(self): + from html import escape as html_escape + fp = ''.join([self.url_prefix, html_escape(self.path)]) return ''.join([self.result_html_prefix, self.html_link_str % \ @@ -427,7 +506,7 @@ def __init__(self, "Use FileLink to display '%s'." % path) self.included_suffixes = included_suffixes # remove trailing slashes for more consistent output formatting - path = path.rstrip('/') + path = fsdecode(path).rstrip("/") self.path = path self.url_prefix = url_prefix @@ -441,28 +520,40 @@ def __init__(self, self.recursive = recursive - def _get_display_formatter(self, - dirname_output_format, - fname_output_format, - fp_format, - fp_cleaner=None): - """ generate built-in formatter function - - this is used to define both the notebook and terminal built-in - formatters as they only differ by some wrapper text for each entry - - dirname_output_format: string to use for formatting directory - names, dirname will be substituted for a single "%s" which - must appear in this string - fname_output_format: string to use for formatting file names, - if a single "%s" appears in the string, fname will be substituted - if two "%s" appear in the string, the path to fname will be - substituted for the first and fname will be substituted for the - second - fp_format: string to use for formatting filepaths, must contain - exactly two "%s" and the dirname will be subsituted for the first - and fname will be substituted for the second + def _get_display_formatter( + self, + dirname_output_format, + fname_output_format, + fp_format, + fp_cleaner=None, + escape_names=False, + ): + """generate built-in formatter function + + this is used to define both the notebook and terminal built-in + formatters as they only differ by some wrapper text for each entry + + dirname_output_format: string to use for formatting directory + names, dirname will be substituted for a single "%s" which + must appear in this string + fname_output_format: string to use for formatting file names, + if a single "%s" appears in the string, fname will be substituted + if two "%s" appear in the string, the path to fname will be + substituted for the first and fname will be substituted for the + second + fp_format: string to use for formatting filepaths, must contain + exactly two "%s" and the dirname will be substituted for the first + and fname will be substituted for the second + escape_names: whether directory and file names must be HTML-escaped + before being substituted, as they are for the notebook formatter """ + if escape_names: + from html import escape as html_escape + + escape = html_escape + else: + escape = str + def f(dirname, fnames, included_suffixes=None): result = [] # begin by figuring out which filenames, if any, @@ -481,18 +572,18 @@ def f(dirname, fnames, included_suffixes=None): else: # otherwise print the formatted directory name followed by # the formatted filenames - dirname_output_line = dirname_output_format % dirname + dirname_output_line = dirname_output_format % escape(dirname) result.append(dirname_output_line) for fname in display_fnames: - fp = fp_format % (dirname,fname) + fp = fp_format % (escape(dirname), escape(fname)) if fp_cleaner is not None: fp = fp_cleaner(fp) try: # output can include both a filepath and a filename... - fname_output_line = fname_output_format % (fp, fname) + fname_output_line = fname_output_format % (fp, escape(fname)) except TypeError: # ... or just a single filepath - fname_output_line = fname_output_format % fname + fname_output_line = fname_output_format % escape(fname) result.append(fname_output_line) return result return f @@ -517,10 +608,13 @@ def fp_cleaner(fp): else: fp_cleaner = None - return self._get_display_formatter(dirname_output_format, - fname_output_format, - fp_format, - fp_cleaner) + return self._get_display_formatter( + dirname_output_format, + fname_output_format, + fp_format, + fp_cleaner, + escape_names=True, + ) def _get_terminal_display_formatter(self, spacer=" "): diff --git a/IPython/lib/editorhooks.py b/IPython/lib/editorhooks.py index 7ce0577a091..41d982798e3 100644 --- a/IPython/lib/editorhooks.py +++ b/IPython/lib/editorhooks.py @@ -6,14 +6,12 @@ """ import os -import pipes import shlex import subprocess import sys -from IPython import get_ipython +from IPython.core.getipython import get_ipython from IPython.core.error import TryNext -from IPython.utils import py3compat def install_editor(template, wait=False): @@ -26,7 +24,7 @@ def install_editor(template, wait=False): Parameters ---------- - template : basestring + template : str run_template acts as a template for how your editor is invoked by the shell. It should contain '{filename}', which will be replaced on invocation with the file name, and '{line}', $line by line number @@ -47,16 +45,16 @@ def install_editor(template, wait=False): def call_editor(self, filename, line=0): if line is None: line = 0 - cmd = template.format(filename=pipes.quote(filename), line=line) + cmd = template.format(filename=shlex.quote(filename), line=line) print(">", cmd) - # pipes.quote doesn't work right on Windows, but it does after splitting + # shlex.quote doesn't work right on Windows, but it does after splitting if sys.platform.startswith('win'): cmd = shlex.split(cmd) proc = subprocess.Popen(cmd, shell=True) if proc.wait() != 0: raise TryNext() if wait: - py3compat.input("Press Enter when done editing:") + input("Press Enter when done editing:") get_ipython().set_hook('editor', call_editor) get_ipython().editor = template @@ -64,27 +62,27 @@ def call_editor(self, filename, line=0): # in these, exe is always the path/name of the executable. Useful # if you don't have the editor directory in your path -def komodo(exe=u'komodo'): +def komodo(exe='komodo'): """ Activestate Komodo [Edit] """ - install_editor(exe + u' -l {line} {filename}', wait=True) + install_editor(exe + ' -l {line} {filename}', wait=True) -def scite(exe=u"scite"): +def scite(exe="scite"): """ SciTE or Sc1 """ - install_editor(exe + u' {filename} -goto:{line}') + install_editor(exe + ' {filename} -goto:{line}') -def notepadplusplus(exe=u'notepad++'): +def notepadplusplus(exe='notepad++'): """ Notepad++ http://notepad-plus.sourceforge.net """ - install_editor(exe + u' -n{line} {filename}') + install_editor(exe + ' -n{line} {filename}') -def jed(exe=u'jed'): +def jed(exe='jed'): """ JED, the lightweight emacsish editor """ - install_editor(exe + u' +{line} {filename}') + install_editor(exe + ' +{line} {filename}') -def idle(exe=u'idle'): +def idle(exe='idle'): """ Idle, the editor bundled with python Parameters @@ -98,13 +96,13 @@ def idle(exe=u'idle'): # i'm not sure if this actually works. Is this idle.py script # guaranteed to be executable? exe = os.path.join(p, 'idle.py') - install_editor(exe + u' {filename}') + install_editor(exe + ' {filename}') -def mate(exe=u'mate'): +def mate(exe='mate'): """ TextMate, the missing editor""" # wait=True is not required since we're using the -w flag to mate - install_editor(exe + u' -w -l {line} {filename}') + install_editor(exe + ' -w -l {line} {filename}') # ########################################## @@ -112,17 +110,17 @@ def mate(exe=u'mate'): # ########################################## -def emacs(exe=u'emacs'): - install_editor(exe + u' +{line} {filename}') +def emacs(exe='emacs'): + install_editor(exe + ' +{line} {filename}') -def gnuclient(exe=u'gnuclient'): - install_editor(exe + u' -nw +{line} {filename}') +def gnuclient(exe='gnuclient'): + install_editor(exe + ' -nw +{line} {filename}') -def crimson_editor(exe=u'cedt.exe'): - install_editor(exe + u' /L:{line} {filename}') +def crimson_editor(exe='cedt.exe'): + install_editor(exe + ' /L:{line} {filename}') -def kate(exe=u'kate'): - install_editor(exe + u' -u -l {line} {filename}') +def kate(exe='kate'): + install_editor(exe + ' -u -l {line} {filename}') diff --git a/IPython/lib/guisupport.py b/IPython/lib/guisupport.py index cfd325e9da8..a3d0f74944c 100644 --- a/IPython/lib/guisupport.py +++ b/IPython/lib/guisupport.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Support for creating GUI apps and starting event loops. @@ -106,21 +105,21 @@ def start_event_loop_wx(app=None): app._in_event_loop = True #----------------------------------------------------------------------------- -# qt4 +# Qt #----------------------------------------------------------------------------- def get_app_qt4(*args, **kwargs): - """Create a new qt4 app or return an existing one.""" + """Create a new Qt app or return an existing one.""" from IPython.external.qt_for_kernel import QtGui app = QtGui.QApplication.instance() if app is None: if not args: - args = ([''],) + args = ([""],) app = QtGui.QApplication(*args, **kwargs) return app def is_event_loop_running_qt4(app=None): - """Is the qt4 event loop running.""" + """Is the qt event loop running.""" # New way: check attribute on shell instance ip = get_ipython() if ip is not None: @@ -128,17 +127,17 @@ def is_event_loop_running_qt4(app=None): # Old way: check attribute on QApplication singleton if app is None: - app = get_app_qt4(['']) + app = get_app_qt4([""]) if hasattr(app, '_in_event_loop'): return app._in_event_loop else: - # Does qt4 provide a other way to detect this? + # Does qt provide a other way to detect this? return False def start_event_loop_qt4(app=None): - """Start the qt4 event loop in a consistent manner.""" + """Start the qt event loop in a consistent manner.""" if app is None: - app = get_app_qt4(['']) + app = get_app_qt4([""]) if not is_event_loop_running_qt4(app): app._in_event_loop = True app.exec_() diff --git a/IPython/lib/inputhook.py b/IPython/lib/inputhook.py deleted file mode 100644 index e6e8f2dbbc7..00000000000 --- a/IPython/lib/inputhook.py +++ /dev/null @@ -1,666 +0,0 @@ -# coding: utf-8 -""" -Deprecated since IPython 5.0 - -Inputhook management for GUI event loop integration. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -try: - import ctypes -except ImportError: - ctypes = None -except SystemError: # IronPython issue, 2/8/2014 - ctypes = None -import os -import platform -import sys -from distutils.version import LooseVersion as V - -from warnings import warn - - -warn("`IPython.lib.inputhook` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - - -#----------------------------------------------------------------------------- -# Constants -#----------------------------------------------------------------------------- - -# Constants for identifying the GUI toolkits. -GUI_WX = 'wx' -GUI_QT = 'qt' -GUI_QT4 = 'qt4' -GUI_GTK = 'gtk' -GUI_TK = 'tk' -GUI_OSX = 'osx' -GUI_GLUT = 'glut' -GUI_PYGLET = 'pyglet' -GUI_GTK3 = 'gtk3' -GUI_NONE = 'none' # i.e. disable - -#----------------------------------------------------------------------------- -# Utilities -#----------------------------------------------------------------------------- - -def _stdin_ready_posix(): - """Return True if there's something to read on stdin (posix version).""" - infds, outfds, erfds = select.select([sys.stdin],[],[],0) - return bool(infds) - -def _stdin_ready_nt(): - """Return True if there's something to read on stdin (nt version).""" - return msvcrt.kbhit() - -def _stdin_ready_other(): - """Return True, assuming there's something to read on stdin.""" - return True - -def _use_appnope(): - """Should we use appnope for dealing with OS X app nap? - - Checks if we are on OS X 10.9 or greater. - """ - return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9') - -def _ignore_CTRL_C_posix(): - """Ignore CTRL+C (SIGINT).""" - signal.signal(signal.SIGINT, signal.SIG_IGN) - -def _allow_CTRL_C_posix(): - """Take CTRL+C into account (SIGINT).""" - signal.signal(signal.SIGINT, signal.default_int_handler) - -def _ignore_CTRL_C_other(): - """Ignore CTRL+C (not implemented).""" - pass - -def _allow_CTRL_C_other(): - """Take CTRL+C into account (not implemented).""" - pass - -if os.name == 'posix': - import select - import signal - stdin_ready = _stdin_ready_posix - ignore_CTRL_C = _ignore_CTRL_C_posix - allow_CTRL_C = _allow_CTRL_C_posix -elif os.name == 'nt': - import msvcrt - stdin_ready = _stdin_ready_nt - ignore_CTRL_C = _ignore_CTRL_C_other - allow_CTRL_C = _allow_CTRL_C_other -else: - stdin_ready = _stdin_ready_other - ignore_CTRL_C = _ignore_CTRL_C_other - allow_CTRL_C = _allow_CTRL_C_other - - -#----------------------------------------------------------------------------- -# Main InputHookManager class -#----------------------------------------------------------------------------- - - -class InputHookManager(object): - """DEPRECATED since IPython 5.0 - - Manage PyOS_InputHook for different GUI toolkits. - - This class installs various hooks under ``PyOSInputHook`` to handle - GUI event loop integration. - """ - - def __init__(self): - if ctypes is None: - warn("IPython GUI event loop requires ctypes, %gui will not be available") - else: - self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int) - self.guihooks = {} - self.aliases = {} - self.apps = {} - self._reset() - - def _reset(self): - self._callback_pyfunctype = None - self._callback = None - self._installed = False - self._current_gui = None - - def get_pyos_inputhook(self): - """DEPRECATED since IPython 5.0 - - Return the current PyOS_InputHook as a ctypes.c_void_p.""" - warn("`get_pyos_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook") - - def get_pyos_inputhook_as_func(self): - """DEPRECATED since IPython 5.0 - - Return the current PyOS_InputHook as a ctypes.PYFUNCYPE.""" - warn("`get_pyos_inputhook_as_func` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook") - - def set_inputhook(self, callback): - """DEPRECATED since IPython 5.0 - - Set PyOS_InputHook to callback and return the previous one.""" - # On platforms with 'readline' support, it's all too likely to - # have a KeyboardInterrupt signal delivered *even before* an - # initial ``try:`` clause in the callback can be executed, so - # we need to disable CTRL+C in this situation. - ignore_CTRL_C() - self._callback = callback - self._callback_pyfunctype = self.PYFUNC(callback) - pyos_inputhook_ptr = self.get_pyos_inputhook() - original = self.get_pyos_inputhook_as_func() - pyos_inputhook_ptr.value = \ - ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value - self._installed = True - return original - - def clear_inputhook(self, app=None): - """DEPRECATED since IPython 5.0 - - Set PyOS_InputHook to NULL and return the previous one. - - Parameters - ---------- - app : optional, ignored - This parameter is allowed only so that clear_inputhook() can be - called with a similar interface as all the ``enable_*`` methods. But - the actual value of the parameter is ignored. This uniform interface - makes it easier to have user-level entry points in the main IPython - app like :meth:`enable_gui`.""" - warn("`clear_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - pyos_inputhook_ptr = self.get_pyos_inputhook() - original = self.get_pyos_inputhook_as_func() - pyos_inputhook_ptr.value = ctypes.c_void_p(None).value - allow_CTRL_C() - self._reset() - return original - - def clear_app_refs(self, gui=None): - """DEPRECATED since IPython 5.0 - - Clear IPython's internal reference to an application instance. - - Whenever we create an app for a user on qt4 or wx, we hold a - reference to the app. This is needed because in some cases bad things - can happen if a user doesn't hold a reference themselves. This - method is provided to clear the references we are holding. - - Parameters - ---------- - gui : None or str - If None, clear all app references. If ('wx', 'qt4') clear - the app for that toolkit. References are not held for gtk or tk - as those toolkits don't have the notion of an app. - """ - warn("`clear_app_refs` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if gui is None: - self.apps = {} - elif gui in self.apps: - del self.apps[gui] - - def register(self, toolkitname, *aliases): - """DEPRECATED since IPython 5.0 - - Register a class to provide the event loop for a given GUI. - - This is intended to be used as a class decorator. It should be passed - the names with which to register this GUI integration. The classes - themselves should subclass :class:`InputHookBase`. - - :: - - @inputhook_manager.register('qt') - class QtInputHook(InputHookBase): - def enable(self, app=None): - ... - """ - warn("`register` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - def decorator(cls): - if ctypes is not None: - inst = cls(self) - self.guihooks[toolkitname] = inst - for a in aliases: - self.aliases[a] = toolkitname - return cls - return decorator - - def current_gui(self): - """DEPRECATED since IPython 5.0 - - Return a string indicating the currently active GUI or None.""" - warn("`current_gui` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - return self._current_gui - - def enable_gui(self, gui=None, app=None): - """DEPRECATED since IPython 5.0 - - Switch amongst GUI input hooks by name. - - This is a higher level method than :meth:`set_inputhook` - it uses the - GUI name to look up a registered object which enables the input hook - for that GUI. - - Parameters - ---------- - gui : optional, string or None - If None (or 'none'), clears input hook, otherwise it must be one - of the recognized GUI names (see ``GUI_*`` constants in module). - - app : optional, existing application object. - For toolkits that have the concept of a global app, you can supply an - existing one. If not given, the toolkit will be probed for one, and if - none is found, a new one will be created. Note that GTK does not have - this concept, and passing an app if ``gui=="GTK"`` will raise an error. - - Returns - ------- - The output of the underlying gui switch routine, typically the actual - PyOS_InputHook wrapper object or the GUI toolkit app created, if there was - one. - """ - warn("`enable_gui` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if gui in (None, GUI_NONE): - return self.disable_gui() - - if gui in self.aliases: - return self.enable_gui(self.aliases[gui], app) - - try: - gui_hook = self.guihooks[gui] - except KeyError: - e = "Invalid GUI request {!r}, valid ones are: {}" - raise ValueError(e.format(gui, ', '.join(self.guihooks))) - self._current_gui = gui - - app = gui_hook.enable(app) - if app is not None: - app._in_event_loop = True - self.apps[gui] = app - return app - - def disable_gui(self): - """DEPRECATED since IPython 5.0 - - Disable GUI event loop integration. - - If an application was registered, this sets its ``_in_event_loop`` - attribute to False. It then calls :meth:`clear_inputhook`. - """ - warn("`disable_gui` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - gui = self._current_gui - if gui in self.apps: - self.apps[gui]._in_event_loop = False - return self.clear_inputhook() - -class InputHookBase(object): - """DEPRECATED since IPython 5.0 - - Base class for input hooks for specific toolkits. - - Subclasses should define an :meth:`enable` method with one argument, ``app``, - which will either be an instance of the toolkit's application class, or None. - They may also define a :meth:`disable` method with no arguments. - """ - def __init__(self, manager): - self.manager = manager - - def disable(self): - pass - -inputhook_manager = InputHookManager() - -@inputhook_manager.register('osx') -class NullInputHook(InputHookBase): - """DEPRECATED since IPython 5.0 - - A null inputhook that doesn't need to do anything""" - def enable(self, app=None): - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - -@inputhook_manager.register('wx') -class WxInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with wxPython. - - Parameters - ---------- - app : WX Application, optional. - Running application to use. If not given, we probe WX for an - existing application object, and create a new one if none is found. - - Notes - ----- - This methods sets the ``PyOS_InputHook`` for wxPython, which allows - the wxPython to integrate with terminal based applications like - IPython. - - If ``app`` is not given we probe for an existing one, and return it if - found. If no existing app is found, we create an :class:`wx.App` as - follows:: - - import wx - app = wx.App(redirect=False, clearSigInt=False) - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - import wx - - wx_version = V(wx.__version__).version - - if wx_version < [2, 8]: - raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__) - - from IPython.lib.inputhookwx import inputhook_wx - self.manager.set_inputhook(inputhook_wx) - if _use_appnope(): - from appnope import nope - nope() - - import wx - if app is None: - app = wx.GetApp() - if app is None: - app = wx.App(redirect=False, clearSigInt=False) - - return app - - def disable(self): - """DEPRECATED since IPython 5.0 - - Disable event loop integration with wxPython. - - This restores appnapp on OS X - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if _use_appnope(): - from appnope import nap - nap() - -@inputhook_manager.register('qt', 'qt4') -class Qt4InputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with PyQt4. - - Parameters - ---------- - app : Qt Application, optional. - Running application to use. If not given, we probe Qt for an - existing application object, and create a new one if none is found. - - Notes - ----- - This methods sets the PyOS_InputHook for PyQt4, which allows - the PyQt4 to integrate with terminal based applications like - IPython. - - If ``app`` is not given we probe for an existing one, and return it if - found. If no existing app is found, we create an :class:`QApplication` - as follows:: - - from PyQt4 import QtCore - app = QtGui.QApplication(sys.argv) - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - from IPython.lib.inputhookqt4 import create_inputhook_qt4 - app, inputhook_qt4 = create_inputhook_qt4(self.manager, app) - self.manager.set_inputhook(inputhook_qt4) - if _use_appnope(): - from appnope import nope - nope() - - return app - - def disable_qt4(self): - """DEPRECATED since IPython 5.0 - - Disable event loop integration with PyQt4. - - This restores appnapp on OS X - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if _use_appnope(): - from appnope import nap - nap() - - -@inputhook_manager.register('qt5') -class Qt5InputHook(Qt4InputHook): - def enable(self, app=None): - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - os.environ['QT_API'] = 'pyqt5' - return Qt4InputHook.enable(self, app) - - -@inputhook_manager.register('gtk') -class GtkInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with PyGTK. - - Parameters - ---------- - app : ignored - Ignored, it's only a placeholder to keep the call signature of all - gui activation methods consistent, which simplifies the logic of - supporting magics. - - Notes - ----- - This methods sets the PyOS_InputHook for PyGTK, which allows - the PyGTK to integrate with terminal based applications like - IPython. - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - import gtk - try: - gtk.set_interactive(True) - except AttributeError: - # For older versions of gtk, use our own ctypes version - from IPython.lib.inputhookgtk import inputhook_gtk - self.manager.set_inputhook(inputhook_gtk) - - -@inputhook_manager.register('tk') -class TkInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with Tk. - - Parameters - ---------- - app : toplevel :class:`Tkinter.Tk` widget, optional. - Running toplevel widget to use. If not given, we probe Tk for an - existing one, and create a new one if none is found. - - Notes - ----- - If you have already created a :class:`Tkinter.Tk` object, the only - thing done by this method is to register with the - :class:`InputHookManager`, since creating that object automatically - sets ``PyOS_InputHook``. - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if app is None: - try: - from tkinter import Tk # Py 3 - except ImportError: - from Tkinter import Tk # Py 2 - app = Tk() - app.withdraw() - self.manager.apps[GUI_TK] = app - return app - - -@inputhook_manager.register('glut') -class GlutInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with GLUT. - - Parameters - ---------- - - app : ignored - Ignored, it's only a placeholder to keep the call signature of all - gui activation methods consistent, which simplifies the logic of - supporting magics. - - Notes - ----- - - This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to - integrate with terminal based applications like IPython. Due to GLUT - limitations, it is currently not possible to start the event loop - without first creating a window. You should thus not create another - window but use instead the created one. See 'gui-glut.py' in the - docs/examples/lib directory. - - The default screen mode is set to: - glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - - import OpenGL.GLUT as glut - from IPython.lib.inputhookglut import glut_display_mode, \ - glut_close, glut_display, \ - glut_idle, inputhook_glut - - if GUI_GLUT not in self.manager.apps: - glut.glutInit( sys.argv ) - glut.glutInitDisplayMode( glut_display_mode ) - # This is specific to freeglut - if bool(glut.glutSetOption): - glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE, - glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS ) - glut.glutCreateWindow( sys.argv[0] ) - glut.glutReshapeWindow( 1, 1 ) - glut.glutHideWindow( ) - glut.glutWMCloseFunc( glut_close ) - glut.glutDisplayFunc( glut_display ) - glut.glutIdleFunc( glut_idle ) - else: - glut.glutWMCloseFunc( glut_close ) - glut.glutDisplayFunc( glut_display ) - glut.glutIdleFunc( glut_idle) - self.manager.set_inputhook( inputhook_glut ) - - - def disable(self): - """DEPRECATED since IPython 5.0 - - Disable event loop integration with glut. - - This sets PyOS_InputHook to NULL and set the display function to a - dummy one and set the timer to a dummy timer that will be triggered - very far in the future. - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - import OpenGL.GLUT as glut - from glut_support import glutMainLoopEvent - - glut.glutHideWindow() # This is an event to be processed below - glutMainLoopEvent() - super(GlutInputHook, self).disable() - -@inputhook_manager.register('pyglet') -class PygletInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with pyglet. - - Parameters - ---------- - app : ignored - Ignored, it's only a placeholder to keep the call signature of all - gui activation methods consistent, which simplifies the logic of - supporting magics. - - Notes - ----- - This methods sets the ``PyOS_InputHook`` for pyglet, which allows - pyglet to integrate with terminal based applications like - IPython. - - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - from IPython.lib.inputhookpyglet import inputhook_pyglet - self.manager.set_inputhook(inputhook_pyglet) - return app - - -@inputhook_manager.register('gtk3') -class Gtk3InputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with Gtk3 (gir bindings). - - Parameters - ---------- - app : ignored - Ignored, it's only a placeholder to keep the call signature of all - gui activation methods consistent, which simplifies the logic of - supporting magics. - - Notes - ----- - This methods sets the PyOS_InputHook for Gtk3, which allows - the Gtk3 to integrate with terminal based applications like - IPython. - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - from IPython.lib.inputhookgtk3 import inputhook_gtk3 - self.manager.set_inputhook(inputhook_gtk3) - - -clear_inputhook = inputhook_manager.clear_inputhook -set_inputhook = inputhook_manager.set_inputhook -current_gui = inputhook_manager.current_gui -clear_app_refs = inputhook_manager.clear_app_refs -enable_gui = inputhook_manager.enable_gui -disable_gui = inputhook_manager.disable_gui -register = inputhook_manager.register -guis = inputhook_manager.guihooks - - -def _deprecated_disable(): - warn("This function is deprecated since IPython 4.0 use disable_gui() instead", - DeprecationWarning, stacklevel=2) - inputhook_manager.disable_gui() - -disable_wx = disable_qt4 = disable_gtk = disable_gtk3 = disable_glut = \ - disable_pyglet = disable_osx = _deprecated_disable diff --git a/IPython/lib/inputhookglut.py b/IPython/lib/inputhookglut.py deleted file mode 100644 index e6f7f125758..00000000000 --- a/IPython/lib/inputhookglut.py +++ /dev/null @@ -1,172 +0,0 @@ -# coding: utf-8 -""" -GLUT Inputhook support functions -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -# GLUT is quite an old library and it is difficult to ensure proper -# integration within IPython since original GLUT does not allow to handle -# events one by one. Instead, it requires for the mainloop to be entered -# and never returned (there is not even a function to exit he -# mainloop). Fortunately, there are alternatives such as freeglut -# (available for linux and windows) and the OSX implementation gives -# access to a glutCheckLoop() function that blocks itself until a new -# event is received. This means we have to setup the idle callback to -# ensure we got at least one event that will unblock the function. -# -# Furthermore, it is not possible to install these handlers without a window -# being first created. We choose to make this window invisible. This means that -# display mode options are set at this level and user won't be able to change -# them later without modifying the code. This should probably be made available -# via IPython options system. - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- -import os -import sys -import time -import signal -import OpenGL.GLUT as glut -import OpenGL.platform as platform -from timeit import default_timer as clock - -#----------------------------------------------------------------------------- -# Constants -#----------------------------------------------------------------------------- - -# Frame per second : 60 -# Should probably be an IPython option -glut_fps = 60 - - -# Display mode : double buffeed + rgba + depth -# Should probably be an IPython option -glut_display_mode = (glut.GLUT_DOUBLE | - glut.GLUT_RGBA | - glut.GLUT_DEPTH) - -glutMainLoopEvent = None -if sys.platform == 'darwin': - try: - glutCheckLoop = platform.createBaseFunction( - 'glutCheckLoop', dll=platform.GLUT, resultType=None, - argTypes=[], - doc='glutCheckLoop( ) -> None', - argNames=(), - ) - except AttributeError: - raise RuntimeError( - '''Your glut implementation does not allow interactive sessions''' - '''Consider installing freeglut.''') - glutMainLoopEvent = glutCheckLoop -elif glut.HAVE_FREEGLUT: - glutMainLoopEvent = glut.glutMainLoopEvent -else: - raise RuntimeError( - '''Your glut implementation does not allow interactive sessions. ''' - '''Consider installing freeglut.''') - - -#----------------------------------------------------------------------------- -# Platform-dependent imports and functions -#----------------------------------------------------------------------------- - -if os.name == 'posix': - import select - - def stdin_ready(): - infds, outfds, erfds = select.select([sys.stdin],[],[],0) - if infds: - return True - else: - return False - -elif sys.platform == 'win32': - import msvcrt - - def stdin_ready(): - return msvcrt.kbhit() - -#----------------------------------------------------------------------------- -# Callback functions -#----------------------------------------------------------------------------- - -def glut_display(): - # Dummy display function - pass - -def glut_idle(): - # Dummy idle function - pass - -def glut_close(): - # Close function only hides the current window - glut.glutHideWindow() - glutMainLoopEvent() - -def glut_int_handler(signum, frame): - # Catch sigint and print the default message - signal.signal(signal.SIGINT, signal.default_int_handler) - print('\nKeyboardInterrupt') - # Need to reprint the prompt at this stage - - - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- -def inputhook_glut(): - """Run the pyglet event loop by processing pending events only. - - This keeps processing pending events until stdin is ready. After - processing all pending events, a call to time.sleep is inserted. This is - needed, otherwise, CPU usage is at 100%. This sleep time should be tuned - though for best performance. - """ - # We need to protect against a user pressing Control-C when IPython is - # idle and this is running. We trap KeyboardInterrupt and pass. - - signal.signal(signal.SIGINT, glut_int_handler) - - try: - t = clock() - - # Make sure the default window is set after a window has been closed - if glut.glutGetWindow() == 0: - glut.glutSetWindow( 1 ) - glutMainLoopEvent() - return 0 - - while not stdin_ready(): - glutMainLoopEvent() - # We need to sleep at this point to keep the idle CPU load - # low. However, if sleep to long, GUI response is poor. As - # a compromise, we watch how often GUI events are being processed - # and switch between a short and long sleep time. Here are some - # stats useful in helping to tune this. - # time CPU load - # 0.001 13% - # 0.005 3% - # 0.01 1.5% - # 0.05 0.5% - used_time = clock() - t - if used_time > 10.0: - # print 'Sleep for 1 s' # dbg - time.sleep(1.0) - elif used_time > 0.1: - # Few GUI events coming in, so we can sleep longer - # print 'Sleep for 0.05 s' # dbg - time.sleep(0.05) - else: - # Many GUI events coming in, so sleep only very little - time.sleep(0.001) - except KeyboardInterrupt: - pass - return 0 diff --git a/IPython/lib/inputhookgtk.py b/IPython/lib/inputhookgtk.py deleted file mode 100644 index 2b4b656f91a..00000000000 --- a/IPython/lib/inputhookgtk.py +++ /dev/null @@ -1,35 +0,0 @@ -# encoding: utf-8 -""" -Enable pygtk to be used interacive by setting PyOS_InputHook. - -Authors: Brian Granger -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import sys -import gtk, gobject - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - - -def _main_quit(*args, **kwargs): - gtk.main_quit() - return False - -def inputhook_gtk(): - gobject.io_add_watch(sys.stdin, gobject.IO_IN, _main_quit) - gtk.main() - return 0 - diff --git a/IPython/lib/inputhookgtk3.py b/IPython/lib/inputhookgtk3.py deleted file mode 100644 index d897e193fe5..00000000000 --- a/IPython/lib/inputhookgtk3.py +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: utf-8 -""" -Enable Gtk3 to be used interacive by IPython. - -Authors: Thomi Richards -""" -#----------------------------------------------------------------------------- -# Copyright (c) 2012, the IPython Development Team. -# -# Distributed under the terms of the Modified BSD License. -# -# The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import sys -from gi.repository import Gtk, GLib - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -def _main_quit(*args, **kwargs): - Gtk.main_quit() - return False - - -def inputhook_gtk3(): - GLib.io_add_watch(sys.stdin, GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit) - Gtk.main() - return 0 diff --git a/IPython/lib/inputhookpyglet.py b/IPython/lib/inputhookpyglet.py deleted file mode 100644 index b82fcf5ea7c..00000000000 --- a/IPython/lib/inputhookpyglet.py +++ /dev/null @@ -1,111 +0,0 @@ -# encoding: utf-8 -""" -Enable pyglet to be used interacive by setting PyOS_InputHook. - -Authors -------- - -* Nicolas P. Rougier -* Fernando Perez -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import os -import sys -import time -from timeit import default_timer as clock -import pyglet - -#----------------------------------------------------------------------------- -# Platform-dependent imports and functions -#----------------------------------------------------------------------------- - -if os.name == 'posix': - import select - - def stdin_ready(): - infds, outfds, erfds = select.select([sys.stdin],[],[],0) - if infds: - return True - else: - return False - -elif sys.platform == 'win32': - import msvcrt - - def stdin_ready(): - return msvcrt.kbhit() - - -# On linux only, window.flip() has a bug that causes an AttributeError on -# window close. For details, see: -# http://groups.google.com/group/pyglet-users/browse_thread/thread/47c1aab9aa4a3d23/c22f9e819826799e?#c22f9e819826799e - -if sys.platform.startswith('linux'): - def flip(window): - try: - window.flip() - except AttributeError: - pass -else: - def flip(window): - window.flip() - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -def inputhook_pyglet(): - """Run the pyglet event loop by processing pending events only. - - This keeps processing pending events until stdin is ready. After - processing all pending events, a call to time.sleep is inserted. This is - needed, otherwise, CPU usage is at 100%. This sleep time should be tuned - though for best performance. - """ - # We need to protect against a user pressing Control-C when IPython is - # idle and this is running. We trap KeyboardInterrupt and pass. - try: - t = clock() - while not stdin_ready(): - pyglet.clock.tick() - for window in pyglet.app.windows: - window.switch_to() - window.dispatch_events() - window.dispatch_event('on_draw') - flip(window) - - # We need to sleep at this point to keep the idle CPU load - # low. However, if sleep to long, GUI response is poor. As - # a compromise, we watch how often GUI events are being processed - # and switch between a short and long sleep time. Here are some - # stats useful in helping to tune this. - # time CPU load - # 0.001 13% - # 0.005 3% - # 0.01 1.5% - # 0.05 0.5% - used_time = clock() - t - if used_time > 10.0: - # print 'Sleep for 1 s' # dbg - time.sleep(1.0) - elif used_time > 0.1: - # Few GUI events coming in, so we can sleep longer - # print 'Sleep for 0.05 s' # dbg - time.sleep(0.05) - else: - # Many GUI events coming in, so sleep only very little - time.sleep(0.001) - except KeyboardInterrupt: - pass - return 0 diff --git a/IPython/lib/inputhookqt4.py b/IPython/lib/inputhookqt4.py deleted file mode 100644 index 8a83902fc0e..00000000000 --- a/IPython/lib/inputhookqt4.py +++ /dev/null @@ -1,180 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Qt4's inputhook support function - -Author: Christian Boos -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import os -import signal -import threading - -from IPython.core.interactiveshell import InteractiveShell -from IPython.external.qt_for_kernel import QtCore, QtGui -from IPython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready - -#----------------------------------------------------------------------------- -# Module Globals -#----------------------------------------------------------------------------- - -got_kbdint = False -sigint_timer = None - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -def create_inputhook_qt4(mgr, app=None): - """Create an input hook for running the Qt4 application event loop. - - Parameters - ---------- - mgr : an InputHookManager - - app : Qt Application, optional. - Running application to use. If not given, we probe Qt for an - existing application object, and create a new one if none is found. - - Returns - ------- - A pair consisting of a Qt Application (either the one given or the - one found or created) and a inputhook. - - Notes - ----- - We use a custom input hook instead of PyQt4's default one, as it - interacts better with the readline packages (issue #481). - - The inputhook function works in tandem with a 'pre_prompt_hook' - which automatically restores the hook as an inputhook in case the - latter has been temporarily disabled after having intercepted a - KeyboardInterrupt. - """ - - if app is None: - app = QtCore.QCoreApplication.instance() - if app is None: - app = QtGui.QApplication([" "]) - - # Re-use previously created inputhook if any - ip = InteractiveShell.instance() - if hasattr(ip, '_inputhook_qt4'): - return app, ip._inputhook_qt4 - - # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of - # hooks (they both share the got_kbdint flag) - - def inputhook_qt4(): - """PyOS_InputHook python hook for Qt4. - - Process pending Qt events and if there's no pending keyboard - input, spend a short slice of time (50ms) running the Qt event - loop. - - As a Python ctypes callback can't raise an exception, we catch - the KeyboardInterrupt and temporarily deactivate the hook, - which will let a *second* CTRL+C be processed normally and go - back to a clean prompt line. - """ - try: - allow_CTRL_C() - app = QtCore.QCoreApplication.instance() - if not app: # shouldn't happen, but safer if it happens anyway... - return 0 - app.processEvents(QtCore.QEventLoop.AllEvents, 300) - if not stdin_ready(): - # Generally a program would run QCoreApplication::exec() - # from main() to enter and process the Qt event loop until - # quit() or exit() is called and the program terminates. - # - # For our input hook integration, we need to repeatedly - # enter and process the Qt event loop for only a short - # amount of time (say 50ms) to ensure that Python stays - # responsive to other user inputs. - # - # A naive approach would be to repeatedly call - # QCoreApplication::exec(), using a timer to quit after a - # short amount of time. Unfortunately, QCoreApplication - # emits an aboutToQuit signal before stopping, which has - # the undesirable effect of closing all modal windows. - # - # To work around this problem, we instead create a - # QEventLoop and call QEventLoop::exec(). Other than - # setting some state variables which do not seem to be - # used anywhere, the only thing QCoreApplication adds is - # the aboutToQuit signal which is precisely what we are - # trying to avoid. - timer = QtCore.QTimer() - event_loop = QtCore.QEventLoop() - timer.timeout.connect(event_loop.quit) - while not stdin_ready(): - timer.start(50) - event_loop.exec_() - timer.stop() - except KeyboardInterrupt: - global got_kbdint, sigint_timer - - ignore_CTRL_C() - got_kbdint = True - mgr.clear_inputhook() - - # This generates a second SIGINT so the user doesn't have to - # press CTRL+C twice to get a clean prompt. - # - # Since we can't catch the resulting KeyboardInterrupt here - # (because this is a ctypes callback), we use a timer to - # generate the SIGINT after we leave this callback. - # - # Unfortunately this doesn't work on Windows (SIGINT kills - # Python and CTRL_C_EVENT doesn't work). - if(os.name == 'posix'): - pid = os.getpid() - if(not sigint_timer): - sigint_timer = threading.Timer(.01, os.kill, - args=[pid, signal.SIGINT] ) - sigint_timer.start() - else: - print("\nKeyboardInterrupt - Ctrl-C again for new prompt") - - - except: # NO exceptions are allowed to escape from a ctypes callback - ignore_CTRL_C() - from traceback import print_exc - print_exc() - print("Got exception from inputhook_qt4, unregistering.") - mgr.clear_inputhook() - finally: - allow_CTRL_C() - return 0 - - def preprompthook_qt4(ishell): - """'pre_prompt_hook' used to restore the Qt4 input hook - - (in case the latter was temporarily deactivated after a - CTRL+C) - """ - global got_kbdint, sigint_timer - - if(sigint_timer): - sigint_timer.cancel() - sigint_timer = None - - if got_kbdint: - mgr.set_inputhook(inputhook_qt4) - got_kbdint = False - - ip._inputhook_qt4 = inputhook_qt4 - ip.set_hook('pre_prompt_hook', preprompthook_qt4) - - return app, inputhook_qt4 diff --git a/IPython/lib/inputhookwx.py b/IPython/lib/inputhookwx.py deleted file mode 100644 index 3aac5261315..00000000000 --- a/IPython/lib/inputhookwx.py +++ /dev/null @@ -1,167 +0,0 @@ -# encoding: utf-8 - -""" -Enable wxPython to be used interacive by setting PyOS_InputHook. - -Authors: Robin Dunn, Brian Granger, Ondrej Certik -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import sys -import signal -import time -from timeit import default_timer as clock -import wx - -from IPython.lib.inputhook import stdin_ready - - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -def inputhook_wx1(): - """Run the wx event loop by processing pending events only. - - This approach seems to work, but its performance is not great as it - relies on having PyOS_InputHook called regularly. - """ - try: - app = wx.GetApp() - if app is not None: - assert wx.Thread_IsMain() - - # Make a temporary event loop and process system events until - # there are no more waiting, then allow idle events (which - # will also deal with pending or posted wx events.) - evtloop = wx.EventLoop() - ea = wx.EventLoopActivator(evtloop) - while evtloop.Pending(): - evtloop.Dispatch() - app.ProcessIdle() - del ea - except KeyboardInterrupt: - pass - return 0 - -class EventLoopTimer(wx.Timer): - - def __init__(self, func): - self.func = func - wx.Timer.__init__(self) - - def Notify(self): - self.func() - -class EventLoopRunner(object): - - def Run(self, time): - self.evtloop = wx.EventLoop() - self.timer = EventLoopTimer(self.check_stdin) - self.timer.Start(time) - self.evtloop.Run() - - def check_stdin(self): - if stdin_ready(): - self.timer.Stop() - self.evtloop.Exit() - -def inputhook_wx2(): - """Run the wx event loop, polling for stdin. - - This version runs the wx eventloop for an undetermined amount of time, - during which it periodically checks to see if anything is ready on - stdin. If anything is ready on stdin, the event loop exits. - - The argument to elr.Run controls how often the event loop looks at stdin. - This determines the responsiveness at the keyboard. A setting of 1000 - enables a user to type at most 1 char per second. I have found that a - setting of 10 gives good keyboard response. We can shorten it further, - but eventually performance would suffer from calling select/kbhit too - often. - """ - try: - app = wx.GetApp() - if app is not None: - assert wx.Thread_IsMain() - elr = EventLoopRunner() - # As this time is made shorter, keyboard response improves, but idle - # CPU load goes up. 10 ms seems like a good compromise. - elr.Run(time=10) # CHANGE time here to control polling interval - except KeyboardInterrupt: - pass - return 0 - -def inputhook_wx3(): - """Run the wx event loop by processing pending events only. - - This is like inputhook_wx1, but it keeps processing pending events - until stdin is ready. After processing all pending events, a call to - time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%. - This sleep time should be tuned though for best performance. - """ - # We need to protect against a user pressing Control-C when IPython is - # idle and this is running. We trap KeyboardInterrupt and pass. - try: - app = wx.GetApp() - if app is not None: - assert wx.Thread_IsMain() - - # The import of wx on Linux sets the handler for signal.SIGINT - # to 0. This is a bug in wx or gtk. We fix by just setting it - # back to the Python default. - if not callable(signal.getsignal(signal.SIGINT)): - signal.signal(signal.SIGINT, signal.default_int_handler) - - evtloop = wx.EventLoop() - ea = wx.EventLoopActivator(evtloop) - t = clock() - while not stdin_ready(): - while evtloop.Pending(): - t = clock() - evtloop.Dispatch() - app.ProcessIdle() - # We need to sleep at this point to keep the idle CPU load - # low. However, if sleep to long, GUI response is poor. As - # a compromise, we watch how often GUI events are being processed - # and switch between a short and long sleep time. Here are some - # stats useful in helping to tune this. - # time CPU load - # 0.001 13% - # 0.005 3% - # 0.01 1.5% - # 0.05 0.5% - used_time = clock() - t - if used_time > 10.0: - # print 'Sleep for 1 s' # dbg - time.sleep(1.0) - elif used_time > 0.1: - # Few GUI events coming in, so we can sleep longer - # print 'Sleep for 0.05 s' # dbg - time.sleep(0.05) - else: - # Many GUI events coming in, so sleep only very little - time.sleep(0.001) - del ea - except KeyboardInterrupt: - pass - return 0 - -if sys.platform == 'darwin': - # On OSX, evtloop.Pending() always returns True, regardless of there being - # any events pending. As such we can't use implementations 1 or 3 of the - # inputhook as those depend on a pending/dispatch loop. - inputhook_wx = inputhook_wx2 -else: - # This is our default implementation - inputhook_wx = inputhook_wx3 diff --git a/IPython/lib/kernel.py b/IPython/lib/kernel.py deleted file mode 100644 index af9827667fb..00000000000 --- a/IPython/lib/kernel.py +++ /dev/null @@ -1,13 +0,0 @@ -"""[DEPRECATED] Utilities for connecting to kernels - -Moved to IPython.kernel.connect -""" - -import warnings -warnings.warn("IPython.lib.kernel moved to IPython.kernel.connect in IPython 1.0," - " and will be removed in IPython 6.0.", - DeprecationWarning -) - -from ipykernel.connect import * - diff --git a/IPython/lib/latextools.py b/IPython/lib/latextools.py index 4de9ecce5aa..18516438d34 100644 --- a/IPython/lib/latextools.py +++ b/IPython/lib/latextools.py @@ -1,28 +1,29 @@ -# -*- coding: utf-8 -*- """Tools for handling LaTeX.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from io import BytesIO, open +from io import BytesIO import os import tempfile import shutil import subprocess from base64 import encodebytes +import textwrap + +from pathlib import Path from IPython.utils.process import find_cmd, FindCmdError from traitlets.config import get_config from traitlets.config.configurable import SingletonConfigurable from traitlets import List, Bool, Unicode -from IPython.utils.py3compat import cast_unicode class LaTeXTool(SingletonConfigurable): """An object to store configuration of the LaTeX tool.""" def _config_default(self): return get_config() - + backends = List( Unicode(), ["matplotlib", "dvipng"], help="Preferred backend to draw LaTeX math equations. " @@ -55,7 +56,9 @@ def _config_default(self): ).tag(config=True) -def latex_to_png(s, encode=False, backend=None, wrap=False): +def latex_to_png( + s: str, encode=False, backend=None, wrap=False, color="Black", scale=1.0 +): """Render a LaTeX string to PNG. Parameters @@ -68,11 +71,15 @@ def latex_to_png(s, encode=False, backend=None, wrap=False): Backend for producing PNG data. wrap : bool If true, Automatically wrap `s` as a LaTeX equation. - + color : string + Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB + format, e.g. '#AA20FA'. + scale : float + Scale factor for the resulting PNG. None is returned when the backend cannot be used. """ - s = cast_unicode(s) + assert isinstance(s, str) allowed_backends = LaTeXTool.instance().backends if backend is None: backend = allowed_backends[0] @@ -82,17 +89,28 @@ def latex_to_png(s, encode=False, backend=None, wrap=False): f = latex_to_png_mpl elif backend == 'dvipng': f = latex_to_png_dvipng + if color.startswith('#'): + # Convert hex RGB color to LaTeX RGB color. + if len(color) == 7: + try: + color = "RGB {}".format(" ".join([str(int(x, 16)) for x in + textwrap.wrap(color[1:], 2)])) + except ValueError as e: + raise ValueError(f'Invalid color specification {color}.') from e + else: + raise ValueError(f'Invalid color specification {color}.') else: - raise ValueError('No such backend {0}'.format(backend)) - bin_data = f(s, wrap) + raise ValueError(f'No such backend {backend}') + bin_data = f(s, wrap, color, scale) if encode and bin_data: bin_data = encodebytes(bin_data) return bin_data -def latex_to_png_mpl(s, wrap): +def latex_to_png_mpl(s, wrap, color='Black', scale=1.0): try: - from matplotlib import mathtext + from matplotlib import figure, font_manager, mathtext + from matplotlib.backends import backend_agg from pyparsing import ParseFatalException except ImportError: return None @@ -100,43 +118,80 @@ def latex_to_png_mpl(s, wrap): # mpl mathtext doesn't support display math, force inline s = s.replace('$$', '$') if wrap: - s = u'${0}$'.format(s) + s = f'${s}$' try: - mt = mathtext.MathTextParser('bitmap') - f = BytesIO() - mt.to_png(f, s, fontsize=12) - return f.getvalue() + prop = font_manager.FontProperties(size=12) + dpi = 120 * scale + buffer = BytesIO() + + # Adapted from mathtext.math_to_image + parser = mathtext.MathTextParser("path") + width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop) + fig = figure.Figure(figsize=(width / 72, height / 72)) + fig.text(0, depth / height, s, fontproperties=prop, color=color) + backend_agg.FigureCanvasAgg(fig) + fig.savefig(buffer, dpi=dpi, format="png", transparent=True) + return buffer.getvalue() except (ValueError, RuntimeError, ParseFatalException): return None -def latex_to_png_dvipng(s, wrap): +def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0): try: find_cmd('latex') find_cmd('dvipng') except FindCmdError: return None - try: - workdir = tempfile.mkdtemp() - tmpfile = os.path.join(workdir, "tmp.tex") - dvifile = os.path.join(workdir, "tmp.dvi") - outfile = os.path.join(workdir, "tmp.png") - with open(tmpfile, "w", encoding='utf8') as f: - f.writelines(genelatex(s, wrap)) + startupinfo = None + if os.name == "nt": + # prevent popup-windows + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - with open(os.devnull, 'wb') as devnull: - subprocess.check_call( - ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile], - cwd=workdir, stdout=devnull, stderr=devnull) + try: + workdir = Path(tempfile.mkdtemp()) + tmpfile = "tmp.tex" + dvifile = "tmp.dvi" + outfile = "tmp.png" - subprocess.check_call( - ["dvipng", "-T", "tight", "-x", "1500", "-z", "9", - "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir, - stdout=devnull, stderr=devnull) + with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f: + f.writelines(genelatex(s, wrap)) - with open(outfile, "rb") as f: + subprocess.check_call( + ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile], + cwd=workdir, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + startupinfo=startupinfo, + ) + + resolution = round(150 * scale) + subprocess.check_call( + [ + "dvipng", + "-T", + "tight", + "-D", + str(resolution), + "-z", + "9", + "-bg", + "Transparent", + "-o", + outfile, + dvifile, + "-fg", + color, + ], + cwd=workdir, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + startupinfo=startupinfo, + ) + + with workdir.joinpath(outfile).open("rb") as f: return f.read() except subprocess.CalledProcessError: return None @@ -166,7 +221,7 @@ def genelatex(body, wrap): if breqn: packages = packages + ['breqn'] for pack in packages: - yield r'\usepackage{{{0}}}'.format(pack) + yield fr'\usepackage{{{pack}}}' yield r'\pagestyle{empty}' if lt.preamble: yield lt.preamble @@ -176,13 +231,13 @@ def genelatex(body, wrap): yield body yield r'\end{dmath*}' elif wrap: - yield u'$${0}$$'.format(body) + yield f'$${body}$$' else: yield body - yield u'\end{document}' + yield '\\end{document}' -_data_uri_template_png = u"""%s""" +_data_uri_template_png = """%s""" def latex_to_html(s, alt='image'): """Render LaTeX to HTML with embedded PNG data using data URIs. @@ -197,5 +252,3 @@ def latex_to_html(s, alt='image'): base64_data = latex_to_png(s, encode=True).decode('ascii') if base64_data: return _data_uri_template_png % (base64_data, alt) - - diff --git a/IPython/lib/lexers.py b/IPython/lib/lexers.py index b8dec7e43b9..da22950a93c 100644 --- a/IPython/lib/lexers.py +++ b/IPython/lib/lexers.py @@ -1,531 +1,31 @@ -# -*- coding: utf-8 -*- """ -Defines a variety of Pygments lexers for highlighting IPython code. - -This includes: - - IPythonLexer, IPython3Lexer - Lexers for pure IPython (python + magic/shell commands) - - IPythonPartialTracebackLexer, IPythonTracebackLexer - Supports 2.x and 3.x via keyword `python3`. The partial traceback - lexer reads everything but the Python code appearing in a traceback. - The full lexer combines the partial lexer with an IPython lexer. - - IPythonConsoleLexer - A lexer for IPython console sessions, with support for tracebacks. - - IPyLexer - A friendly lexer which examines the first line of text and from it, - decides whether to use an IPython lexer or an IPython console lexer. - This is probably the only lexer that needs to be explicitly added - to Pygments. +The IPython lexers are now a separate package, ipython-pygments-lexers. +Importing from here is deprecated and may break in the future. """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (c) 2013, the IPython Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- - -# Standard library -import re - -# Third party -from pygments.lexers import ( - BashLexer, HtmlLexer, JavascriptLexer, RubyLexer, PerlLexer, PythonLexer, - Python3Lexer, TexLexer) -from pygments.lexer import ( - Lexer, DelegatingLexer, RegexLexer, do_insertions, bygroups, using, -) -from pygments.token import ( - Generic, Keyword, Literal, Name, Operator, Other, Text, Error, +# ----------------------------------------------------------------------------- + +from ipython_pygments_lexers import ( + IPythonLexer, + IPython3Lexer, + IPythonPartialTracebackLexer, + IPythonTracebackLexer, + IPythonConsoleLexer, + IPyLexer, ) -from pygments.util import get_bool_opt - -# Local - -line_re = re.compile('.*?\n') - -__all__ = ['build_ipy_lexer', 'IPython3Lexer', 'IPythonLexer', - 'IPythonPartialTracebackLexer', 'IPythonTracebackLexer', - 'IPythonConsoleLexer', 'IPyLexer'] - - -def build_ipy_lexer(python3): - """Builds IPython lexers depending on the value of `python3`. - - The lexer inherits from an appropriate Python lexer and then adds - information about IPython specific keywords (i.e. magic commands, - shell commands, etc.) - - Parameters - ---------- - python3 : bool - If `True`, then build an IPython lexer from a Python 3 lexer. - - """ - # It would be nice to have a single IPython lexer class which takes - # a boolean `python3`. But since there are two Python lexer classes, - # we will also have two IPython lexer classes. - if python3: - PyLexer = Python3Lexer - name = 'IPython3' - aliases = ['ipython3'] - doc = """IPython3 Lexer""" - else: - PyLexer = PythonLexer - name = 'IPython' - aliases = ['ipython2', 'ipython'] - doc = """IPython Lexer""" - - ipython_tokens = [ - (r'(?s)(\s*)(%%capture)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), - (r'(?s)(\s*)(%%debug)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), - (r'(?is)(\s*)(%%html)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(HtmlLexer))), - (r'(?s)(\s*)(%%javascript)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(JavascriptLexer))), - (r'(?s)(\s*)(%%js)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(JavascriptLexer))), - (r'(?s)(\s*)(%%latex)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(TexLexer))), - (r'(?s)(\s*)(%%pypy)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PerlLexer))), - (r'(?s)(\s*)(%%prun)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), - (r'(?s)(\s*)(%%pypy)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), - (r'(?s)(\s*)(%%python)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), - (r'(?s)(\s*)(%%python2)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PythonLexer))), - (r'(?s)(\s*)(%%python3)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(Python3Lexer))), - (r'(?s)(\s*)(%%ruby)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(RubyLexer))), - (r'(?s)(\s*)(%%time)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), - (r'(?s)(\s*)(%%timeit)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), - (r'(?s)(\s*)(%%writefile)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), - (r"(?s)(\s*)(%%)(\w+)(.*)", bygroups(Text, Operator, Keyword, Text)), - (r'(?s)(^\s*)(%%!)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(BashLexer))), - (r"(%%?)(\w+)(\?\??)$", bygroups(Operator, Keyword, Operator)), - (r"\b(\?\??)(\s*)$", bygroups(Operator, Text)), - (r'(%)(sx|sc|system)(.*)(\n)', bygroups(Operator, Keyword, - using(BashLexer), Text)), - (r'(%)(\w+)(.*\n)', bygroups(Operator, Keyword, Text)), - (r'^(!!)(.+)(\n)', bygroups(Operator, using(BashLexer), Text)), - (r'(!)(?!=)(.+)(\n)', bygroups(Operator, using(BashLexer), Text)), - (r'^(\s*)(\?\??)(\s*%{0,2}[\w\.\*]*)', bygroups(Text, Operator, Text)), - (r'(\s*%{0,2}[\w\.\*]*)(\?\??)(\s*)$', bygroups(Text, Operator, Text)), - ] - - tokens = PyLexer.tokens.copy() - tokens['root'] = ipython_tokens + tokens['root'] - - attrs = {'name': name, 'aliases': aliases, 'filenames': [], - '__doc__': doc, 'tokens': tokens} - - return type(name, (PyLexer,), attrs) - - -IPython3Lexer = build_ipy_lexer(python3=True) -IPythonLexer = build_ipy_lexer(python3=False) - - -class IPythonPartialTracebackLexer(RegexLexer): - """ - Partial lexer for IPython tracebacks. - - Handles all the non-python output. This works for both Python 2.x and 3.x. - - """ - name = 'IPython Partial Traceback' - - tokens = { - 'root': [ - # Tracebacks for syntax errors have a different style. - # For both types of tracebacks, we mark the first line with - # Generic.Traceback. For syntax errors, we mark the filename - # as we mark the filenames for non-syntax tracebacks. - # - # These two regexps define how IPythonConsoleLexer finds a - # traceback. - # - ## Non-syntax traceback - (r'^(\^C)?(-+\n)', bygroups(Error, Generic.Traceback)), - ## Syntax traceback - (r'^( File)(.*)(, line )(\d+\n)', - bygroups(Generic.Traceback, Name.Namespace, - Generic.Traceback, Literal.Number.Integer)), - - # (Exception Identifier)(Whitespace)(Traceback Message) - (r'(?u)(^[^\d\W]\w*)(\s*)(Traceback.*?\n)', - bygroups(Name.Exception, Generic.Whitespace, Text)), - # (Module/Filename)(Text)(Callee)(Function Signature) - # Better options for callee and function signature? - (r'(.*)( in )(.*)(\(.*\)\n)', - bygroups(Name.Namespace, Text, Name.Entity, Name.Tag)), - # Regular line: (Whitespace)(Line Number)(Python Code) - (r'(\s*?)(\d+)(.*?\n)', - bygroups(Generic.Whitespace, Literal.Number.Integer, Other)), - # Emphasized line: (Arrow)(Line Number)(Python Code) - # Using Exception token so arrow color matches the Exception. - (r'(-*>?\s?)(\d+)(.*?\n)', - bygroups(Name.Exception, Literal.Number.Integer, Other)), - # (Exception Identifier)(Message) - (r'(?u)(^[^\d\W]\w*)(:.*?\n)', - bygroups(Name.Exception, Text)), - # Tag everything else as Other, will be handled later. - (r'.*\n', Other), - ], - } - - -class IPythonTracebackLexer(DelegatingLexer): - """ - IPython traceback lexer. - - For doctests, the tracebacks can be snipped as much as desired with the - exception to the lines that designate a traceback. For non-syntax error - tracebacks, this is the line of hyphens. For syntax error tracebacks, - this is the line which lists the File and line number. - - """ - # The lexer inherits from DelegatingLexer. The "root" lexer is an - # appropriate IPython lexer, which depends on the value of the boolean - # `python3`. First, we parse with the partial IPython traceback lexer. - # Then, any code marked with the "Other" token is delegated to the root - # lexer. - # - name = 'IPython Traceback' - aliases = ['ipythontb'] - - def __init__(self, **options): - self.python3 = get_bool_opt(options, 'python3', False) - if self.python3: - self.aliases = ['ipython3tb'] - else: - self.aliases = ['ipython2tb', 'ipythontb'] - - if self.python3: - IPyLexer = IPython3Lexer - else: - IPyLexer = IPythonLexer - - DelegatingLexer.__init__(self, IPyLexer, - IPythonPartialTracebackLexer, **options) - -class IPythonConsoleLexer(Lexer): - """ - An IPython console lexer for IPython code-blocks and doctests, such as: - - .. code-block:: rst - - .. code-block:: ipythonconsole - - In [1]: a = 'foo' - - In [2]: a - Out[2]: 'foo' - - In [3]: print a - foo - - In [4]: 1 / 0 - - - Support is also provided for IPython exceptions: - - .. code-block:: rst - - .. code-block:: ipythonconsole - - In [1]: raise Exception - - --------------------------------------------------------------------------- - Exception Traceback (most recent call last) - in - ----> 1 raise Exception - - Exception: - - """ - name = 'IPython console session' - aliases = ['ipythonconsole'] - mimetypes = ['text/x-ipython-console'] - - # The regexps used to determine what is input and what is output. - # The default prompts for IPython are: - # - # in = 'In [#]: ' - # continuation = ' .D.: ' - # template = 'Out[#]: ' - # - # Where '#' is the 'prompt number' or 'execution count' and 'D' - # D is a number of dots matching the width of the execution count - # - in1_regex = r'In \[[0-9]+\]: ' - in2_regex = r' \.\.+\.: ' - out_regex = r'Out\[[0-9]+\]: ' - - #: The regex to determine when a traceback starts. - ipytb_start = re.compile(r'^(\^C)?(-+\n)|^( File)(.*)(, line )(\d+\n)') - - def __init__(self, **options): - """Initialize the IPython console lexer. - - Parameters - ---------- - python3 : bool - If `True`, then the console inputs are parsed using a Python 3 - lexer. Otherwise, they are parsed using a Python 2 lexer. - in1_regex : RegexObject - The compiled regular expression used to detect the start - of inputs. Although the IPython configuration setting may have a - trailing whitespace, do not include it in the regex. If `None`, - then the default input prompt is assumed. - in2_regex : RegexObject - The compiled regular expression used to detect the continuation - of inputs. Although the IPython configuration setting may have a - trailing whitespace, do not include it in the regex. If `None`, - then the default input prompt is assumed. - out_regex : RegexObject - The compiled regular expression used to detect outputs. If `None`, - then the default output prompt is assumed. - - """ - self.python3 = get_bool_opt(options, 'python3', False) - if self.python3: - self.aliases = ['ipython3console'] - else: - self.aliases = ['ipython2console', 'ipythonconsole'] - - in1_regex = options.get('in1_regex', self.in1_regex) - in2_regex = options.get('in2_regex', self.in2_regex) - out_regex = options.get('out_regex', self.out_regex) - - # So that we can work with input and output prompts which have been - # rstrip'd (possibly by editors) we also need rstrip'd variants. If - # we do not do this, then such prompts will be tagged as 'output'. - # The reason can't just use the rstrip'd variants instead is because - # we want any whitespace associated with the prompt to be inserted - # with the token. This allows formatted code to be modified so as hide - # the appearance of prompts, with the whitespace included. One example - # use of this is in copybutton.js from the standard lib Python docs. - in1_regex_rstrip = in1_regex.rstrip() + '\n' - in2_regex_rstrip = in2_regex.rstrip() + '\n' - out_regex_rstrip = out_regex.rstrip() + '\n' - - # Compile and save them all. - attrs = ['in1_regex', 'in2_regex', 'out_regex', - 'in1_regex_rstrip', 'in2_regex_rstrip', 'out_regex_rstrip'] - for attr in attrs: - self.__setattr__(attr, re.compile(locals()[attr])) - - Lexer.__init__(self, **options) - - if self.python3: - pylexer = IPython3Lexer - tblexer = IPythonTracebackLexer - else: - pylexer = IPythonLexer - tblexer = IPythonTracebackLexer - - self.pylexer = pylexer(**options) - self.tblexer = tblexer(**options) - - self.reset() - - def reset(self): - self.mode = 'output' - self.index = 0 - self.buffer = u'' - self.insertions = [] - - def buffered_tokens(self): - """ - Generator of unprocessed tokens after doing insertions and before - changing to a new state. - - """ - if self.mode == 'output': - tokens = [(0, Generic.Output, self.buffer)] - elif self.mode == 'input': - tokens = self.pylexer.get_tokens_unprocessed(self.buffer) - else: # traceback - tokens = self.tblexer.get_tokens_unprocessed(self.buffer) - - for i, t, v in do_insertions(self.insertions, tokens): - # All token indexes are relative to the buffer. - yield self.index + i, t, v - - # Clear it all - self.index += len(self.buffer) - self.buffer = u'' - self.insertions = [] - - def get_mci(self, line): - """ - Parses the line and returns a 3-tuple: (mode, code, insertion). - - `mode` is the next mode (or state) of the lexer, and is always equal - to 'input', 'output', or 'tb'. - - `code` is a portion of the line that should be added to the buffer - corresponding to the next mode and eventually lexed by another lexer. - For example, `code` could be Python code if `mode` were 'input'. - - `insertion` is a 3-tuple (index, token, text) representing an - unprocessed "token" that will be inserted into the stream of tokens - that are created from the buffer once we change modes. This is usually - the input or output prompt. - - In general, the next mode depends on current mode and on the contents - of `line`. - - """ - # To reduce the number of regex match checks, we have multiple - # 'if' blocks instead of 'if-elif' blocks. - - # Check for possible end of input - in2_match = self.in2_regex.match(line) - in2_match_rstrip = self.in2_regex_rstrip.match(line) - if (in2_match and in2_match.group().rstrip() == line.rstrip()) or \ - in2_match_rstrip: - end_input = True - else: - end_input = False - if end_input and self.mode != 'tb': - # Only look for an end of input when not in tb mode. - # An ellipsis could appear within the traceback. - mode = 'output' - code = u'' - insertion = (0, Generic.Prompt, line) - return mode, code, insertion - - # Check for output prompt - out_match = self.out_regex.match(line) - out_match_rstrip = self.out_regex_rstrip.match(line) - if out_match or out_match_rstrip: - mode = 'output' - if out_match: - idx = out_match.end() - else: - idx = out_match_rstrip.end() - code = line[idx:] - # Use the 'heading' token for output. We cannot use Generic.Error - # since it would conflict with exceptions. - insertion = (0, Generic.Heading, line[:idx]) - return mode, code, insertion - - - # Check for input or continuation prompt (non stripped version) - in1_match = self.in1_regex.match(line) - if in1_match or (in2_match and self.mode != 'tb'): - # New input or when not in tb, continued input. - # We do not check for continued input when in tb since it is - # allowable to replace a long stack with an ellipsis. - mode = 'input' - if in1_match: - idx = in1_match.end() - else: # in2_match - idx = in2_match.end() - code = line[idx:] - insertion = (0, Generic.Prompt, line[:idx]) - return mode, code, insertion - - # Check for input or continuation prompt (stripped version) - in1_match_rstrip = self.in1_regex_rstrip.match(line) - if in1_match_rstrip or (in2_match_rstrip and self.mode != 'tb'): - # New input or when not in tb, continued input. - # We do not check for continued input when in tb since it is - # allowable to replace a long stack with an ellipsis. - mode = 'input' - if in1_match_rstrip: - idx = in1_match_rstrip.end() - else: # in2_match - idx = in2_match_rstrip.end() - code = line[idx:] - insertion = (0, Generic.Prompt, line[:idx]) - return mode, code, insertion - - # Check for traceback - if self.ipytb_start.match(line): - mode = 'tb' - code = line - insertion = None - return mode, code, insertion - - # All other stuff... - if self.mode in ('input', 'output'): - # We assume all other text is output. Multiline input that - # does not use the continuation marker cannot be detected. - # For example, the 3 in the following is clearly output: - # - # In [1]: print 3 - # 3 - # - # But the following second line is part of the input: - # - # In [2]: while True: - # print True - # - # In both cases, the 2nd line will be 'output'. - # - mode = 'output' - else: - mode = 'tb' - - code = line - insertion = None - - return mode, code, insertion - - def get_tokens_unprocessed(self, text): - self.reset() - for match in line_re.finditer(text): - line = match.group() - mode, code, insertion = self.get_mci(line) - - if mode != self.mode: - # Yield buffered tokens before transitioning to new mode. - for token in self.buffered_tokens(): - yield token - self.mode = mode - - if insertion: - self.insertions.append((len(self.buffer), [insertion])) - self.buffer += code - - for token in self.buffered_tokens(): - yield token - -class IPyLexer(Lexer): - """ - Primary lexer for all IPython-like code. - - This is a simple helper lexer. If the first line of the text begins with - "In \[[0-9]+\]:", then the entire text is parsed with an IPython console - lexer. If not, then the entire text is parsed with an IPython lexer. - - The goal is to reduce the number of lexers that are registered - with Pygments. - - """ - name = 'IPy session' - aliases = ['ipy'] - - def __init__(self, **options): - self.python3 = get_bool_opt(options, 'python3', False) - if self.python3: - self.aliases = ['ipy3'] - else: - self.aliases = ['ipy2', 'ipy'] - - Lexer.__init__(self, **options) - - self.IPythonLexer = IPythonLexer(**options) - self.IPythonConsoleLexer = IPythonConsoleLexer(**options) - def get_tokens_unprocessed(self, text): - # Search for the input prompt anywhere...this allows code blocks to - # begin with comments as well. - if re.match(r'.*(In \[[0-9]+\]:)', text.strip(), re.DOTALL): - lex = self.IPythonConsoleLexer - else: - lex = self.IPythonLexer - for token in lex.get_tokens_unprocessed(text): - yield token +__all__ = [ + "IPython3Lexer", + "IPythonLexer", + "IPythonPartialTracebackLexer", + "IPythonTracebackLexer", + "IPythonConsoleLexer", + "IPyLexer", +] diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index 39d23ddcd8a..6ae5813c989 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Python advanced pretty printer. This pretty printer is intended to replace the old `pprint` python module which does not allow developers @@ -34,6 +33,22 @@ class MyObject(object): def _repr_pretty_(self, p, cycle): ... +Here's an example for a class with a simple constructor:: + + class MySimpleObject: + + def __init__(self, a, b, *, c=None): + self.a = a + self.b = b + self.c = c + + def _repr_pretty_(self, p, cycle): + ctor = CallExpression.factory(self.__class__.__name__) + if self.c is None: + p.pretty(ctor(a, b)) + else: + p.pretty(ctor(a, b, c=c)) + Here is an example implementation of a `_repr_pretty_` method for a list subclass:: @@ -87,19 +102,20 @@ def _repr_pretty_(self, p, cycle): from collections import deque from inspect import signature from io import StringIO -from warnings import warn -from IPython.utils.decorators import undoc -from IPython.utils.py3compat import PYPY +# Allow pretty-printing of functions with PEP-649 annotations +if sys.version_info >= (3, 14): + from annotationlib import Format + from functools import partial + + signature = partial(signature, annotation_format=Format.FORWARDREF) + __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter', - 'for_type', 'for_type_by_name'] + 'for_type', 'for_type_by_name', 'RawText', 'RawStringLiteral', 'CallExpression'] MAX_SEQ_LENGTH = 1000 -# The language spec says that dicts preserve order from 3.7, but CPython -# does so from 3.6, so it seems likely that people will expect that. -DICT_IS_ORDERED = sys.version_info >= (3, 6) _re_pattern_type = type(re.compile('')) def _safe_getattr(obj, attr, default=None): @@ -113,14 +129,6 @@ def _safe_getattr(obj, attr, default=None): except Exception: return default -@undoc -class CUnicodeIO(StringIO): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - warn(("CUnicodeIO is deprecated since IPython 6.0. " - "Please use io.StringIO instead."), - DeprecationWarning, stacklevel=2) - def _sorted_for_pprint(items): """ Sort the given items for pretty printing. Since some predictable @@ -157,7 +165,8 @@ def pprint(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SE sys.stdout.write(newline) sys.stdout.flush() -class _PrettyPrinterBase(object): + +class _PrettyPrinterBase: @contextmanager def indent(self, indent): @@ -199,19 +208,22 @@ def __init__(self, output, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LE self.group_queue = GroupQueue(root_group) self.indentation = 0 + def _break_one_group(self, group): + while group.breakables: + x = self.buffer.popleft() + self.output_width = x.output(self.output, self.output_width) + self.buffer_width -= x.width + while self.buffer and isinstance(self.buffer[0], Text): + x = self.buffer.popleft() + self.output_width = x.output(self.output, self.output_width) + self.buffer_width -= x.width + def _break_outer_groups(self): while self.max_width < self.output_width + self.buffer_width: group = self.group_queue.deq() if not group: return - while group.breakables: - x = self.buffer.popleft() - self.output_width = x.output(self.output, self.output_width) - self.buffer_width -= x.width - while self.buffer and isinstance(self.buffer[0], Text): - x = self.buffer.popleft() - self.output_width = x.output(self.output, self.output_width) - self.buffer_width -= x.width + self._break_one_group(group) def text(self, obj): """Add literal text to the output.""" @@ -251,6 +263,9 @@ def break_(self): """ Explicitly insert a newline into the output, maintaining correct indentation. """ + group = self.group_queue.deq() + if group: + self._break_one_group(group) self.flush() self.output.write(self.newline) self.output.write(' ' * self.indentation) @@ -260,18 +275,7 @@ def break_(self): def begin_group(self, indent=0, open=''): """ - Begin a group. If you want support for python < 2.5 which doesn't has - the with statement this is the preferred way: - - p.begin_group(1, '{') - ... - p.end_group(1, '}') - - The python 2.5 expression would be this: - - with p.group(1, '{', '}'): - ... - + Begin a group. The first parameter specifies the indentation for the next line (usually the width of the opening text), the second the opening text. All parameters are optional. @@ -397,8 +401,16 @@ def pretty(self, obj): meth = cls._repr_pretty_ if callable(meth): return meth(obj, self, cycle) - if cls is not object \ - and callable(cls.__dict__.get('__repr__')): + if ( + cls is not object + # check if cls defines __repr__ + and "__repr__" in cls.__dict__ + # check if __repr__ is callable. + # Note: we need to test getattr(cls, '__repr__') + # instead of cls.__dict__['__repr__'] + # in order to work with descriptors like partialmethod, + and callable(_safe_getattr(cls, "__repr__", None)) + ): return _repr_pprint(obj, self, cycle) return _default_pprint(obj, self, cycle) @@ -425,7 +437,7 @@ class is not in the registry. Successful matches will be moved to the return printer -class Printable(object): +class Printable: def output(self, stream, output_width): return output_width @@ -477,7 +489,7 @@ def __init__(self, depth): self.want_break = False -class GroupQueue(object): +class GroupQueue: def __init__(self, *groups): self.queue = [] @@ -508,6 +520,75 @@ def remove(self, group): pass +class RawText: + """ Object such that ``p.pretty(RawText(value))`` is the same as ``p.text(value)``. + + An example usage of this would be to show a list as binary numbers, using + ``p.pretty([RawText(bin(i)) for i in integers])``. + """ + def __init__(self, value): + self.value = value + + def _repr_pretty_(self, p, cycle): + p.text(self.value) + + +class CallExpression: + """ Object which emits a line-wrapped call expression in the form `__name(*args, **kwargs)` """ + def __init__(__self, __name, *args, **kwargs): + # dunders are to avoid clashes with kwargs, as python's name managing + # will kick in. + self = __self + self.name = __name + self.args = args + self.kwargs = kwargs + + @classmethod + def factory(cls, name): + def inner(*args, **kwargs): + return cls(name, *args, **kwargs) + return inner + + def _repr_pretty_(self, p, cycle): + # dunders are to avoid clashes with kwargs, as python's name managing + # will kick in. + + started = False + def new_item(): + nonlocal started + if started: + p.text(",") + p.breakable() + started = True + + prefix = self.name + "(" + with p.group(len(prefix), prefix, ")"): + for arg in self.args: + new_item() + p.pretty(arg) + for arg_name, arg in self.kwargs.items(): + new_item() + arg_prefix = arg_name + "=" + with p.group(len(arg_prefix), arg_prefix): + p.pretty(arg) + + +class RawStringLiteral: + """ Wrapper that shows a string with a `r` prefix """ + def __init__(self, value): + self.value = value + + def _repr_pretty_(self, p, cycle): + base_repr = repr(self.value) + if base_repr[:1] in 'uU': + base_repr = base_repr[1:] + prefix = 'ur' + else: + prefix = 'r' + base_repr = prefix + base_repr.replace('\\\\', '\\') + p.text(base_repr) + + def _default_pprint(obj, p, cycle): """ The default print function. Used if an object does not provide one and @@ -549,7 +630,7 @@ def _default_pprint(obj, p, cycle): def _seq_pprinter_factory(start, end): """ Factory that returns a pprint function useful for sequences. Used by - the default pprint for tuples, dicts, and lists. + the default pprint for tuples and lists. """ def inner(obj, p, cycle): if cycle: @@ -561,7 +642,7 @@ def inner(obj, p, cycle): p.text(',') p.breakable() p.pretty(x) - if len(obj) == 1 and type(obj) is tuple: + if len(obj) == 1 and isinstance(obj, tuple): # Special case for 1-item tuples. p.text(',') p.end_group(step, end) @@ -606,11 +687,6 @@ def inner(obj, p, cycle): step = len(start) p.begin_group(step, start) keys = obj.keys() - # if dict isn't large enough to be truncated, sort keys before displaying - # From Python 3.7, dicts preserve order by definition, so we don't sort. - if not DICT_IS_ORDERED \ - and not (p.max_seq_length and len(obj) >= p.max_seq_length): - keys = _sorted_for_pprint(keys) for idx, key in p._enumerate(keys): if idx: p.text(',') @@ -628,7 +704,9 @@ def _super_pprint(obj, p, cycle): p.pretty(obj.__thisclass__) p.text(',') p.breakable() - if PYPY: # In PyPy, super() objects don't have __self__ attributes + import platform + + if platform.python_implementation() == "PyPy": # In PyPy, super() objects don't have __self__ attributes dself = obj.__repr__.__self__ p.pretty(None if dself is obj else dself) else: @@ -636,29 +714,45 @@ def _super_pprint(obj, p, cycle): p.end_group(8, '>') -def _re_pattern_pprint(obj, p, cycle): - """The pprint function for regular expression patterns.""" - p.text('re.compile(') - pattern = repr(obj.pattern) - if pattern[:1] in 'uU': - pattern = pattern[1:] - prefix = 'ur' - else: - prefix = 'r' - pattern = prefix + pattern.replace('\\\\', '\\') - p.text(pattern) - if obj.flags: - p.text(',') - p.breakable() + +class _ReFlags: + def __init__(self, value): + self.value = value + + def _repr_pretty_(self, p, cycle): done_one = False - for flag in ('TEMPLATE', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL', - 'UNICODE', 'VERBOSE', 'DEBUG'): - if obj.flags & getattr(re, flag): + for flag in ( + "IGNORECASE", + "LOCALE", + "MULTILINE", + "DOTALL", + "UNICODE", + "VERBOSE", + "DEBUG", + ): + if self.value & getattr(re, flag): if done_one: p.text('|') p.text('re.' + flag) done_one = True - p.text(')') + + +def _re_pattern_pprint(obj, p, cycle): + """The pprint function for regular expression patterns.""" + re_compile = CallExpression.factory('re.compile') + if obj.flags: + p.pretty(re_compile(RawStringLiteral(obj.pattern), _ReFlags(obj.flags))) + else: + p.pretty(re_compile(RawStringLiteral(obj.pattern))) + + +def _types_simplenamespace_pprint(obj, p, cycle): + """The pprint function for types.SimpleNamespace.""" + namespace = CallExpression.factory('namespace') + if cycle: + p.pretty(namespace(RawText("..."))) + else: + p.pretty(namespace(**obj.__dict__)) def _type_pprint(obj, p, cycle): @@ -695,10 +789,12 @@ def _repr_pprint(obj, p, cycle): """A pprint that just redirects to the normal repr function.""" # Find newlines and replace them with p.break_() output = repr(obj) - for idx,output_line in enumerate(output.splitlines()): - if idx: - p.break_() - p.text(output_line) + lines = output.splitlines() + with p.group(): + for idx, output_line in enumerate(lines): + if idx: + p.break_() + p.text(output_line) def _function_pprint(obj, p, cycle): @@ -718,18 +814,13 @@ def _exception_pprint(obj, p, cycle): """Base pprint for all exceptions.""" name = getattr(obj.__class__, '__qualname__', obj.__class__.__name__) if obj.__class__.__module__ not in ('exceptions', 'builtins'): - name = '%s.%s' % (obj.__class__.__module__, name) - step = len(name) + 1 - p.begin_group(step, name + '(') - for idx, arg in enumerate(getattr(obj, 'args', ())): - if idx: - p.text(',') - p.breakable() - p.pretty(arg) - p.end_group(step, ')') + name = '{}.{}'.format(obj.__class__.__module__, name) + + p.pretty(CallExpression(name, *getattr(obj, 'args', ()))) #: the exception base +_exception_base: type try: _exception_base = BaseException except NameError: @@ -752,6 +843,7 @@ def _exception_pprint(obj, p, cycle): types.FunctionType: _function_pprint, types.BuiltinFunctionType: _function_pprint, types.MethodType: _repr_pprint, + types.SimpleNamespace: _types_simplenamespace_pprint, datetime.datetime: _repr_pprint, datetime.timedelta: _repr_pprint, _exception_base: _exception_pprint @@ -763,28 +855,15 @@ def _exception_pprint(obj, p, cycle): if _env_type is not dict: _type_pprinters[_env_type] = _dict_pprinter_factory('environ{', '}') -try: - # In PyPy, types.DictProxyType is dict, setting the dictproxy printer - # using dict.setdefault avoids overwriting the dict printer - _type_pprinters.setdefault(types.DictProxyType, - _dict_pprinter_factory('dict_proxy({', '})')) - _type_pprinters[types.ClassType] = _type_pprint - _type_pprinters[types.SliceType] = _repr_pprint -except AttributeError: # Python 3 - _type_pprinters[types.MappingProxyType] = \ - _dict_pprinter_factory('mappingproxy({', '})') - _type_pprinters[slice] = _repr_pprint +_type_pprinters[types.MappingProxyType] = _dict_pprinter_factory("mappingproxy({", "})") +_type_pprinters[slice] = _repr_pprint -try: - _type_pprinters[long] = _repr_pprint - _type_pprinters[unicode] = _repr_pprint -except NameError: - _type_pprinters[range] = _repr_pprint - _type_pprinters[bytes] = _repr_pprint +_type_pprinters[range] = _repr_pprint +_type_pprinters[bytes] = _repr_pprint #: printers for types specified by name -_deferred_type_pprinters = { -} +_deferred_type_pprinters: dict = {} + def for_type(typ, func): """ @@ -815,49 +894,58 @@ def for_type_by_name(type_module, type_name, func): def _defaultdict_pprint(obj, p, cycle): - name = obj.__class__.__name__ - with p.group(len(name) + 1, name + '(', ')'): - if cycle: - p.text('...') - else: - p.pretty(obj.default_factory) - p.text(',') - p.breakable() - p.pretty(dict(obj)) + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + else: + p.pretty(cls_ctor(obj.default_factory, dict(obj))) def _ordereddict_pprint(obj, p, cycle): - name = obj.__class__.__name__ - with p.group(len(name) + 1, name + '(', ')'): - if cycle: - p.text('...') - elif len(obj): - p.pretty(list(obj.items())) + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + elif len(obj): + p.pretty(cls_ctor(list(obj.items()))) + else: + p.pretty(cls_ctor()) def _deque_pprint(obj, p, cycle): - name = obj.__class__.__name__ - with p.group(len(name) + 1, name + '(', ')'): - if cycle: - p.text('...') - else: - p.pretty(list(obj)) - + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + elif obj.maxlen is not None: + p.pretty(cls_ctor(list(obj), maxlen=obj.maxlen)) + else: + p.pretty(cls_ctor(list(obj))) def _counter_pprint(obj, p, cycle): - name = obj.__class__.__name__ - with p.group(len(name) + 1, name + '(', ')'): - if cycle: - p.text('...') - elif len(obj): - p.pretty(dict(obj)) + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + elif len(obj): + p.pretty(cls_ctor(dict(obj.most_common()))) + else: + p.pretty(cls_ctor()) + + +def _userlist_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + else: + p.pretty(cls_ctor(obj.data)) + for_type_by_name('collections', 'defaultdict', _defaultdict_pprint) for_type_by_name('collections', 'OrderedDict', _ordereddict_pprint) for_type_by_name('collections', 'deque', _deque_pprint) for_type_by_name('collections', 'Counter', _counter_pprint) +for_type_by_name("collections", "UserList", _userlist_pprint) if __name__ == '__main__': from random import randrange - class Foo(object): + + class Foo: def __init__(self): self.foo = 1 self.bar = re.compile(r'\s+') diff --git a/IPython/lib/security.py b/IPython/lib/security.py deleted file mode 100644 index 91a2344eab8..00000000000 --- a/IPython/lib/security.py +++ /dev/null @@ -1,114 +0,0 @@ -""" -Password generation for the IPython notebook. -""" -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- -# Stdlib -import getpass -import hashlib -import random - -# Our own -from IPython.core.error import UsageError -from IPython.utils.py3compat import encode - -#----------------------------------------------------------------------------- -# Globals -#----------------------------------------------------------------------------- - -# Length of the salt in nr of hex chars, which implies salt_len * 4 -# bits of randomness. -salt_len = 12 - -#----------------------------------------------------------------------------- -# Functions -#----------------------------------------------------------------------------- - -def passwd(passphrase=None, algorithm='sha1'): - """Generate hashed password and salt for use in notebook configuration. - - In the notebook configuration, set `c.NotebookApp.password` to - the generated string. - - Parameters - ---------- - passphrase : str - Password to hash. If unspecified, the user is asked to input - and verify a password. - algorithm : str - Hashing algorithm to use (e.g, 'sha1' or any argument supported - by :func:`hashlib.new`). - - Returns - ------- - hashed_passphrase : str - Hashed password, in the format 'hash_algorithm:salt:passphrase_hash'. - - Examples - -------- - >>> passwd('mypassword') - 'sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12' - - """ - if passphrase is None: - for i in range(3): - p0 = getpass.getpass('Enter password: ') - p1 = getpass.getpass('Verify password: ') - if p0 == p1: - passphrase = p0 - break - else: - print('Passwords do not match.') - else: - raise UsageError('No matching passwords found. Giving up.') - - h = hashlib.new(algorithm) - salt = ('%0' + str(salt_len) + 'x') % random.getrandbits(4 * salt_len) - h.update(encode(passphrase, 'utf-8') + encode(salt, 'ascii')) - - return ':'.join((algorithm, salt, h.hexdigest())) - - -def passwd_check(hashed_passphrase, passphrase): - """Verify that a given passphrase matches its hashed version. - - Parameters - ---------- - hashed_passphrase : str - Hashed password, in the format returned by `passwd`. - passphrase : str - Passphrase to validate. - - Returns - ------- - valid : bool - True if the passphrase matches the hash. - - Examples - -------- - >>> from IPython.lib.security import passwd_check - >>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a', - ... 'mypassword') - True - - >>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a', - ... 'anotherpassword') - False - """ - try: - algorithm, salt, pw_digest = hashed_passphrase.split(':', 2) - except (ValueError, TypeError): - return False - - try: - h = hashlib.new(algorithm) - except ValueError: - return False - - if len(pw_digest) == 0: - return False - - h.update(encode(passphrase, 'utf-8') + encode(salt, 'ascii')) - - return h.hexdigest() == pw_digest diff --git a/IPython/lib/tests/test_backgroundjobs.py b/IPython/lib/tests/test_backgroundjobs.py deleted file mode 100644 index d7793f50553..00000000000 --- a/IPython/lib/tests/test_backgroundjobs.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Tests for pylab tools module. -""" -#----------------------------------------------------------------------------- -# Copyright (c) 2011, the IPython Development Team. -# -# Distributed under the terms of the Modified BSD License. -# -# The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Stdlib imports -import time - -# Third-party imports -import nose.tools as nt - -# Our own imports -from IPython.lib import backgroundjobs as bg - -#----------------------------------------------------------------------------- -# Globals and constants -#----------------------------------------------------------------------------- -t_short = 0.0001 # very short interval to wait on jobs - -#----------------------------------------------------------------------------- -# Local utilities -#----------------------------------------------------------------------------- -def sleeper(interval=t_short, *a, **kw): - args = dict(interval=interval, - other_args=a, - kw_args=kw) - time.sleep(interval) - return args - -def crasher(interval=t_short, *a, **kw): - time.sleep(interval) - raise Exception("Dead job with interval %s" % interval) - -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - -def test_result(): - """Test job submission and result retrieval""" - jobs = bg.BackgroundJobManager() - j = jobs.new(sleeper) - j.join() - nt.assert_equal(j.result['interval'], t_short) - - -def test_flush(): - """Test job control""" - jobs = bg.BackgroundJobManager() - j = jobs.new(sleeper) - j.join() - nt.assert_equal(len(jobs.completed), 1) - nt.assert_equal(len(jobs.dead), 0) - jobs.flush() - nt.assert_equal(len(jobs.completed), 0) - - -def test_dead(): - """Test control of dead jobs""" - jobs = bg.BackgroundJobManager() - j = jobs.new(crasher) - j.join() - nt.assert_equal(len(jobs.completed), 0) - nt.assert_equal(len(jobs.dead), 1) - jobs.flush() - nt.assert_equal(len(jobs.dead), 0) - - -def test_longer(): - """Test control of longer-running jobs""" - jobs = bg.BackgroundJobManager() - # Sleep for long enough for the following two checks to still report the - # job as running, but not so long that it makes the test suite noticeably - # slower. - j = jobs.new(sleeper, 0.1) - nt.assert_equal(len(jobs.running), 1) - nt.assert_equal(len(jobs.completed), 0) - j.join() - nt.assert_equal(len(jobs.running), 0) - nt.assert_equal(len(jobs.completed), 1) diff --git a/IPython/lib/tests/test_clipboard.py b/IPython/lib/tests/test_clipboard.py deleted file mode 100644 index f1050bfb5a6..00000000000 --- a/IPython/lib/tests/test_clipboard.py +++ /dev/null @@ -1,21 +0,0 @@ -import nose.tools as nt - -from IPython.core.error import TryNext -from IPython.lib.clipboard import ClipboardEmpty -from IPython.testing.decorators import skip_if_no_x11 - -@skip_if_no_x11 -def test_clipboard_get(): - # Smoketest for clipboard access - we can't easily guarantee that the - # clipboard is accessible and has something on it, but this tries to - # exercise the relevant code anyway. - try: - a = get_ipython().hooks.clipboard_get() - except ClipboardEmpty: - # Nothing in clipboard to get - pass - except TryNext: - # No clipboard access API available - pass - else: - nt.assert_is_instance(a, str) diff --git a/IPython/lib/tests/test_deepreload.py b/IPython/lib/tests/test_deepreload.py deleted file mode 100644 index abc57a38fb2..00000000000 --- a/IPython/lib/tests/test_deepreload.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -"""Test suite for the deepreload module.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import os - -import nose.tools as nt - -from IPython.utils.syspathcontext import prepended_to_syspath -from IPython.utils.tempdir import TemporaryDirectory -from IPython.lib.deepreload import reload as dreload - -def test_deepreload(): - "Test that dreload does deep reloads and skips excluded modules." - with TemporaryDirectory() as tmpdir: - with prepended_to_syspath(tmpdir): - with open(os.path.join(tmpdir, 'A.py'), 'w') as f: - f.write("class Object(object):\n pass\n") - with open(os.path.join(tmpdir, 'B.py'), 'w') as f: - f.write("import A\n") - import A - import B - - # Test that A is not reloaded. - obj = A.Object() - dreload(B, exclude=['A']) - nt.assert_true(isinstance(obj, A.Object)) - - # Test that A is reloaded. - obj = A.Object() - dreload(B) - nt.assert_false(isinstance(obj, A.Object)) diff --git a/IPython/lib/tests/test_display.py b/IPython/lib/tests/test_display.py deleted file mode 100644 index aa0d617edd3..00000000000 --- a/IPython/lib/tests/test_display.py +++ /dev/null @@ -1,189 +0,0 @@ -"""Tests for IPython.lib.display. - -""" -#----------------------------------------------------------------------------- -# Copyright (c) 2012, the IPython Development Team. -# -# Distributed under the terms of the Modified BSD License. -# -# The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- -from tempfile import NamedTemporaryFile, mkdtemp -from os.path import split, join as pjoin, dirname -import sys -try: - import pathlib -except ImportError: - pass - -# Third-party imports -import nose.tools as nt - -# Our own imports -from IPython.lib import display -from IPython.testing.decorators import skipif_not_numpy - -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - -#-------------------------- -# FileLink tests -#-------------------------- - -def test_instantiation_FileLink(): - """FileLink: Test class can be instantiated""" - fl = display.FileLink('example.txt') - # TODO: remove if when only Python >= 3.6 is supported - if sys.version_info >= (3, 6): - fl = display.FileLink(pathlib.PurePath('example.txt')) - -def test_warning_on_non_existant_path_FileLink(): - """FileLink: Calling _repr_html_ on non-existant files returns a warning - """ - fl = display.FileLink('example.txt') - nt.assert_true(fl._repr_html_().startswith('Path (example.txt)')) - -def test_existing_path_FileLink(): - """FileLink: Calling _repr_html_ functions as expected on existing filepath - """ - tf = NamedTemporaryFile() - fl = display.FileLink(tf.name) - actual = fl._repr_html_() - expected = "%s
" % (tf.name,tf.name) - nt.assert_equal(actual,expected) - -def test_existing_path_FileLink_repr(): - """FileLink: Calling repr() functions as expected on existing filepath - """ - tf = NamedTemporaryFile() - fl = display.FileLink(tf.name) - actual = repr(fl) - expected = tf.name - nt.assert_equal(actual,expected) - -def test_error_on_directory_to_FileLink(): - """FileLink: Raises error when passed directory - """ - td = mkdtemp() - nt.assert_raises(ValueError,display.FileLink,td) - -#-------------------------- -# FileLinks tests -#-------------------------- - -def test_instantiation_FileLinks(): - """FileLinks: Test class can be instantiated - """ - fls = display.FileLinks('example') - -def test_warning_on_non_existant_path_FileLinks(): - """FileLinks: Calling _repr_html_ on non-existant files returns a warning - """ - fls = display.FileLinks('example') - nt.assert_true(fls._repr_html_().startswith('Path (example)')) - -def test_existing_path_FileLinks(): - """FileLinks: Calling _repr_html_ functions as expected on existing dir - """ - td = mkdtemp() - tf1 = NamedTemporaryFile(dir=td) - tf2 = NamedTemporaryFile(dir=td) - fl = display.FileLinks(td) - actual = fl._repr_html_() - actual = actual.split('\n') - actual.sort() - # the links should always have forward slashes, even on windows, so replace - # backslashes with forward slashes here - expected = ["%s/
" % td, - "  %s
" %\ - (tf2.name.replace("\\","/"),split(tf2.name)[1]), - "  %s
" %\ - (tf1.name.replace("\\","/"),split(tf1.name)[1])] - expected.sort() - # We compare the sorted list of links here as that's more reliable - nt.assert_equal(actual,expected) - -def test_existing_path_FileLinks_alt_formatter(): - """FileLinks: Calling _repr_html_ functions as expected w/ an alt formatter - """ - td = mkdtemp() - tf1 = NamedTemporaryFile(dir=td) - tf2 = NamedTemporaryFile(dir=td) - def fake_formatter(dirname,fnames,included_suffixes): - return ["hello","world"] - fl = display.FileLinks(td,notebook_display_formatter=fake_formatter) - actual = fl._repr_html_() - actual = actual.split('\n') - actual.sort() - expected = ["hello","world"] - expected.sort() - # We compare the sorted list of links here as that's more reliable - nt.assert_equal(actual,expected) - -def test_existing_path_FileLinks_repr(): - """FileLinks: Calling repr() functions as expected on existing directory """ - td = mkdtemp() - tf1 = NamedTemporaryFile(dir=td) - tf2 = NamedTemporaryFile(dir=td) - fl = display.FileLinks(td) - actual = repr(fl) - actual = actual.split('\n') - actual.sort() - expected = ['%s/' % td, ' %s' % split(tf1.name)[1],' %s' % split(tf2.name)[1]] - expected.sort() - # We compare the sorted list of links here as that's more reliable - nt.assert_equal(actual,expected) - -def test_existing_path_FileLinks_repr_alt_formatter(): - """FileLinks: Calling repr() functions as expected w/ alt formatter - """ - td = mkdtemp() - tf1 = NamedTemporaryFile(dir=td) - tf2 = NamedTemporaryFile(dir=td) - def fake_formatter(dirname,fnames,included_suffixes): - return ["hello","world"] - fl = display.FileLinks(td,terminal_display_formatter=fake_formatter) - actual = repr(fl) - actual = actual.split('\n') - actual.sort() - expected = ["hello","world"] - expected.sort() - # We compare the sorted list of links here as that's more reliable - nt.assert_equal(actual,expected) - -def test_error_on_file_to_FileLinks(): - """FileLinks: Raises error when passed file - """ - td = mkdtemp() - tf1 = NamedTemporaryFile(dir=td) - nt.assert_raises(ValueError,display.FileLinks,tf1.name) - -def test_recursive_FileLinks(): - """FileLinks: Does not recurse when recursive=False - """ - td = mkdtemp() - tf = NamedTemporaryFile(dir=td) - subtd = mkdtemp(dir=td) - subtf = NamedTemporaryFile(dir=subtd) - fl = display.FileLinks(td) - actual = str(fl) - actual = actual.split('\n') - nt.assert_equal(len(actual), 4, actual) - fl = display.FileLinks(td, recursive=False) - actual = str(fl) - actual = actual.split('\n') - nt.assert_equal(len(actual), 2, actual) - -@skipif_not_numpy -def test_audio_from_file(): - path = pjoin(dirname(__file__), 'test.wav') - display.Audio(filename=path) - -def test_code_from_file(): - c = display.Code(filename=__file__) - assert c._repr_html_().startswith(' -

-

In [34]: from scipy.special import jn

-

-

In [35]: x = linspace(0,4*pi)

-

-

In [36]: for i in range(6):

-

    ...: plot(x,jn(i,x))

-

-

-

-

In [37]: 1/0

-

---------------------------------------------------------------------------

-

ZeroDivisionError Traceback (most recent call last)

-

/Users/minrk/<ipython-input-37-05c9758a9c21> in <module>()

-

----> 1 1/0

-

-

ZeroDivisionError: integer division or modulo by zero

-

-

In [38]:

\ No newline at end of file diff --git a/docs/source/interactive/figs/jn.xhtml b/docs/source/interactive/figs/jn.xhtml deleted file mode 100644 index f802a0a2a58..00000000000 --- a/docs/source/interactive/figs/jn.xhtml +++ /dev/null @@ -1,375 +0,0 @@ - - - - -

Python 2.6.1 (r261:67515, Feb 11 2010, 00:51:29)

-

Type "copyright", "credits" or "license" for more information.

-

-

IPython 0.11.alpha1.git -- An enhanced Interactive Python.

-

? -> Introduction and overview of IPython's features.

-

%quickref -> Quick reference.

-

help -> Python's own help system.

-

object? -> Details about 'object', use 'object??' for extra details.

-

%guiref -> A brief reference about the graphical user interface.

-

-

In [1]: from scipy.special import jn

-

-

In [2]: x = linspace(0,4*pi)

-

-

In [3]: for n in range(6):

-

   ...: plot(x,jn(n,x))

-

   ...:

-

-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-

-

In [4]:

\ No newline at end of file diff --git a/docs/source/interactive/index.rst b/docs/source/interactive/index.rst index 7010c51b320..586209136d4 100644 --- a/docs/source/interactive/index.rst +++ b/docs/source/interactive/index.rst @@ -8,7 +8,7 @@ tutorial, though some sections might make slightly more sense if you have alread done some work in the classic Python REPL. .. note:: - + Some part of this documentation are more than a decade old so might be out of date, we welcome any report of inaccuracy, and Pull Requests that make that up to date. @@ -29,4 +29,4 @@ done some work in the classic Python REPL. .. seealso:: `A Qt Console for Jupyter `__ - `The Jupyter Notebook `__ + `The Jupyter Notebook `__ diff --git a/docs/source/interactive/magics.rst b/docs/source/interactive/magics.rst index bb562f44f20..6efa02fca8c 100644 --- a/docs/source/interactive/magics.rst +++ b/docs/source/interactive/magics.rst @@ -13,11 +13,11 @@ Built-in magic commands other languages. Here is the help auto-generated from the docstrings of all the available Magics -function that IPython ships with. +functions that IPython ships with. -You can create an register your own Magics with IPython. You can find many user -defined Magics on `PyPI `_. Feel free to publish your own and -use the ``Framework :: IPython`` trove classifier. +You can create and register your own Magics with IPython. You can find many user +defined Magics on `PyPI `_. Feel free to publish your own and +use the ``Framework :: IPython`` trove classifier. .. include:: magics-generated.txt diff --git a/docs/source/interactive/plotting.rst b/docs/source/interactive/plotting.rst index f473bd00160..e53791a1752 100644 --- a/docs/source/interactive/plotting.rst +++ b/docs/source/interactive/plotting.rst @@ -8,60 +8,68 @@ show rich output. This means that object that can be representing as image, sounds, animation, (etc...) can be shown this way if the frontend support it. In order for this to be possible, you need to use the ``display()`` function, -that should be available by default on IPython 5.4+ and 6.1+, or that you can -import with ``from IPython.display import display``. Then use ``display()`` instead of ``print()``, and if possible your object will be displayed -with a richer representation. In the terminal of course, there won't be much -difference as object are most of the time represented by text, but in notebook -and similar interface you will get richer outputs. +which you can import with ``from IPython.display import display``. Then use +``display()`` instead of ``print()``, and if possible your object +will be displayed with a richer representation. In the terminal of course, there +won't be much difference as objects are most of the time represented by text, +but in notebook and similar interface you will get richer outputs. +.. _matplotlib_magic: + Plotting -------- .. note:: - Starting with IPython 5.0 and matplotlib 2.0 you can avoid the use of - IPython's specific magic and use + You can avoid the use of IPython's specific magic and use ``matplotlib.pyplot.ion()``/``matplotlib.pyplot.ioff()`` which have the advantages of working outside of IPython as well. -One major feature of the IPython kernel is the ability to display plots that -are the output of running code cells. The IPython kernel is designed to work +One major feature of the IPython kernel is the ability to display plots that +are the output of running code cells. The IPython kernel is designed to work seamlessly with the matplotlib_ plotting library to provide this functionality. To set this up, before any plotting or import of matplotlib is performed you -must execute the ``%matplotlib`` :ref:`magic command `. This +may execute the ``%matplotlib`` :ref:`magic command `. This performs the necessary behind-the-scenes setup for IPython to work correctly hand in hand with ``matplotlib``; it does *not*, however, actually execute any Python ``import`` commands, that is, no names are added to the namespace. -If the ``%matplotlib`` magic is called without an argument, the -output of a plotting command is displayed using the default ``matplotlib`` -backend in a separate window. Alternatively, the backend can be explicitly -requested using, for example:: - - %matplotlib gtk +If you do not use the ``%matplotlib`` magic or you call it without an argument, +the output of a plotting command is displayed using the default ``matplotlib`` +backend, which may be different depending on Operating System and whether +running within Jupyter or not. -A particularly interesting backend, provided by IPython, is the ``inline`` -backend. This is available only for the Jupyter Notebook and the -Jupyter QtConsole. It can be invoked as follows:: +Alternatively, the backend can be explicitly requested using, for example:: - %matplotlib inline + %matplotlib qt -With this backend, the output of plotting commands is displayed *inline* within -frontends like the Jupyter notebook, directly below the code cell that produced -it. The resulting plots will then also be stored in the notebook document. +The argument passed to the ``%matplotlib`` magic command may be the name of any +backend understood by ``matplotlib`` or it may the name of a GUI loop such as +``qt`` or ``osx``, in which case an appropriate backend supporting that GUI +loop will be selected. To obtain a full list of all backends and GUI loops +understood by ``matplotlib`` use ``%matplotlib --list``. -.. seealso:: +There are some specific backends that are used in the Jupyter ecosystem: - `Plotting with Matplotlib`_ example notebook +- The ``inline`` backend is provided by IPython and can be used in Jupyter Lab, + Notebook and QtConsole; it is the default backend when using Jupyter. The + outputs of plotting commands are displayed *inline* within frontends like + Jupyter Notebook, directly below the code cells that produced them. + The resulting plots will then also be stored in the notebook document. +- The ``notebook`` or ``nbagg`` backend is built into ``matplotlib`` and can be + used with Jupyter ``notebook <7`` and ``nbclassic``. Plots are interactive so + they can be zoomed and panned. -The matplotlib_ library also ships with ``%matplotlib notebook`` command that -allows interactive figures if your environment allows it. +- The ``ipympl`` or ``widget`` backend is for use with Jupyter ``lab`` and + ``notebook >=7``. It is in a separate ``ipympl`` module that must be + installed using ``pip`` or ``conda`` in the usual manner. Plots are + interactive so they can be zoomed and panned. -See the matplotlib_ documentation for more information. +See the matplotlib_ documentation for more information, in particular the +section on backends. .. include:: ../links.txt diff --git a/docs/source/interactive/python-ipython-diff.rst b/docs/source/interactive/python-ipython-diff.rst index 569aebfb17a..0265f2f34c1 100644 --- a/docs/source/interactive/python-ipython-diff.rst +++ b/docs/source/interactive/python-ipython-diff.rst @@ -3,20 +3,20 @@ Python vs IPython ================= This document is meant to highlight the main differences between the Python -language and what are the specific construct you can do only in IPython. +language and what are the specific constructs you can do only in IPython. -Unless expressed otherwise all of the construct you will see here will raise a +Unless expressed otherwise all of the constructs you will see here will raise a ``SyntaxError`` if run in a pure Python shell, or if executing in a Python script. -Each of these features are describe more in details in further part of the documentation. +Each of these features is described more in detail in the further parts of the documentation. Quick overview: =============== -All the following construct are valid IPython syntax: +All the following constructs are valid IPython syntax: .. code-block:: ipython @@ -46,9 +46,9 @@ All the following construct are valid IPython syntax: .. code-block:: ipython In [1]: my_files = !ls ~/ - In [1]: for i,file in enumerate(my_file): + In [1]: for i, file in enumerate(my_files): ...: raw = !echo $file - ...: !echo {files[0].upper()} $raw + ...: !echo {file[0].upper()} $raw .. code-block:: ipython @@ -58,8 +58,8 @@ All the following construct are valid IPython syntax: ...: print $months[0]; -Each of these construct is compile by IPython into valid python code and will -do most of the time what you expect it will do. Let see each of these example +Each of these constructs is compiled by IPython into valid python code and will +do most of the time what you expect it will do. Let's see each of these examples in more detail. @@ -89,7 +89,7 @@ shortcut to get help. A question mark alone will bring up the IPython help: ------------- ... -A single question mark before, or after an object available in current +A single question mark before or after an object available in the current namespace will show help relative to this object: .. code-block:: ipython @@ -105,7 +105,7 @@ and if possible display the python source code of this object. .. code-block:: ipython In[1]: import collections - In[2]: collection.Counter?? + In[2]: collections.Counter?? Init signature: collections.Counter(*args, **kwds) Source: @@ -127,7 +127,7 @@ and if possible display the python source code of this object. If you are looking for an object, the use of wildcards ``*`` in conjunction -with question mark will allow you to search current namespace for object with +with a question mark will allow you to search the current namespace for objects with matching names: .. code-block:: ipython @@ -142,23 +142,17 @@ Shell Assignment ================ -When doing interactive computing it is common to need to access the underlying shell. +When doing interactive computing it is a common need to access the underlying shell. This is doable through the use of the exclamation mark ``!`` (or bang). -This allow to execute simple command when present in beginning of line: +This allows to execute simple commands when present in beginning of the line: .. code-block:: ipython In[1]: !pwd /User/home/ -Change directory: - -.. code-block:: ipython - - In[1]: !cd /var/etc - -Or edit file: +Edit file: .. code-block:: ipython @@ -167,7 +161,7 @@ Or edit file: The line after the bang can call any program installed in the underlying shell, and support variable expansion in the form of ``$variable`` or ``{variable}``. -The later form of expansion supports arbitrary python expression: +The later form of expansion supports arbitrary python expressions: .. code-block:: ipython @@ -176,55 +170,58 @@ The later form of expansion supports arbitrary python expression: In[2]: !mv $file {file.upper()} -The bang can also be present in the right hand side of an assignment, just -after the equal sign, or separated from it by a white space. In which case the -standard output of the command after the bang ``!`` will be split out into lines -in a list-like object and assign to the left hand side. +The bang (``!``) can also be present on the right hand side of an assignment, just +after the equal sign, or separated from it by a white space. In this case the +standard output of the command after the bang will be split out into lines +in a list-like object and assigned to the left hand side. -This allow you for example to put the list of files of the current working directory in a variable: +This allows you, for example, to put the list of files of the current working directory in a variable: .. code-block:: ipython In[1]: my_files = !ls -You can combine the different possibilities in for loops, condition, functions...: +You can combine the different possibilities in for loops, conditions, functions...: .. code-block:: ipython my_files = !ls ~/ - b = "backup file" - for i,file in enumerate(my_file): + for i, file in enumerate(my_files): raw = !echo $backup $file - !cp $file {file.split('.')[0]+'.bak'} + !cp $file {file.split('.')[0] + '.bak'} + + +Each ``!`` gets executed in a separate shell, so changing directory by ``!cd`` or env vars ``!export FOO=bar`` will have no effect. +Use instead the built-in magics ``%cd DIR/`` (there are also ``%pushd DIR/``, ``%dirs``, ``%popd``) and ``%env FOO=bar``. Magics ------ -Magics function are often present in the form of shell-like syntax, but are -under the hood python function. The syntax and assignment possibility are +Magic functions (magics) are often present in the form of shell-like syntax, but they are +python functions under the hood. The syntax and assignment possibilities are similar to the one with the bang (``!``) syntax, but with more flexibility and -power. Magic function start with a percent sign (``%``) or double percent (``%%``). +power. Magic functions start with a percent sign (``%``) or double percent signs (``%%``). -A magic call with a sign percent will act only one line: +A magic call with a single percent sign will act only on one line: .. code-block:: ipython In[1]: %xmode Exception reporting mode: Verbose -And support assignment: +Magics support assignment: .. code-block:: ipython In [1]: results = %timeit -r1 -n1 -o list(range(1000)) - 1 loops, best of 1: 21.1 µs per loop + 62.1 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each) In [2]: results - Out[2]: + -Magic with two percent sign can spread over multiple lines, but do not support assignment: +Magics with double percent signs (``%%``) can spread over multiple lines, but they do not support assignments: .. code-block:: ipython @@ -239,11 +236,3 @@ Magic with two percent sign can spread over multiple lines, but do not support a devfs 190Ki 190Ki 0Bi 100% 656 0 100% /dev map -hosts 0Bi 0Bi 0Bi 100% 0 0 100% /net map auto_home 0Bi 0Bi 0Bi 100% 0 0 100% /hom - - -Combining it all ----------------- - -:: - - find a snippet that combine all that into one thing! diff --git a/docs/source/interactive/reference.rst b/docs/source/interactive/reference.rst index f58f1006cee..41e8a6c3c11 100644 --- a/docs/source/interactive/reference.rst +++ b/docs/source/interactive/reference.rst @@ -11,8 +11,11 @@ You start IPython with the command:: $ ipython [options] files -If invoked with no options, it executes all the files listed in sequence and -exits. If you add the ``-i`` flag, it drops you into the interpreter while still +If invoked with no options, it executes the file and exits, passing the +remaining arguments to the script, just as if you had specified the same +command with python. You may need to specify ``--`` before args to be passed +to the script, to prevent IPython from attempting to parse them. +If you add the ``-i`` flag, it drops you into the interpreter while still acknowledging any options you may have set in your ``ipython_config.py``. This behavior is different from standard Python, which when called as python ``-i`` will only execute one file and ignore your configuration setup. @@ -20,8 +23,8 @@ will only execute one file and ignore your configuration setup. Please note that some of the configuration options are not available at the command line, simply because they are not practical here. Look into your configuration files for details on those. There are separate configuration files -for each profile, and the files look like :file:`ipython_config.py` or -:file:`ipython_config_{frontendname}.py`. Profile directories look like +for each profile, and the files look like :file:`ipython_config.py` (or +:file:`ipython_kernel_config.py` for the kernel). Profile directories look like :file:`profile_{profilename}` and are typically installed in the :envvar:`IPYTHONDIR` directory, which defaults to :file:`$HOME/.ipython`. For Windows users, :envvar:`HOME` resolves to :file:`C:\\Users\\{YourUserName}` in @@ -41,7 +44,7 @@ the command-line by passing the full class name and a corresponding value; type <...snip...> --matplotlib= (InteractiveShellApp.matplotlib) Default: None - Choices: ['auto', 'gtk', 'gtk3', 'inline', 'nbagg', 'notebook', 'osx', 'qt', 'qt4', 'qt5', 'tk', 'wx'] + Choices: ['auto', 'gtk3', 'gtk4', 'inline', 'nbagg', 'notebook', 'osx', 'qt', 'qt5', 'qt6', 'tk', 'wx'] Configure matplotlib for interactive use with the default matplotlib backend. <...snip...> @@ -54,7 +57,7 @@ Indicate that the following:: is equivalent to:: - $ ipython --TerminalIPythonApp.matplotlib='qt' + $ ipython --InteractiveShellApp.matplotlib='qt' Note that in the second form, you *must* use the equal sign, as the expression is evaluated as an actual Python assignment. While in the above example the @@ -77,8 +80,8 @@ prompt. What follows is a list of these. Caution for Windows users ------------------------- -Windows, unfortunately, uses the '\\' character as a path separator. This is a -terrible choice, because '\\' also represents the escape character in most +Windows, unfortunately, uses the ``\`` character as a path separator. This is a +terrible choice, because ``\`` also represents the escape character in most modern programming languages, including Python. For this reason, using '/' character is recommended if you have problems with ``\``. However, in Windows commands '/' flags options, so you can not use it for the root directory. This @@ -114,7 +117,7 @@ The following uses the builtin :magic:`timeit` in cell mode:: ...: min(x) ...: max(x) ...: - 1000 loops, best of 3: 438 us per loop + 518 µs ± 4.39 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) In this case, ``x = range(10000)`` is called as the line argument, and the block with ``min(x)`` and ``max(x)`` is called as the cell body. The @@ -160,7 +163,7 @@ use it: /home/fperez/ipython Line magics, if they return a value, can be assigned to a variable using the -syntax ``l = %sx ls`` (which in this particular case returns the result of `ls` +syntax ``l = %sx ls`` (which in this particular case returns the result of ``ls`` as a python list). See :ref:`below ` for more information. Type ``%magic`` for more information, including a list of all available magic @@ -342,8 +345,8 @@ exclamation marks (``!!ls``) or the :magic:`sx` magic command without an assignm The captured list in this example has some convenience features. ``myfiles.n`` or ``myfiles.s`` returns a string delimited by newlines or spaces, respectively. ``myfiles.p`` -produces `path objects `_ from the list items. -See :ref:`string_lists` for details. +produces :class:`pathlib.Path` objects for the list items which exist on the +filesystem. See :ref:`string_lists` for details. IPython also allows you to expand the value of python variables when making system calls. Wrap variables or expressions in {braces}:: @@ -363,7 +366,7 @@ For simple cases, you can alternatively prepend $ to a variable name:: In [7]: !echo "A system variable: $$HOME" # Use $$ for literal $ A system variable: /home/fperez -Note that `$$` is used to represent a literal `$`. +Note that ``$$`` is used to represent a literal ``$``. System command aliases ---------------------- @@ -419,7 +422,7 @@ to parse visually. See the magic :magic:`xmode` and :magic:`colors` functions for details. These features are basically a terminal version of Ka-Ping Yee's cgitb -module, now part of the standard Python library. +module, formerly part of the standard Python library. .. _input_caching: @@ -549,7 +552,10 @@ but this will work:: In [5]: /zip (1,2,3),(4,5,6) ------> zip ((1,2,3),(4,5,6)) - Out[5]: [(1, 4), (2, 5), (3, 6)] + Out[5]: + + In [6]: list(_) + Out[6]: [(1, 4), (2, 5), (3, 6)] IPython tells you that it has altered your command line by displaying the new command line preceded by ``--->``. @@ -620,27 +626,27 @@ code snippet:: a = 42 IPython.embed() -and within the IPython shell, you reassign `a` to `23` to do further testing of +and within the IPython shell, you reassign ``a`` to ``23`` to do further testing of some sort, you can then exit:: >>> IPython.embed() - Python 3.6.2 (default, Jul 17 2017, 16:44:45) + Python 3.12.0 (default, Oct 10 2023, 12:00:00) Type 'copyright', 'credits' or 'license' for more information - IPython 6.2.0.dev -- An enhanced Interactive Python. Type '?' for help. + IPython 9.0.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: a = 23 In [2]: exit() -Once you exit and print `a`, the value 23 will be shown:: +Once you exit and print ``a``, the value 23 will be shown:: In: print(a) 23 -It's important to note that the code run in the embedded IPython shell will -*not* change the state of your code and variables, **unless** the shell is -contained within the global namespace. In the above example, `a` is changed +It's important to note that the code run in the embedded IPython shell will +*not* change the state of your code and variables, **unless** the shell is +contained within the global namespace. In the above example, ``a`` is changed because this is true. To further exemplify this, consider the following example:: @@ -653,8 +659,8 @@ To further exemplify this, consider the following example:: print(a) Now if call the function and complete the state changes as we did above, the -value `42` will be printed. Again, this is because it's not in the global -namespace:: +value ``42`` will be printed. Again, this is because it's not in the global +namespace:: do() @@ -662,9 +668,9 @@ Running a file with the above code can lead to the following session:: >>> do() 42 - Python 3.6.2 (default, Jul 17 2017, 16:44:45) + Python 3.12.0 (default, Oct 10 2023, 12:00:00) Type 'copyright', 'credits' or 'license' for more information - IPython 6.2.0.dev -- An enhanced Interactive Python. Type '?' for help. + IPython 9.0.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: a = 23 @@ -697,9 +703,14 @@ your Python programs for this to work (detailed examples follow later):: embed() # this call anywhere in your program will start IPython -You can also embed an IPython *kernel*, for use with qtconsole, etc. via -``IPython.embed_kernel()``. This should function work the same way, but you can -connect an external frontend (``ipython qtconsole`` or ``ipython console``), +You can also embed an IPython *kernel*, for use with qtconsole, etc. via:: + + from ipykernel.embed import embed_kernel + + embed_kernel() + +This should work the same way, but you can connect an external frontend +(``jupyter qtconsole`` or ``jupyter console``), rather than interacting with it in the terminal. You can run embedded instances even in code which is itself being run at @@ -714,7 +725,7 @@ them separately, for example with different options for data presentation. If you close and open the same instance multiple times, its prompt counters simply continue from each execution to the next. -Please look at the docstrings in the :mod:`~IPython.frontend.terminal.embed` +Please look at the docstrings in the :mod:`~IPython.terminal.embed` module for more details on the use of this system. The following sample file illustrating how to use the embedding @@ -812,6 +823,65 @@ And less context on shallower Stack Trace: (5)foo() ----> 5 return 1/x+foo(x-1) +Module Ignoring Commands +------------------------ + +IPython's debugger provides commands to ignore specific modules when navigating +through stack frames. This is particularly useful when debugging applications +that use frameworks or libraries where you want to skip over their internal +code and focus on your application logic. + +The module ignoring system supports wildcard patterns using Python's ``fnmatch`` +syntax, allowing you to ignore groups of related modules with a single pattern. + +ignore_module +++++++++++++++++++++++++++++ + +Add a module to the list of modules to skip when navigating frames. When a module +is ignored, the debugger will automatically skip over frames from that module when +using the ``next``, ``step``, ``continue``, ``up`` and ``down`` commands. + +**Usage:** + +.. code:: + + ipdb> ignore_module threading + ipdb> ignore_module __main__ + ipdb> ignore_module asyncio.* # Ignore all asyncio submodules + ipdb> ignore_module *.tests # Ignore all test modules + ipdb> ignore_module django.* # Ignore all django modules + ipdb> ignore_module # List currently ignored modules + Currently ignored modules: ['__main__', 'asyncio.*', 'django.*', 'threading'] + +**Wildcard Pattern Examples:** + +- ``asyncio.*`` - Matches ``asyncio.tasks``, ``asyncio.events``, etc. +- ``*.tests`` - Matches ``myapp.tests``, ``utils.tests``, etc. +- ``test_*`` - Matches ``test_models``, ``test_views``, etc. +- ``django.*`` - Matches all Django framework modules +- ``*pytest*`` - Matches any module containing "pytest" + +**Common use cases:** + +- Ignore framework code (e.g., ``django.*``, ``flask.*``, ``asyncio.*``) +- Skip standard library modules (e.g., ``threading``, ``multiprocessing``, ``logging.*``) +- Hide main module frames (``__main__``) to focus on function implementations +- Skip test framework internals (``*pytest*``, ``unittest.*``) + +unignore_module ++++++++++++++++++++++++++++++ + +Remove a module from the list of modules to skip when navigating frames. This +allows the debugger to step into frames from the specified module again. + +**Usage:** + +.. code:: + + ipdb> unignore_module threading + ipdb> unignore_module asyncio.* # Remove the pattern + ipdb> unignore_module # List currently ignored modules + Currently ignored modules: ['__main__'] Post-mortem debugging --------------------- @@ -834,12 +904,13 @@ put the following lines toward the top of your 'main' routine:: import sys from IPython.core import ultratb sys.excepthook = ultratb.FormattedTB(mode='Verbose', - color_scheme='Linux', call_pdb=1) + theme_name='linux', call_pdb=1) -The mode keyword can be either 'Verbose' or 'Plain', giving either very -detailed or normal tracebacks respectively. The color_scheme keyword can -be one of 'NoColor', 'Linux' (default) or 'LightBG'. These are the same -options which can be set in IPython with ``--colors`` and ``--xmode``. +The mode keyword can be 'Plain', 'Context', 'Verbose' or 'Minimal', giving +increasingly detailed tracebacks. The theme_name keyword can be the name of +any IPython theme, e.g. 'nocolor', 'linux' (default), 'lightbg' or 'neutral'. +These are the same options which can be set in IPython with ``--theme`` and +the :magic:`xmode` magic. This will give any of your programs detailed, colored tracebacks with automatic invocation of pdb. @@ -889,7 +960,7 @@ GUI event loop support ====================== IPython has excellent support for working interactively with Graphical User -Interface (GUI) toolkits, such as wxPython, PyQt4/PySide, PyGTK and Tk. This is +Interface (GUI) toolkits, such as wxPython, PyQt/PySide, PyGTK and Tk. This is implemented by running the toolkit's event loop while IPython is waiting for input. @@ -899,14 +970,15 @@ For users, enabling GUI event loop integration is simple. You simple use the %gui [GUINAME] With no arguments, ``%gui`` removes all GUI support. Valid ``GUINAME`` -arguments include ``wx``, ``qt``, ``qt5``, ``gtk``, ``gtk3`` and ``tk``. +arguments include ``wx``, ``qt``, ``qt5``, ``qt6``, ``gtk3`` ``gtk4``, and +``tk``. Thus, to use wxPython interactively and create a running :class:`wx.App` object, do:: %gui wx -You can also start IPython with an event loop set up using the `--gui` +You can also start IPython with an event loop set up using the ``--gui`` flag:: $ ipython --gui=qt @@ -932,37 +1004,13 @@ PyQt and PySide .. attempt at explanation of the complete mess that is Qt support When you use ``--gui=qt`` or ``--matplotlib=qt``, IPython can work with either -PyQt4 or PySide. There are three options for configuration here, because -PyQt4 has two APIs for QString and QVariant: v1, which is the default on -Python 2, and the more natural v2, which is the only API supported by PySide. -v2 is also the default for PyQt4 on Python 3. IPython's code for the QtConsole -uses v2, but you can still use any interface in your code, since the -Qt frontend is in a different process. - -The default will be to import PyQt4 without configuration of the APIs, thus -matching what most applications would expect. It will fall back to PySide if -PyQt4 is unavailable. - -If specified, IPython will respect the environment variable ``QT_API`` used -by ETS. ETS 4.0 also works with both PyQt4 and PySide, but it requires -PyQt4 to use its v2 API. So if ``QT_API=pyside`` PySide will be used, -and if ``QT_API=pyqt`` then PyQt4 will be used *with the v2 API* for -QString and QVariant, so ETS codes like MayaVi will also work with IPython. - -If you launch IPython in matplotlib mode with ``ipython --matplotlib=qt``, -then IPython will ask matplotlib which Qt library to use (only if QT_API is -*not set*), via the 'backend.qt4' rcParam. If matplotlib is version 1.0.1 or -older, then IPython will always use PyQt4 without setting the v2 APIs, since -neither v2 PyQt nor PySide work. +PyQt or PySide. ``qt`` implies "use the latest version available", and it favors +PyQt over PySide. To request a specific version, use ``qt5`` or ``qt6``. -.. warning:: - - Note that this means for ETS 4 to work with PyQt4, ``QT_API`` *must* be set - to work with IPython's qt integration, because otherwise PyQt4 will be - loaded in an incompatible mode. - - It also means that you must *not* have ``QT_API`` set if you want to - use ``--gui=qt`` with code that requires PyQt4 API v1. +If specified, IPython will respect the environment variable ``QT_API``. If +``QT_API`` is not specified and you launch IPython in matplotlib mode with +``ipython --matplotlib=qt`` then IPython will ask matplotlib which Qt library +to use. See the matplotlib_ documentation on ``QT_API`` for further details. .. _matplotlib_support: @@ -972,19 +1020,16 @@ Plotting with matplotlib matplotlib_ provides high quality 2D and 3D plotting for Python. matplotlib_ can produce plots on screen using a variety of GUI toolkits, including Tk, -PyGTK, PyQt4 and wxPython. It also provides a number of commands useful for +PyGTK, PyQt6 and wxPython. It also provides a number of commands useful for scientific computing, all with a syntax compatible with that of the popular Matlab program. To start IPython with matplotlib support, use the ``--matplotlib`` switch. If IPython is already running, you can run the :magic:`matplotlib` magic. If no arguments are given, IPython will automatically detect your choice of -matplotlib backend. You can also request a specific backend with -``%matplotlib backend``, where ``backend`` must be one of: 'tk', 'qt', 'wx', -'gtk', 'osx'. In the web notebook and Qt console, 'inline' is also a valid -backend value, which produces static figures inlined inside the application -window instead of matplotlib's interactive figures that live in separate -windows. +matplotlib backend. For information on matplotlib backends see +:ref:`matplotlib_magic`. + .. _interactive_demos: diff --git a/docs/source/interactive/shell.rst b/docs/source/interactive/shell.rst index 420f5a53ba1..eaa5d690677 100644 --- a/docs/source/interactive/shell.rst +++ b/docs/source/interactive/shell.rst @@ -1,3 +1,9 @@ + +.. note:: + + This page has been kept for historical reason. You most likely want to use + `Xonsh `__ instead of this. + .. _ipython_as_shell: ========================= @@ -44,6 +50,10 @@ so you should be able to type any normal system command and have it executed. See ``%alias?`` and ``%unalias?`` for details on the alias facilities. See also ``%rehashx?`` for details on the mechanism used to load $PATH. +.. warning:: + + See info at the top of the page. You most likely want to use + `Xonsh `__ instead of this. Directory management ==================== @@ -71,7 +81,7 @@ See :ref:`custom_prompts`. String lists ============ -String lists (IPython.utils.text.SList) are handy way to process output +String lists (:any:`IPython.utils.text.SList`) are handy way to process output from system commands. They are produced by ``var = !cmd`` syntax. First, we acquire the output of 'ls -l':: @@ -186,13 +196,18 @@ First, capture output of "hg status":: Now we can just remove these files by doing 'rm $junk.s'. -The .s, .n, .p properties +The .n, .s, .p properties ------------------------- -The ``.s`` property returns one string where lines are separated by -single space (for convenient passing to system commands). The ``.n`` -property return one string where the lines are separated by a newline -(i.e. the original output of the function). If the items in string -list are file names, ``.p`` can be used to get a list of "path" objects -for convenient file manipulation. +Properties of :py:class:`SList ` wrapper +provide a convenient ways to use contained text in different formats: + +* ``.n`` returns (original) string with lines separated by a newline +* ``.s`` returns string with lines separated by single space (for + convenient passing to system commands) +* ``.p`` returns list of "path" objects from detected file names + +.. error:: + You went too far scroll back up. You most likely want to use + `Xonsh `__ instead of this. diff --git a/docs/source/interactive/tips.rst b/docs/source/interactive/tips.rst index c6cf3cbcf2d..d35e21e53c0 100644 --- a/docs/source/interactive/tips.rst +++ b/docs/source/interactive/tips.rst @@ -5,10 +5,8 @@ IPython Tips & Tricks ===================== The `IPython cookbook -`_ details more things -you can do with IPython. - -.. This is not in the current version: +`_ details more +things you can do with IPython. Embed IPython in your programs @@ -22,12 +20,12 @@ Run doctests ------------ Run your doctests from within IPython for development and debugging. The -special %doctest_mode command toggles a mode where the prompt, output and +special ``%doctest_mode`` command toggles a mode where the prompt, output and exceptions display matches as closely as possible that of the default Python interpreter. In addition, this mode allows you to directly paste in code that contains leading '>>>' prompts, even if they have extra leading whitespace -(as is common in doctest files). This combined with the ``%history -t`` call -to see your translated history allows for an easy doctest workflow, where you +(as is common in doctest files). This combined with the ``%hist -t`` call to +see your translated history allows for an easy doctest workflow, where you can go from doctest to interactive execution to pasting into valid Python code as needed. @@ -99,4 +97,3 @@ Continuing with the example above, this should illustrate this idea:: This section was written after a contribution by Alexander Belchenko on the IPython user list. - diff --git a/docs/source/interactive/tutorial.rst b/docs/source/interactive/tutorial.rst index 5a70345190b..1f87cdbdabf 100644 --- a/docs/source/interactive/tutorial.rst +++ b/docs/source/interactive/tutorial.rst @@ -10,18 +10,17 @@ more than the standard prompt. Some key features are described here. For more information, check the :ref:`tips page `, or look at examples in the `IPython cookbook `_. -If you haven't done that yet see `how to install ipython `_ . +If you haven't done that yet see :ref:`how to install ipython `. -If you've never used Python before, you might want to look at `the official -tutorial `_ or an alternative, `Dive into -Python `_. +If you've never used Python before, you might want to look at the official +:external+python:doc:`Python tutorial `. Start IPython by issuing the ``ipython`` command from your shell, you should be greeted by the following:: - Python 3.6.0 + Python 3.12.0 Type 'copyright', 'credits' or 'license' for more information - IPython 6.0.0.dev -- An enhanced Interactive Python. Type '?' for help. + IPython 9.0.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: @@ -59,7 +58,7 @@ while some other do not. We'll come to this later. Depending on the exact command you are typing you might realize that sometimes :kbd:`Enter` will add a new line, and sometimes it will execute the current statement. IPython tries to guess what you are doing, so most of the time you -should not have to care. Though if by any chance IPython does not the right +should not have to care. Though if by any chance IPython does not do the right thing you can force execution of the current code block by pressing in sequence :kbd:`Esc` and :kbd:`Enter`. You can also force the insertion of a new line at the position of the cursor by using :kbd:`Ctrl-o`. @@ -67,8 +66,8 @@ the position of the cursor by using :kbd:`Ctrl-o`. The four most helpful commands ============================== -The four most helpful commands, as well as their brief description, is shown -to you in a banner, every time you start IPython: +Four commands are especially helpful; the banner shown every time you start +IPython points you to the first one (``?``): ========== ========================================================= command description @@ -87,11 +86,11 @@ structure of any object you're dealing with. Simply type ``object_name.`` to view the object's attributes. Besides Python objects and keywords, tab completion also works on file and directory names. -Starting with IPython 6.0, if ``jedi`` is installed, IPython will try to pull -completions from Jedi as well. This allows to not only inspect currently -existing objects, but also to infer completion statically without executing -code. There is nothing particular need to get this to work, simply use tab -completion on more complex expressions like the following:: +IPython also pulls completions from ``jedi`` (installed as one of its +dependencies). This allows to not only inspect currently existing objects, but also to +infer completion statically without executing code. There is nothing particular +needed to get this to work, simply use tab completion on more complex +expressions like the following:: >>> data = ['Number of users', 123456] ... data[0]. @@ -120,25 +119,25 @@ IPython has a set of predefined 'magic functions' that you can call with a command line style syntax. There are two kinds of magics, line-oriented and cell-oriented. **Line magics** are prefixed with the ``%`` character and work much like OS command-line calls: they get as an argument the rest of the line, -where arguments are passed without parentheses or quotes. **Lines magics** can +where arguments are passed without parentheses or quotes. **Line magics** can return results and can be used in the right hand side of an assignment. **Cell magics** are prefixed with a double ``%%``, and they are functions that get as an argument not only the rest of the line, but also the lines below it in a separate argument. Magics are useful as convenient functions where Python syntax is not the most -natural one, or when one want to embed invalid python syntax in their work flow. +natural one, or when one want to embed invalid python syntax in their work flow. The following examples show how to call the built-in :magic:`timeit` magic, both in line and cell mode:: In [1]: %timeit range(1000) - 100000 loops, best of 3: 7.76 us per loop + 179 ns ± 2.66 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) In [2]: %%timeit x = range(10000) ...: max(x) - ...: - 1000 loops, best of 3: 223 us per loop + ...: + 264 µs ± 1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) The built-in magics include: @@ -171,8 +170,6 @@ read its docstring. To see all the available magic functions, call the magics works and how to define your own, and :doc:`magics` for a list of built-in magics. - `Cell magics`_ example notebook - Running and Editing ------------------- @@ -221,12 +218,12 @@ You can use the ``%history`` magic function to examine past input and output. Input history from previous sessions is saved in a database, and IPython can be configured to save output history. -Several other magic functions can use your input history, including ``%edit``, +Several other magic functions can use your input history, including ``%edit``, ``%rerun``, ``%recall``, ``%macro``, ``%save`` and ``%pastebin``. You can use a standard format to refer to lines:: %pastebin 3 18-20 ~1/1-5 - + This will take line 3 and lines 18 to 20 from the current session, and lines 1-5 from the previous session. @@ -236,7 +233,7 @@ System shell commands To run any command at the system shell, simply prefix it with ``!``, e.g.:: !ping www.bbc.co.uk - + You can capture the output into a Python list, e.g.: ``files = !ls``. To pass the values of Python variables or expressions to system commands, prefix them with $: ``!grep -rF $pattern ipython/*`` or wrap in `{braces}`. See :ref:`our diff --git a/docs/source/links.txt b/docs/source/links.txt index 9379d1f9357..bdcf66e4ecb 100644 --- a/docs/source/links.txt +++ b/docs/source/links.txt @@ -2,14 +2,14 @@ and name substitutions. It may be included in many files, therefore it should only contain link targets and name substitutions. Try grepping for "^\.\. _" to find plausible - candidates for this list. + candidates for this list. NOTE: this file must have an extension *opposite* to that of the main reST files in the manuals, so that we can include it with ".. include::" directives, but without triggering warnings from Sphinx for not being listed in any toctree. Since IPython uses .txt for the main files, this one will use .rst. - + NOTE: reST targets are __not_case_sensitive__, so only one target definition is needed for ipython, IPython, etc. @@ -18,84 +18,50 @@ .. Main IPython links .. _ipython: https://ipython.org -.. _`ipython manual`: https://ipython.org/documentation.html -.. _ipython_github: http://github.com/ipython/ipython/ -.. _ipython_github_repo: http://github.com/ipython/ipython/ -.. _ipython_downloads: https://ipython.org/download.html -.. _ipython_pypi: http://pypi.python.org/pypi/ipython -.. _nbviewer: http://nbviewer.ipython.org +.. _ipython_github: https://github.com/ipython/ipython/ +.. _nbviewer: https://nbviewer.org -.. _ZeroMQ: http://zeromq.org +.. _ZeroMQ: https://zeromq.org .. Documentation tools and related links -.. _graphviz: http://www.graphviz.org -.. _Sphinx: http://sphinx.pocoo.org -.. _`Sphinx reST`: http://sphinx.pocoo.org/rest.html -.. _sampledoc: http://matplotlib.org/sampledoc -.. _reST: http://docutils.sourceforge.net/rst.html -.. _docutils: http://docutils.sourceforge.net -.. _lyx: http://www.lyx.org -.. _pep8: http://www.python.org/dev/peps/pep-0008 -.. _numpy_coding_guide: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt +.. _graphviz: https://www.graphviz.org +.. _Sphinx: https://www.sphinx-doc.org +.. _`Sphinx reST`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html +.. _sampledoc: https://matplotlib.org/sampledoc +.. _reST: https://docutils.sourceforge.io/rst.html +.. _docutils: https://docutils.sourceforge.io +.. _lyx: https://www.lyx.org +.. _pep8: https://peps.python.org/pep-0008/ .. Licenses -.. _GPL: http://www.gnu.org/licenses/gpl.html -.. _BSD: http://www.opensource.org/licenses/bsd-license.php -.. _LGPL: http://www.gnu.org/copyleft/lesser.html +.. _GPL: https://www.gnu.org/licenses/gpl.html +.. _BSD: https://opensource.org/licenses/BSD-3-Clause +.. _LGPL: https://www.gnu.org/copyleft/lesser.html .. Other python projects -.. _numpy: http://numpy.scipy.org -.. _scipy: http://www.scipy.org -.. _scipy_conference: http://conference.scipy.org -.. _matplotlib: http://matplotlib.org -.. _pythonxy: https://code.google.com/p/pythonxy/ -.. _ETS: http://code.enthought.com/projects/tool-suite.php -.. _EPD: http://www.enthought.com/products/epd.php -.. _python: http://www.python.org -.. _mayavi: http://code.enthought.com/projects/mayavi -.. _sympy: http://code.google.com/p/sympy -.. _sage: http://sagemath.org -.. _pydy: http://code.google.com/p/pydy -.. _vpython: http://vpython.org -.. _cython: http://cython.org -.. _software carpentry: http://software-carpentry.org +.. _numpy: https://numpy.org +.. _scipy: https://www.scipy.org +.. _scipy_conference: https://conference.scipy.org +.. _matplotlib: https://matplotlib.org +.. _python: https://www.python.org +.. _mayavi: https://docs.enthought.com/mayavi/mayavi/ +.. _sympy: https://www.sympy.org +.. _sage: https://www.sagemath.org +.. _pydy: https://www.pydy.org +.. _vpython: https://vpython.org +.. _cython: https://cython.org +.. _software carpentry: https://software-carpentry.org .. Not so python scientific computing tools -.. _matlab: http://www.mathworks.com -.. _VTK: http://vtk.org +.. _matlab: https://www.mathworks.com +.. _VTK: https://vtk.org .. Other organizations -.. _enthought: http://www.enthought.com -.. _kitware: http://www.kitware.com -.. _netlib: http://netlib.org +.. _enthought: https://www.enthought.com +.. _kitware: https://www.kitware.com +.. _netlib: https://netlib.org .. Other tools and projects -.. _indefero: http://www.indefero.net -.. _git: http://git-scm.com -.. _github: http://github.com -.. _Markdown: http://daringfireball.net/projects/markdown/syntax - -.. _Running Code in the IPython Notebook: notebook_p1_ -.. _notebook_p1: http://nbviewer.ipython.org/urls/raw.github.com/ipython/ipython/1.x/examples/notebooks/Part%25201%2520-%2520Running%2520Code.ipynb - -.. _Basic Output: notebook_p2_ -.. _notebook_p2: http://nbviewer.ipython.org/urls/raw.github.com/ipython/ipython/1.x/examples/notebooks/Part%202%20-%20Basic%20Output.ipynb - -.. _Plotting with Matplotlib: notebook_p3_ -.. _notebook_p3: http://nbviewer.ipython.org/urls/raw.github.com/ipython/ipython/1.x/examples/notebooks/Part%203%20-%20Plotting%20with%20Matplotlib.ipynb - -.. _Markdown Cells: notebook_p4_ -.. _notebook_p4: http://nbviewer.ipython.org/urls/raw.github.com/ipython/ipython/1.x/examples/notebooks/Part%204%20-%20Markdown%20Cells.ipynb - -.. _Rich Display System: notebook_p5_ -.. _notebook_p5: http://nbviewer.ipython.org/urls/raw.github.com/ipython/ipython/1.x/examples/notebooks/Part%205%20-%20Rich%20Display%20System.ipynb - -.. _notebook_custom_display: http://nbviewer.ipython.org/urls/raw.github.com/ipython/ipython/1.x/examples/notebooks/Custom%20Display%20Logic.ipynb - -.. _Frontend/Kernel Model: notebook_two_proc_ -.. _notebook_two_proc: http://nbviewer.ipython.org/urls/raw.github.com/ipython/ipython/1.x/examples/notebooks/Frontend-Kernel%20Model.ipynb - -.. _Cell magics: notebook_cell_magics_ -.. _notebook_cell_magics: http://nbviewer.ipython.org/urls/raw.github.com/ipython/ipython/1.x/examples/notebooks/Cell%20Magics.ipynb - - +.. _git: https://git-scm.com +.. _github: https://github.com +.. _Markdown: https://daringfireball.net/projects/markdown/syntax diff --git a/docs/source/overview.rst b/docs/source/overview.rst index bf8654e9e65..73901a2b7bb 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -38,19 +38,19 @@ amongst others: tries to be a very efficient environment both for Python code development and for exploration of problems using Python objects (in situations like data analysis). - + 2. Serve as an embeddable, ready to use interpreter for your own programs. An interactive IPython shell can be started with a single call from inside another program, providing access to the current namespace. This can be very useful both for debugging purposes and for situations where a blend of batch-processing and interactive exploration are needed. - + 3. Offer a flexible framework which can be used as the base environment for working with other systems, with Python as the underlying bridge language. Specifically scientific environments like Mathematica, IDL and Matlab inspired its design, but similar ideas can be useful in many fields. - + 4. Allow interactive testing of threaded graphical toolkits. IPython has support for interactive, non-blocking control of GTK, Qt, WX, GLUT, and OS X applications via special threading flags. The normal Python @@ -63,7 +63,7 @@ Main features of the interactive shell definition prototypes, source code, source files and other details of any object accessible to the interpreter with a single keystroke (:samp:`?`, and using :samp:`??` provides additional detail). - + * Searching through modules and namespaces with :samp:`*` wildcards, both when using the :samp:`?` system and via the :samp:`%psearch` command. @@ -85,7 +85,7 @@ Main features of the interactive shell * Alias facility for defining your own system aliases. * Complete system shell access. Lines starting with :samp:`!` are passed - directly to the system shell, and using :samp:`!!` or :samp:`var = !cmd` + directly to the system shell, and using :samp:`!!` or :samp:`var = !cmd` captures shell output into python variables for further use. * The ability to expand python variables when calling the system shell. In a @@ -167,21 +167,20 @@ Main features of the interactive shell * Simple timing information. You can use the :samp:`%timeit` command to get the execution time of a Python statement or expression. This machinery is intelligent enough to do more repetitions for commands that finish very - quickly in order to get a better estimate of their running time. + quickly in order to get a better estimate of their running time. -.. sourcecode:: ipython + .. sourcecode:: ipython In [1]: %timeit 1+1 - 10000000 loops, best of 3: 25.5 ns per loop + 7.88 ns ± 0.0494 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each) In [2]: %timeit [math.sin(x) for x in range(5000)] - 1000 loops, best of 3: 719 µs per loop + 608 µs ± 5.57 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) -.. + .. To get the timing information for more than one expression, use the :samp:`%%timeit` cell magic command. - * Doctest support. The special :samp:`%doctest_mode` command toggles a mode to use doctest-compatible prompts, so you can use IPython sessions as @@ -208,7 +207,7 @@ of the `Jupyter` project, which includes ``jupyter console``, ``jupyter qtconsole``, and ``jupyter notebook``. As an example, this means that when you start ``jupyter qtconsole``, you're -really starting two processes, a kernel and a Qt-based client can send +really starting two processes, a kernel and a Qt-based client which can send commands to and receive results from that kernel. If there is already a kernel running that you want to connect to, you can pass the ``--existing`` flag which will skip initiating a new kernel and connect to the most recent kernel, @@ -218,39 +217,30 @@ which will be something like ``--existing kernel-19732.json`` but with different numbers which correspond to the Process ID of the kernel. You can read more about using `jupyter qtconsole -`_, and -`jupyter notebook `_. There +`_, and +`jupyter notebook `_. There is also a :ref:`message spec ` which documents the protocol for communication between kernels and clients. -.. seealso:: - - `Frontend/Kernel Model`_ example notebook - - Interactive parallel computing ============================== This functionality is optional and now part of the `ipyparallel -`_ project. +`_ project. Portability and Python requirements ----------------------------------- -Version 7.0+ supports Python 3.4 and higher. -Versions 6.x support Python 3.3 and higher. -Versions 2.0 to 5.x work with Python 2.7.x releases and Python 3.3 and higher. -Version 1.0 additionally worked with Python 2.6 and 3.2. -Version 0.12 was the first version to fully support Python 3. +IPython requires Python 3.11 or newer. IPython is known to work on the following operating systems: * Linux - * Most other Unix-like OSs (AIX, Solaris, BSD, etc.) - * Mac OS X - * Windows (CygWin, XP, Vista, etc.) + * macOS + * Windows + * Most other Unix-like OSs (BSD, Solaris, etc.) See :ref:`here ` for instructions on how to install IPython. diff --git a/docs/source/sphinx.toml b/docs/source/sphinx.toml new file mode 100644 index 00000000000..f5e5a264556 --- /dev/null +++ b/docs/source/sphinx.toml @@ -0,0 +1,41 @@ +[sphinx] +templates_path = ["_templates"] +master_doc = "index" +project = "IPython" +copyright = "The IPython Development Team" +github_project_url = "https://github.com/ipython/ipython" +exclude_patterns = ["**.ipynb_checkpoints", "config/options/terminal.rst"] +pygments_style = "sphinx" +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.inheritance_diagram", + "sphinx.ext.intersphinx", + "sphinx.ext.graphviz", + "sphinx.ext.extlinks", # for easy GitHub links + "sphinxcontrib.jquery", + "IPython.sphinxext.ipython_console_highlighting", + "IPython.sphinxext.ipython_directive", + "sphinx.ext.napoleon", # to preprocess docstrings + "magics", + "configtraits", +] +default_role = "literal" +modindex_common_prefix = ["IPython."] + +[sphinx.source_suffix] +".rst" = "restructuredtext" + +[html] +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] +html_favicon = "_static/favicon.ico" +html_last_updated_fmt = "%b %d, %Y" +htmlhelp_basename = "ipythondoc" +html_additional_pages = [ + ["interactive/htmlnotebook", "notebook_redirect.html"], + ["interactive/notebook", "notebook_redirect.html"], + ["interactive/nbconvert", "notebook_redirect.html"], + ["interactive/public_server", "notebook_redirect.html"] +] diff --git a/docs/source/sphinxext.rst b/docs/source/sphinxext.rst index ef000e31d50..49c46ecf8b7 100644 --- a/docs/source/sphinxext.rst +++ b/docs/source/sphinxext.rst @@ -1,6 +1,521 @@ + +.. _ipython_directive: + ======================== -IPython Sphinx extension +IPython Sphinx Directive ======================== +.. note:: + + The IPython Sphinx Directive is in 'beta' and currently under + active development. Improvements to the code or documentation are welcome! + +.. |rst| replace:: reStructured text + +The :rst:dir:`ipython` directive is a stateful shell that can be used +in |rst| files. + +It knows about standard ipython prompts, and extracts the input and output +lines. These prompts will be renumbered starting at ``1``. The inputs will be +fed to an embedded ipython interpreter and the outputs from that interpreter +will be inserted as well. For example, code blocks like the following:: + + .. ipython:: + + In [136]: x = 2 + + In [137]: x**3 + Out[137]: 8 + +will be rendered as + +.. ipython:: + + In [136]: x = 2 + + In [137]: x**3 + Out[137]: 8 + +.. note:: + + This tutorial should be read side-by-side with the Sphinx source + for this document because otherwise you will see only the rendered + output and not the code that generated it. Excepting the example + above, we will not in general be showing the literal ReST in this + document that generates the rendered output. + + +Directive and options +===================== + +The IPython directive takes a number of options detailed here. + +.. rst:directive:: ipython + + Create an IPython directive. + + .. rst:directive:option:: doctest + + Run a doctest on IPython code blocks in rst. + + .. rst:directive:option:: python + + Used to indicate that the relevant code block does not have IPython prompts. + + .. rst:directive:option:: okexcept + + Allow the code block to raise an exception. + + .. rst:directive:option:: okwarning + + Allow the code block to emit an warning. + + .. rst:directive:option:: suppress + + Silence any warnings or expected errors. + + .. rst:directive:option:: verbatim + + A noop that allows for any text to be syntax highlighted as valid IPython code. + + .. rst:directive:option:: savefig: OUTFILE [IMAGE_OPTIONS] + + Save output from matplotlib to *outfile*. + +It's important to note that all of these options can be used for the entire +directive block or they can decorate individual lines of code as explained +in :ref:`pseudo-decorators`. + + +Persisting the Python session across IPython directive blocks +============================================================= + +The state from previous sessions is stored, and standard error is +trapped. At doc build time, ipython's output and std err will be +inserted, and prompts will be renumbered. So the prompt below should +be renumbered in the rendered docs, and pick up where the block above +left off. + +.. ipython:: + :verbatim: + + In [138]: z = x*3 # x is recalled from previous block + + In [139]: z + Out[139]: 6 + + In [142]: print(z) + 6 + + In [141]: q = z[) # this is a syntax error -- we trap ipy exceptions + ------------------------------------------------------------ + File "", line 1 + q = z[) # this is a syntax error -- we trap ipy exceptions + ^ + SyntaxError: invalid syntax + + +Adding documentation tests to your IPython directive +==================================================== + +The embedded interpreter supports some limited markup. For example, +you can put comments in your ipython sessions, which are reported +verbatim. There are some handy "pseudo-decorators" that let you +doctest the output. The inputs are fed to an embedded ipython +session and the outputs from the ipython session are inserted into +your doc. If the output in your doc and in the ipython session don't +match on a doctest assertion, an error will occur. + + +.. ipython:: + + In [1]: x = 'hello world' + + # this will raise an error if the ipython output is different + @doctest + In [2]: x.upper() + Out[2]: 'HELLO WORLD' + + # some readline features cannot be supported, so we allow + # "verbatim" blocks, which are dumped in verbatim except prompts + # are continuously numbered + @verbatim + In [3]: x.st + x.startswith x.strip + +For more information on @doctest decorator, please refer to the end of this page in Pseudo-Decorators section. + +Multi-line input +================ + +Multi-line input is supported. + +.. ipython:: + :verbatim: + + In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ + .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' + + In [131]: print(url.split('&')) + ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', + +Testing directive outputs +========================= + +The IPython Sphinx Directive makes it possible to test the outputs that you provide with your code. To do this, +decorate the contents in your directive block with one of the options listed +above. + +If an IPython doctest decorator is found, it will take these steps when your documentation is built: + +1. Run the *input* lines in your IPython directive block against the current Python kernel (remember that the session +persists across IPython directive blocks); + +2. Compare the *output* of this with the output text that you've put in the IPython directive block (what comes +after `Out[NN]`); + +3. If there is a difference, the directive will raise an error and your documentation build will fail. + +You can do doctesting on multi-line output as well. Just be careful +when using non-deterministic inputs like random numbers in the ipython +directive, because your inputs are run through a live interpreter, so +if you are doctesting random output you will get an error. Here we +"seed" the random number generator for deterministic output, and we +suppress the seed line so it doesn't show up in the rendered output + +.. ipython:: + + In [133]: import numpy.random + + @suppress + In [134]: numpy.random.seed(2358) + + @doctest + In [135]: numpy.random.rand(10,2) + Out[135]: + array([[0.64524308, 0.59943846], + [0.47102322, 0.8715456 ], + [0.29370834, 0.74776844], + [0.99539577, 0.1313423 ], + [0.16250302, 0.21103583], + [0.81626524, 0.1312433 ], + [0.67338089, 0.72302393], + [0.7566368 , 0.07033696], + [0.22591016, 0.77731835], + [0.0072729 , 0.34273127]]) + +For more information on @suppress and @doctest decorators, please refer to the end of this file in +Pseudo-Decorators section. + +Another demonstration of multi-line input and output + +.. ipython:: + :verbatim: + + In [106]: print(x) + jdh + + In [109]: for i in range(10): + .....: print(i) + .....: + .....: + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + +Most of the "pseudo-decorators" can be used an options to ipython +mode. For example, to setup matplotlib pylab but suppress the output, +you can do. When using the matplotlib ``use`` directive, it should +occur before any import of pylab. This will not show up in the +rendered docs, but the commands will be executed in the embedded +interpreter and subsequent line numbers will be incremented to reflect +the inputs:: + + + .. ipython:: + :suppress: + :okexcept: + + In [144]: from matplotlib.pylab import * + + In [145]: ion() + +.. ipython:: + :suppress: + :okexcept: + + In [144]: from matplotlib.pylab import * + + In [145]: ion() + +Likewise, you can set ``:doctest:`` or ``:verbatim:`` to apply these +settings to the entire block. For example, + +.. ipython:: + :verbatim: + + In [9]: cd mpl/examples/ + /home/jdhunter/mpl/examples + + In [10]: pwd + Out[10]: '/home/jdhunter/mpl/examples' + + + In [14]: cd mpl/examples/ + mpl/examples/animation/ mpl/examples/misc/ + mpl/examples/api/ mpl/examples/mplot3d/ + mpl/examples/axes_grid/ mpl/examples/pylab_examples/ + mpl/examples/event_handling/ mpl/examples/widgets + + In [14]: cd mpl/examples/widgets/ + /home/msierig/mpl/examples/widgets + + In [15]: !wc * + 2 12 77 README.txt + 40 97 884 buttons.py + 26 90 712 check_buttons.py + 19 52 416 cursor.py + 180 404 4882 menu.py + 16 45 337 multicursor.py + 36 106 916 radio_buttons.py + 48 226 2082 rectangle_selector.py + 43 118 1063 slider_demo.py + 40 124 1088 span_selector.py + 450 1274 12457 total + +You can create one or more pyplot plots and insert them with the +``@savefig`` decorator. + +For more information on @savefig decorator, please refer to the end of this page in Pseudo-Decorators section. + +.. ipython:: + + @savefig plot_simple.png width=4in + In [151]: plot([1,2,3]); + + # use a semicolon to suppress the output + @savefig hist_simple.png width=4in + In [151]: hist(np.random.randn(10000), 100); + +In a subsequent session, we can update the current figure with some +text, and then resave + +.. ipython:: + + + In [151]: ylabel('number') + + In [152]: title('normal distribution') + + @savefig hist_with_text.png width=4in + In [153]: grid(True) + +You can also have function definitions included in the source. + +.. ipython:: + + In [3]: def square(x): + ...: """ + ...: An overcomplicated square function as an example. + ...: """ + ...: if x < 0: + ...: x = abs(x) + ...: y = x * x + ...: return y + ...: + +Then call it from a subsequent section. + +.. ipython:: + + In [4]: square(3) + Out [4]: 9 + + In [5]: square(-2) + Out [5]: 4 + + +Writing Pure Python Code +------------------------ + +Pure python code is supported by the optional argument `python`. In this pure +python syntax you do not include the output from the python interpreter. The +following markup:: + + .. ipython:: python + + foo = 'bar' + print(foo) + foo = 2 + foo**2 + +Renders as + +.. ipython:: python + + foo = 'bar' + print(foo) + foo = 2 + foo**2 + +We can even plot from python, using the savefig decorator, as well as, suppress +output with a semicolon + +.. ipython:: python + + @savefig plot_simple_python.png width=4in + plot([1,2,3]); + +For more information on @savefig decorator, please refer to the end of this page in Pseudo-Decorators section. + +Similarly, std err is inserted + +.. ipython:: python + :okexcept: + + foo = 'bar' + foo[) + +Handling Comments +================== + +Comments are handled and state is preserved + +.. ipython:: python + + # comments are handled + print(foo) + +If you don't see the next code block then the options work. + +.. ipython:: python + :suppress: + :okexcept: + + ioff() + ion() + +Splitting Python statements across lines +======================================== + +Multi-line input is handled. + +.. ipython:: python + + line = 'Multi\ + line &\ + support &\ + works' + print(line.split('&')) + +Functions definitions are correctly parsed + +.. ipython:: python + + def square(x): + """ + An overcomplicated square function as an example. + """ + if x < 0: + x = abs(x) + y = x * x + return y + +And persist across sessions + +.. ipython:: python + + print(square(3)) + print(square(-2)) + +Pretty much anything you can do with the ipython code, you can do with +a simple python script. Obviously, though it doesn't make sense +to use the doctest option. + +.. _pseudo-decorators: + +Pseudo-Decorators +================= + +Here are the supported decorators, and any optional arguments they +take. Some of the decorators can be used as options to the entire +block (eg ``verbatim`` and ``suppress``), and some only apply to the +line just below them (eg ``savefig``). + +@suppress + + execute the ipython input block, but suppress the input and output + block from the rendered output. Also, can be applied to the entire + ``.. ipython`` block as a directive option with ``:suppress:``. + +@verbatim + + insert the input and output block in verbatim, but auto-increment + the line numbers. Internally, the interpreter will be fed an empty + string, so it is a no-op that keeps line numbering consistent. + Also, can be applied to the entire ``.. ipython`` block as a + directive option with ``:verbatim:``. + +@savefig OUTFILE [IMAGE_OPTIONS] + + save the figure to the static directory and insert it into the + document, possibly binding it into a minipage and/or putting + code/figure label/references to associate the code and the + figure. Takes args to pass to the image directive (*scale*, + *width*, etc can be kwargs); see `image options + `_ + for details. + +@doctest + + Compare the pasted in output in the ipython block with the output + generated at doc build time, and raise errors if they don't + match. Also, can be applied to the entire ``.. ipython`` block as a + directive option with ``:doctest:``. + +Configuration Options +===================== + +ipython_savefig_dir + + The directory in which to save the figures. This is relative to the + Sphinx source directory. The default is ``savefig``. + +ipython_rgxin + + The compiled regular expression to denote the start of IPython input + lines. The default is `re.compile('In \[(\d+)\]:\s?(.*)\s*')`. You + shouldn't need to change this. + +ipython_rgxout + + The compiled regular expression to denote the start of IPython output + lines. The default is `re.compile('Out\[(\d+)\]:\s?(.*)\s*')`. You + shouldn't need to change this. + + +ipython_promptin + + The string to represent the IPython input prompt in the generated ReST. + The default is `'In [%d]:'`. This expects that the line numbers are used + in the prompt. + +ipython_promptout + + The string to represent the IPython prompt in the generated ReST. The + default is `'Out[%d]:'`. This expects that the line numbers are used + in the prompt. + + +Automatically generated documentation +===================================== .. automodule:: IPython.sphinxext.ipython_directive diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index 532ba742778..cd827b459d7 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -11,11 +11,6 @@ This document describes in-flight development work. `docs/source/whatsnew/pr` folder -Released .... ...., 2017 - - -Need to be updated: - .. toctree:: :maxdepth: 2 :glob: @@ -25,7 +20,6 @@ Need to be updated: .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. - Backwards incompatible changes ------------------------------ diff --git a/docs/source/whatsnew/github-stats-0.11.rst b/docs/source/whatsnew/github-stats-0.11.rst deleted file mode 100644 index 2f647ce7414..00000000000 --- a/docs/source/whatsnew/github-stats-0.11.rst +++ /dev/null @@ -1,528 +0,0 @@ -.. _issues_list_011: - -Issues closed in the 0.11 development cycle -=========================================== - -In this cycle, we closed a total of 511 issues, 226 pull requests and 285 -regular issues; this is the full list (generated with the script -`tools/github_stats.py`). We should note that a few of these were made on the -0.10.x series, but we have no automatic way of filtering the issues by branch, -so this reflects all of our development over the last two years, including work -already released in 0.10.2: - -Pull requests (226): - -* `620 `_: Release notes and updates to GUI support docs for 0.11 -* `642 `_: fix typo in docs/examples/vim/README.rst -* `631 `_: two-way vim-ipython integration -* `637 `_: print is a function, this allows to properly exit ipython -* `635 `_: support html representations in the notebook frontend -* `639 `_: Updating the credits file -* `628 `_: import pexpect from IPython.external in irunner -* `596 `_: Irunner -* `598 `_: Fix templates for CrashHandler -* `590 `_: Desktop -* `600 `_: Fix bug with non-ascii reprs inside pretty-printed lists. -* `618 `_: I617 -* `599 `_: Gui Qt example and docs -* `619 `_: manpage update -* `582 `_: Updating sympy profile to match the exec_lines of isympy. -* `578 `_: Check to see if correct source for decorated functions can be displayed -* `589 `_: issue 588 -* `591 `_: simulate shell expansion on %run arguments, at least tilde expansion -* `576 `_: Show message about %paste magic on an IndentationError -* `574 `_: Getcwdu -* `565 `_: don't move old config files, keep nagging the user -* `575 `_: Added more docstrings to IPython.zmq.session. -* `567 `_: fix trailing whitespace from resetting indentation -* `564 `_: Command line args in docs -* `560 `_: reorder qt support in kernel -* `561 `_: command-line suggestions -* `556 `_: qt_for_kernel: use matplotlib rcParams to decide between PyQt4 and PySide -* `557 `_: Update usage.py to newapp -* `555 `_: Rm default old config -* `552 `_: update parallel code for py3k -* `504 `_: Updating string formatting -* `551 `_: Make pylab import all configurable -* `496 `_: Qt editing keybindings -* `550 `_: Support v2 PyQt4 APIs and PySide in kernel's GUI support -* `546 `_: doc update -* `548 `_: Fix sympy profile to work with sympy 0.7. -* `542 `_: issue 440 -* `533 `_: Remove unused configobj and validate libraries from externals. -* `538 `_: fix various tests on Windows -* `540 `_: support `-pylab` flag with deprecation warning -* `537 `_: Docs update -* `536 `_: `setup.py install` depends on setuptools on Windows -* `480 `_: Get help mid-command -* `462 `_: Str and Bytes traitlets -* `534 `_: Handle unicode properly in IPython.zmq.iostream -* `527 `_: ZMQ displayhook -* `526 `_: Handle asynchronous output in Qt console -* `528 `_: Do not import deprecated functions from external decorators library. -* `454 `_: New BaseIPythonApplication -* `532 `_: Zmq unicode -* `531 `_: Fix Parallel test -* `525 `_: fallback on lsof if otool not found in libedit detection -* `517 `_: Merge IPython.parallel.streamsession into IPython.zmq.session -* `521 `_: use dict.get(key) instead of dict[key] for safety from KeyErrors -* `492 `_: add QtConsoleApp using newapplication -* `485 `_: terminal IPython with newapp -* `486 `_: Use newapp in parallel code -* `511 `_: Add a new line before displaying multiline strings in the Qt console. -* `509 `_: i508 -* `501 `_: ignore EINTR in channel loops -* `495 `_: Better selection of Qt bindings when QT_API is not specified -* `498 `_: Check for .pyd as extension for binary files. -* `494 `_: QtConsole zoom adjustments -* `490 `_: fix UnicodeEncodeError writing SVG string to .svg file, fixes #489 -* `491 `_: add QtConsoleApp using newapplication -* `479 `_: embed() doesn't load default config -* `483 `_: Links launchpad -> github -* `419 `_: %xdel magic -* `477 `_: Add \n to lines in the log -* `459 `_: use os.system for shell.system in Terminal frontend -* `475 `_: i473 -* `471 `_: Add test decorator onlyif_unicode_paths. -* `474 `_: Fix support for raw GTK and WX matplotlib backends. -* `472 `_: Kernel event loop is robust against random SIGINT. -* `460 `_: Share code for magic_edit -* `469 `_: Add exit code when running all tests with iptest. -* `464 `_: Add home directory expansion to IPYTHON_DIR environment variables. -* `455 `_: Bugfix with logger -* `448 `_: Separate out skip_doctest decorator -* `453 `_: Draft of new main BaseIPythonApplication. -* `452 `_: Use list/tuple/dict/set subclass's overridden __repr__ instead of the pretty -* `398 `_: allow toggle of svg/png inline figure format -* `381 `_: Support inline PNGs of matplotlib plots -* `413 `_: Retries and Resubmit (#411 and #412) -* `370 `_: Fixes to the display system -* `449 `_: Fix issue 447 - inspecting old-style classes. -* `423 `_: Allow type checking on elements of List,Tuple,Set traits -* `400 `_: Config5 -* `421 `_: Generalise mechanism to put text at the next prompt in the Qt console. -* `443 `_: pinfo code duplication -* `429 `_: add check_pid, and handle stale PID info in ipcluster. -* `431 `_: Fix error message in test_irunner -* `427 `_: handle different SyntaxError messages in test_irunner -* `424 `_: Irunner test failure -* `430 `_: Small parallel doc typo -* `422 `_: Make ipython-qtconsole a GUI script -* `420 `_: Permit kernel std* to be redirected -* `408 `_: History request -* `388 `_: Add Emacs-style kill ring to Qt console -* `414 `_: Warn on old config files -* `415 `_: Prevent prefilter from crashing IPython -* `418 `_: Minor configuration doc fixes -* `407 `_: Update What's new documentation -* `410 `_: Install notebook frontend -* `406 `_: install IPython.zmq.gui -* `393 `_: ipdir unicode -* `397 `_: utils.io.Term.cin/out/err -> utils.io.stdin/out/err -* `389 `_: DB fixes and Scheduler HWM -* `374 `_: Various Windows-related fixes to IPython.parallel -* `362 `_: fallback on defaultencoding if filesystemencoding is None -* `382 `_: Shell's reset method clears namespace from last %run command. -* `385 `_: Update iptest exclusions (fix #375) -* `383 `_: Catch errors in querying readline which occur with pyreadline. -* `373 `_: Remove runlines etc. -* `364 `_: Single output -* `372 `_: Multiline input push -* `363 `_: Issue 125 -* `361 `_: don't rely on setuptools for readline dependency check -* `349 `_: Fix %autopx magic -* `355 `_: History save thread -* `356 `_: Usability improvements to history in Qt console -* `357 `_: Exit autocall -* `353 `_: Rewrite quit()/exit()/Quit()/Exit() calls as magic -* `354 `_: Cell tweaks -* `345 `_: Attempt to address (partly) issue ipython/#342 by rewriting quit(), exit(), etc. -* `352 `_: #342: Try to recover as intelligently as possible if user calls magic(). -* `346 `_: Dedent prefix bugfix + tests: #142 -* `348 `_: %reset doesn't reset prompt number. -* `347 `_: Make ip.reset() work the same in interactive or non-interactive code. -* `343 `_: make readline a dependency on OSX -* `344 `_: restore auto debug behavior -* `339 `_: fix for issue 337: incorrect/phantom tooltips for magics -* `254 `_: newparallel branch (add zmq.parallel submodule) -* `334 `_: Hard reset -* `316 `_: Unicode win process -* `332 `_: AST splitter -* `325 `_: Removetwisted -* `330 `_: Magic pastebin -* `309 `_: Bug tests for GH Issues 238, 284, 306, 307. Skip module machinery if not installed. Known failures reported as 'K' -* `331 `_: Tweak config loader for PyPy compatibility. -* `319 `_: Rewrite code to restore readline history after an action -* `329 `_: Do not store file contents in history when running a .ipy file. -* `179 `_: Html notebook -* `323 `_: Add missing external.pexpect to packages -* `295 `_: Magic local scope -* `315 `_: Unicode magic args -* `310 `_: allow Unicode Command-Line options -* `313 `_: Readline shortcuts -* `311 `_: Qtconsole exit -* `312 `_: History memory -* `294 `_: Issue 290 -* `292 `_: Issue 31 -* `252 `_: Unicode issues -* `235 `_: Fix history magic command's bugs wrt to full history and add -O option to display full history -* `236 `_: History minus p flag -* `261 `_: Adapt magic commands to new history system. -* `282 `_: SQLite history -* `191 `_: Unbundle external libraries -* `199 `_: Magic arguments -* `204 `_: Emacs completion bugfix -* `293 `_: Issue 133 -* `249 `_: Writing unicode characters to a log file. (IPython 0.10.2.git) -* `283 `_: Support for 256-color escape sequences in Qt console -* `281 `_: Refactored and improved Qt console's HTML export facility -* `237 `_: Fix185 (take two) -* `251 `_: Issue 129 -* `278 `_: add basic XDG_CONFIG_HOME support -* `275 `_: inline pylab cuts off labels on log plots -* `280 `_: Add %precision magic -* `259 `_: Pyside support -* `193 `_: Make ipython cProfile-able -* `272 `_: Magic examples -* `219 `_: Doc magic pycat -* `221 `_: Doc magic alias -* `230 `_: Doc magic edit -* `224 `_: Doc magic cpaste -* `229 `_: Doc magic pdef -* `273 `_: Docs build -* `228 `_: Doc magic who -* `233 `_: Doc magic cd -* `226 `_: Doc magic pwd -* `218 `_: Doc magic history -* `231 `_: Doc magic reset -* `225 `_: Doc magic save -* `222 `_: Doc magic timeit -* `223 `_: Doc magic colors -* `203 `_: Small typos in zmq/blockingkernelmanager.py -* `227 `_: Doc magic logon -* `232 `_: Doc magic profile -* `264 `_: Kernel logging -* `220 `_: Doc magic edit -* `268 `_: PyZMQ >= 2.0.10 -* `267 `_: GitHub Pages (again) -* `266 `_: OSX-specific fixes to the Qt console -* `255 `_: Gitwash typo -* `265 `_: Fix string input2 -* `260 `_: Kernel crash with empty history -* `243 `_: New display system -* `242 `_: Fix terminal exit -* `250 `_: always use Session.send -* `239 `_: Makefile command & script for GitHub Pages -* `244 `_: My exit -* `234 `_: Timed history save -* `217 `_: Doc magic lsmagic -* `215 `_: History fix -* `195 `_: Formatters -* `192 `_: Ready colorize bug -* `198 `_: Windows workdir -* `174 `_: Whitespace cleanup -* `188 `_: Version info: update our version management system to use git. -* `158 `_: Ready for merge -* `187 `_: Resolved Print shortcut collision with ctrl-P emacs binding -* `183 `_: cleanup of exit/quit commands for qt console -* `184 `_: Logo added to sphinx docs -* `180 `_: Cleanup old code -* `171 `_: Expose Pygments styles as options -* `170 `_: HTML Fixes -* `172 `_: Fix del method exit test -* `164 `_: Qt frontend shutdown behavior fixes and enhancements -* `167 `_: Added HTML export -* `163 `_: Execution refactor -* `159 `_: Ipy3 preparation -* `155 `_: Ready startup fix -* `152 `_: 0.10.1 sge -* `151 `_: mk_object_info -> object_info -* `149 `_: Simple bug-fix - -Regular issues (285): - -* `630 `_: new.py in pwd prevents ipython from starting -* `623 `_: Execute DirectView commands while running LoadBalancedView tasks -* `437 `_: Users should have autocompletion in the notebook -* `583 `_: update manpages -* `594 `_: irunner command line options defer to file extensions -* `603 `_: Users should see colored text in tracebacks and the pager -* `597 `_: UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 -* `608 `_: Organize and layout buttons in the notebook panel sections -* `609 `_: Implement controls in the Kernel panel section -* `611 `_: Add kernel status widget back to notebook -* `610 `_: Implement controls in the Cell section panel -* `612 `_: Implement Help panel section -* `621 `_: [qtconsole] on windows xp, cannot PageUp more than once -* `616 `_: Store exit status of last command -* `605 `_: Users should be able to open different notebooks in the cwd -* `302 `_: Users should see a consistent behavior in the Out prompt in the html notebook -* `435 `_: Notebook should not import anything by default -* `595 `_: qtconsole command issue -* `588 `_: ipython-qtconsole uses 100% CPU -* `586 `_: ? + plot() Command B0rks QTConsole Strangely -* `585 `_: %pdoc throws Errors for classes without __init__ or docstring -* `584 `_: %pdoc throws TypeError -* `580 `_: Client instantiation AssertionError -* `569 `_: UnicodeDecodeError during startup -* `572 `_: Indented command hits error -* `573 `_: -wthread breaks indented top-level statements -* `570 `_: "--pylab inline" vs. "--pylab=inline" -* `566 `_: Can't use exec_file in config file -* `562 `_: update docs to reflect '--args=values' -* `558 `_: triple quote and %s at beginning of line -* `554 `_: Update 0.11 docs to explain Qt console and how to do a clean install -* `553 `_: embed() fails if config files not installed -* `8 `_: Ensure %gui qt works with new Mayavi and pylab -* `269 `_: Provide compatibility api for IPython.Shell().start().mainloop() -* `66 `_: Update the main What's New document to reflect work on 0.11 -* `549 `_: Don't check for 'linux2' value in sys.platform -* `505 `_: Qt windows created within imported functions won't show() -* `545 `_: qtconsole ignores exec_lines -* `371 `_: segfault in qtconsole when kernel quits -* `377 `_: Failure: error (nothing to repeat) -* `544 `_: Ipython qtconsole pylab config issue. -* `543 `_: RuntimeError in completer -* `440 `_: %run filename autocompletion "The kernel heartbeat has been inactive ... " error -* `541 `_: log_level is broken in the ipython Application -* `369 `_: windows source install doesn't create scripts correctly -* `351 `_: Make sure that the Windows installer handles the top-level IPython scripts. -* `512 `_: Two displayhooks in zmq -* `340 `_: Make sure that the Windows HPC scheduler support is working for 0.11 -* `98 `_: Should be able to get help on an object mid-command -* `529 `_: unicode problem in qtconsole for windows -* `476 `_: Separate input area in Qt Console -* `175 `_: Qt console needs configuration support -* `156 `_: Key history lost when debugging program crash -* `470 `_: decorator: uses deprecated features -* `30 `_: readline in OS X does not have correct key bindings -* `503 `_: merge IPython.parallel.streamsession and IPython.zmq.session -* `456 `_: pathname in document punctuated by dots not slashes -* `451 `_: Allow switching the default image format for inline mpl backend -* `79 `_: Implement more robust handling of config stages in Application -* `522 `_: Encoding problems -* `524 `_: otool should not be unconditionally called on osx -* `523 `_: Get profile and config file inheritance working -* `519 `_: qtconsole --pure: "TypeError: string indices must be integers, not str" -* `516 `_: qtconsole --pure: "KeyError: 'ismagic'" -* `520 `_: qtconsole --pure: "TypeError: string indices must be integers, not str" -* `450 `_: resubmitted tasks sometimes stuck as pending -* `518 `_: JSON serialization problems with ObjectId type (MongoDB) -* `178 `_: Channels should be named for their function, not their socket type -* `515 `_: [ipcluster] termination on os x -* `510 `_: qtconsole: indentation problem printing numpy arrays -* `508 `_: "AssertionError: Missing message part." in ipython-qtconsole --pure -* `499 `_: "ZMQError: Interrupted system call" when saving inline figure -* `426 `_: %edit magic fails in qtconsole -* `497 `_: Don't show info from .pyd files -* `493 `_: QFont::setPointSize: Point size <= 0 (0), must be greater than 0 -* `489 `_: UnicodeEncodeError in qt.svg.save_svg -* `458 `_: embed() doesn't load default config -* `488 `_: Using IPython with RubyPython leads to problems with IPython.parallel.client.client.Client.__init() -* `401 `_: Race condition when running lbview.apply() fast multiple times in loop -* `168 `_: Scrub Launchpad links from code, docs -* `141 `_: garbage collection problem (revisited) -* `59 `_: test_magic.test_obj_del fails on win32 -* `457 `_: Backgrounded Tasks not Allowed? (but easy to slip by . . .) -* `297 `_: Shouldn't use pexpect for subprocesses in in-process terminal frontend -* `110 `_: magic to return exit status -* `473 `_: OSX readline detection fails in the debugger -* `466 `_: tests fail without unicode filename support -* `468 `_: iptest script has 0 exit code even when tests fail -* `465 `_: client.db_query() behaves different with SQLite and MongoDB -* `467 `_: magic_install_default_config test fails when there is no .ipython directory -* `463 `_: IPYTHON_DIR (and IPYTHONDIR) don't expand tilde to '~' directory -* `446 `_: Test machinery is imported at normal runtime -* `438 `_: Users should be able to use Up/Down for cell navigation -* `439 `_: Users should be able to copy notebook input and output -* `291 `_: Rename special display methods and put them lower in priority than display functions -* `447 `_: Instantiating classes without __init__ function causes kernel to crash -* `444 `_: Ctrl + t in WxIPython Causes Unexpected Behavior -* `445 `_: qt and console Based Startup Errors -* `428 `_: ipcluster doesn't handle stale pid info well -* `434 `_: 10.0.2 seg fault with rpy2 -* `441 `_: Allow running a block of code in a file -* `432 `_: Silent request fails -* `409 `_: Test failure in IPython.lib -* `402 `_: History section of messaging spec is incorrect -* `88 `_: Error when inputting UTF8 CJK characters -* `366 `_: Ctrl-K should kill line and store it, so that Ctrl-y can yank it back -* `425 `_: typo in %gui magic help -* `304 `_: Persistent warnings if old configuration files exist -* `216 `_: crash of ipython when alias is used with %s and echo -* `412 `_: add support to automatic retry of tasks -* `411 `_: add support to continue tasks -* `417 `_: IPython should display things unsorted if it can't sort them -* `416 `_: wrong encode when printing unicode string -* `376 `_: Failing InputsplitterTest -* `405 `_: TraitError in traitlets.py(332) on any input -* `392 `_: UnicodeEncodeError on start -* `137 `_: sys.getfilesystemencoding return value not checked -* `300 `_: Users should be able to manage kernels and kernel sessions from the notebook UI -* `301 `_: Users should have access to working Kernel, Tabs, Edit, Help menus in the notebook -* `396 `_: cursor move triggers a lot of IO access -* `379 `_: Minor doc nit: --paging argument -* `399 `_: Add task queue limit in engine when load-balancing -* `78 `_: StringTask won't take unicode code strings -* `391 `_: MongoDB.add_record() does not work in 0.11dev -* `365 `_: newparallel on Windows -* `386 `_: FAIL: test that pushed functions have access to globals -* `387 `_: Interactively defined functions can't access user namespace -* `118 `_: Snow Leopard ipy_vimserver POLL error -* `394 `_: System escape interpreted in multi-line string -* `26 `_: find_job_cmd is too hasty to fail on Windows -* `368 `_: Installation instructions in dev docs are completely wrong -* `380 `_: qtconsole pager RST - HTML not happening consistently -* `367 `_: Qt console doesn't support ibus input method -* `375 `_: Missing libraries cause ImportError in tests -* `71 `_: temp file errors in iptest IPython.core -* `350 `_: Decide how to handle displayhook being triggered multiple times -* `360 `_: Remove `runlines` method -* `125 `_: Exec lines in config should not contribute to line numbering or history -* `20 `_: Robust readline support on OS X's builtin Python -* `147 `_: On Windows, %page is being too restrictive to split line by \r\n only -* `326 `_: Update docs and examples for parallel stuff to reflect movement away from Twisted -* `341 `_: FIx Parallel Magics for newparallel -* `338 `_: Usability improvements to Qt console -* `142 `_: unexpected auto-indenting when variables names that start with 'pass' -* `296 `_: Automatic PDB via %pdb doesn't work -* `337 `_: exit( and quit( in Qt console produces phantom signature/docstring popup, even though quit() or exit() raises NameError -* `318 `_: %debug broken in master: invokes missing save_history() method -* `307 `_: lines ending with semicolon should not go to cache -* `104 `_: have ipengine run start-up scripts before registering with the controller -* `33 `_: The skip_doctest decorator is failing to work on Shell.MatplotlibShellBase.magic_run -* `336 `_: Missing figure development/figs/iopubfade.png for docs -* `49 `_: %clear should also delete _NN references and Out[NN] ones -* `335 `_: using setuptools installs every script twice -* `306 `_: multiline strings at end of input cause noop -* `327 `_: PyPy compatibility -* `328 `_: %run script.ipy raises "ERROR! Session/line number was not unique in database." -* `7 `_: Update the changes doc to reflect the kernel config work -* `303 `_: Users should be able to scroll a notebook w/o moving the menu/buttons -* `322 `_: Embedding an interactive IPython shell -* `321 `_: %debug broken in master -* `287 `_: Crash when using %macros in sqlite-history branch -* `55 `_: Can't edit files whose names begin with numbers -* `284 `_: In variable no longer works in 0.11 -* `92 `_: Using multiprocessing module crashes parallel IPython -* `262 `_: Fail to recover history after force-kill. -* `320 `_: Tab completing re.search objects crashes IPython -* `317 `_: IPython.kernel: parallel map issues -* `197 `_: ipython-qtconsole unicode problem in magic ls -* `305 `_: more readline shortcuts in qtconsole -* `314 `_: Multi-line, multi-block cells can't be executed. -* `308 `_: Test suite should set sqlite history to work in :memory: -* `202 `_: Matplotlib native 'MacOSX' backend broken in '-pylab' mode -* `196 `_: IPython can't deal with unicode file name. -* `25 `_: unicode bug - encoding input -* `290 `_: try/except/else clauses can't be typed, code input stops too early. -* `43 `_: Implement SSH support in ipcluster -* `6 `_: Update the Sphinx docs for the new ipcluster -* `9 `_: Getting "DeadReferenceError: Calling Stale Broker" after ipcontroller restart -* `132 `_: Ipython prevent south from working -* `27 `_: generics.complete_object broken -* `60 `_: Improve absolute import management for iptest.py -* `31 `_: Issues in magic_whos code -* `52 `_: Document testing process better -* `44 `_: Merge history from multiple sessions -* `182 `_: ipython q4thread in version 10.1 not starting properly -* `143 `_: Ipython.gui.wx.ipython_view.IPShellWidget: ignores user*_ns arguments -* `127 `_: %edit does not work on filenames consisted of pure numbers -* `126 `_: Can't transfer command line argument to script -* `28 `_: Offer finer control for initialization of input streams -* `58 `_: ipython change char '0xe9' to 4 spaces -* `68 `_: Problems with Control-C stopping ipcluster on Windows/Python2.6 -* `24 `_: ipcluster does not start all the engines -* `240 `_: Incorrect method displayed in %psource -* `120 `_: inspect.getsource fails for functions defined on command line -* `212 `_: IPython ignores exceptions in the first evaulation of class attrs -* `108 `_: ipython disables python logger -* `100 `_: Overzealous introspection -* `18 `_: %cpaste freeze sync frontend -* `200 `_: Unicode error when starting ipython in a folder with non-ascii path -* `130 `_: Deadlock when importing a module that creates an IPython client -* `134 `_: multline block scrolling -* `46 `_: Input to %timeit is not preparsed -* `285 `_: ipcluster local -n 4 fails -* `205 `_: In the Qt console, Tab should insert 4 spaces when not completing -* `145 `_: Bug on MSW systems: idle can not be set as default IPython editor. Fix Suggested. -* `77 `_: ipython oops in cygwin -* `121 `_: If plot windows are closed via window controls, no more plotting is possible. -* `111 `_: Iterator version of TaskClient.map() that returns results as they become available -* `109 `_: WinHPCLauncher is a hard dependency that causes errors in the test suite -* `86 `_: Make IPython work with multiprocessing -* `15 `_: Implement SGE support in ipcluster -* `3 `_: Implement PBS support in ipcluster -* `53 `_: Internal Python error in the inspect module -* `74 `_: Manager() [from multiprocessing module] hangs ipythonx but not ipython -* `51 `_: Out not working with ipythonx -* `201 `_: use session.send throughout zmq code -* `115 `_: multiline specials not defined in 0.11 branch -* `93 `_: when looping, cursor appears at leftmost point in newline -* `133 `_: whitespace after Source introspection -* `50 `_: Ctrl-C with -gthread on Windows, causes uncaught IOError -* `65 `_: Do not use .message attributes in exceptions, deprecated in 2.6 -* `76 `_: syntax error when raise is inside except process -* `107 `_: bdist_rpm causes traceback looking for a non-existant file -* `113 `_: initial magic ? (question mark) fails before wildcard -* `128 `_: Pdb instance has no attribute 'curframe' -* `139 `_: running with -pylab pollutes namespace -* `140 `_: malloc error during tab completion of numpy array member functions starting with 'c' -* `153 `_: ipy_vimserver traceback on Windows -* `154 `_: using ipython in Slicer3 show how os.environ['HOME'] is not defined -* `185 `_: show() blocks in pylab mode with ipython 0.10.1 -* `189 `_: Crash on tab completion -* `274 `_: bashism in sshx.sh -* `276 `_: Calling `sip.setapi` does not work if app has already imported from PyQt4 -* `277 `_: matplotlib.image imgshow from 10.1 segfault -* `288 `_: Incorrect docstring in zmq/kernelmanager.py -* `286 `_: Fix IPython.Shell compatibility layer -* `99 `_: blank lines in history -* `129 `_: psearch: TypeError: expected string or buffer -* `190 `_: Add option to format float point output -* `246 `_: Application not conforms XDG Base Directory Specification -* `48 `_: IPython should follow the XDG Base Directory spec for configuration -* `176 `_: Make client-side history persistence readline-independent -* `279 `_: Backtraces when using ipdb do not respect -colour LightBG setting -* `119 `_: Broken type filter in magic_who_ls -* `271 `_: Intermittent problem with print output in Qt console. -* `270 `_: Small typo in IPython developer’s guide -* `166 `_: Add keyboard accelerators to Qt close dialog -* `173 `_: asymmetrical ctrl-A/ctrl-E behavior in multiline -* `45 `_: Autosave history for robustness -* `162 `_: make command history persist in ipythonqt -* `161 `_: make ipythonqt exit without dialog when exit() is called -* `263 `_: [ipython + numpy] Some test errors -* `256 `_: reset docstring ipython 0.10 -* `258 `_: allow caching to avoid matplotlib object references -* `248 `_: Can't open and read files after upgrade from 0.10 to 0.10.0 -* `247 `_: ipython + Stackless -* `245 `_: Magic save and macro missing newlines, line ranges don't match prompt numbers. -* `241 `_: "exit" hangs on terminal version of IPython -* `213 `_: ipython -pylab no longer plots interactively on 0.10.1 -* `4 `_: wx frontend don't display well commands output -* `5 `_: ls command not supported in ipythonx wx frontend -* `1 `_: Document winhpcjob.py and launcher.py -* `83 `_: Usage of testing.util.DeferredTestCase should be replace with twisted.trial.unittest.TestCase -* `117 `_: Redesign how Component instances are tracked and queried -* `47 `_: IPython.kernel.client cannot be imported inside an engine -* `105 `_: Refactor the task dependencies system -* `210 `_: 0.10.1 doc mistake - New IPython Sphinx directive error -* `209 `_: can't activate IPython parallel magics -* `206 `_: Buggy linewrap in Mac OSX Terminal -* `194 `_: !sudo displays password in plain text -* `186 `_: %edit issue under OS X 10.5 - IPython 0.10.1 -* `11 `_: Create a daily build PPA for ipython -* `144 `_: logo missing from sphinx docs -* `181 `_: cls command does not work on windows -* `169 `_: Kernel can only be bound to localhost -* `36 `_: tab completion does not escape () -* `177 `_: Report tracebacks of interactively entered input -* `148 `_: dictionary having multiple keys having frozenset fails to print on IPython -* `160 `_: magic_gui throws TypeError when gui magic is used -* `150 `_: History entries ending with parentheses corrupt command line on OS X 10.6.4 -* `146 `_: -ipythondir - using an alternative .ipython dir for rc type stuff -* `114 `_: Interactive strings get mangled with "_ip.magic" -* `135 `_: crash on invalid print -* `69 `_: Usage of "mycluster" profile in docs and examples -* `37 `_: Fix colors in output of ResultList on Windows diff --git a/docs/source/whatsnew/github-stats-0.12.rst b/docs/source/whatsnew/github-stats-0.12.rst deleted file mode 100644 index ac2b56c099b..00000000000 --- a/docs/source/whatsnew/github-stats-0.12.rst +++ /dev/null @@ -1,621 +0,0 @@ -.. _issues_list_012: - -Issues closed in the 0.12 development cycle -=========================================== - -Issues closed in 0.12.1 ------------------------ - -GitHub stats for bugfix release 0.12.1 (12/28/2011-04/16/2012), backporting -pull requests from 0.13. - -We closed a total of 71 issues: 44 pull requests and 27 issues; this is the -full list (generated with the script `tools/github_stats.py`). - -This list is automatically generated, and may be incomplete: - -Pull Requests (44): - -* :ghpull:`1175`: core.completer: Clean up excessive and unused code. -* :ghpull:`1187`: misc notebook: connection file cleanup, first heartbeat, startup flush -* :ghpull:`1190`: Fix link to Chris Fonnesbeck blog post about 0.11 highlights. -* :ghpull:`1196`: docs: looks like a file path might have been accidentally pasted in the middle of a word -* :ghpull:`1206`: don't preserve fixConsole output in json -* :ghpull:`1207`: fix loadpy duplicating newlines -* :ghpull:`1213`: BUG: Minor typo in history_console_widget.py -* :ghpull:`1218`: Added -q option to %prun for suppression of the output, along with editing the dochelp string. -* :ghpull:`1222`: allow Reference as callable in map/apply -* :ghpull:`1229`: Fix display of SyntaxError in Python 3 -* :ghpull:`1246`: Skip tests that require X, when importing pylab results in RuntimeError. -* :ghpull:`1253`: set auto_create flag for notebook apps -* :ghpull:`1257`: use self.kernel_manager_class in qtconsoleapp -* :ghpull:`1262`: Heartbeat no longer shares the app's Context -* :ghpull:`1283`: HeartMonitor.period should be an Integer -* :ghpull:`1284`: a fix for GH 1269 -* :ghpull:`1289`: Make autoreload extension work on Python 3. -* :ghpull:`1306`: Fix %prun input parsing for escaped characters (closes #1302) -* :ghpull:`1312`: minor heartbeat tweaks -* :ghpull:`1318`: make Ctrl-D in qtconsole act same as in terminal (ready to merge) -* :ghpull:`1341`: Don't attempt to tokenize binary files for tracebacks -* :ghpull:`1353`: Save notebook as script using unicode file handle. -* :ghpull:`1363`: Fix some minor color/style config issues in the qtconsole -* :ghpull:`1364`: avoid jsonlib returning Decimal -* :ghpull:`1369`: load header with engine id when engine dies in TaskScheduler -* :ghpull:`1370`: allow draft76 websockets (Safari) -* :ghpull:`1374`: remove calls to meaningless ZMQStream.on_err -* :ghpull:`1377`: Saving non-ascii history -* :ghpull:`1396`: Fix for %tb magic. -* :ghpull:`1402`: fix symlinked /home issue for FreeBSD -* :ghpull:`1413`: get_home_dir expands symlinks, adjust test accordingly -* :ghpull:`1414`: ignore errors in shell.var_expand -* :ghpull:`1430`: Fix for tornado check for tornado < 1.1.0 -* :ghpull:`1445`: Don't build sphinx docs for sdists -* :ghpull:`1463`: Fix completion when importing modules in the cwd. -* :ghpull:`1477`: fix dangling `buffer` in IPython.parallel.util -* :ghpull:`1495`: BUG: Fix pretty-printing for overzealous objects -* :ghpull:`1496`: BUG: LBYL when clearing the output history on shutdown. -* :ghpull:`1514`: DOC: Fix references to IPython.lib.pretty instead of the old location -* :ghpull:`1517`: Fix indentation bug in IPython/lib/pretty.py -* :ghpull:`1538`: store git commit hash in utils._sysinfo instead of hidden data file -* :ghpull:`1599`: Fix for %run -d in Python 3 -* :ghpull:`1602`: Fix %env for Python 3 -* :ghpull:`1607`: cleanup sqlitedb temporary db file after tests - -Issues (27): - -* :ghissue:`676`: IPython.embed() from ipython crashes twice on exit -* :ghissue:`846`: Autoreload extension doesn't work with Python 3.2 -* :ghissue:`1187`: misc notebook: connection file cleanup, first heartbeat, startup flush -* :ghissue:`1191`: profile/startup files not executed with "notebook" -* :ghissue:`1197`: Interactive shell trying to: from ... import history -* :ghissue:`1198`: Kernel Has Died error in Notebook -* :ghissue:`1201`: %env magic fails with Python 3.2 -* :ghissue:`1204`: double newline from %loadpy in python notebook (at least on mac) -* :ghissue:`1208`: should dv.sync_import print failed imports ? -* :ghissue:`1225`: SyntaxError display broken in Python 3 -* :ghissue:`1232`: Dead kernel loop -* :ghissue:`1241`: When our debugger class is used standalone `_oh` key errors are thrown -* :ghissue:`1254`: typo in notebooklist.js breaks links -* :ghissue:`1260`: heartbeat failure on long gil-holding operation -* :ghissue:`1268`: notebook %reset magic fails with StdinNotImplementedError -* :ghissue:`1269`: Another strange input handling error -* :ghissue:`1281`: in Hub: registration_timeout must be an integer, but heartmonitor.period is CFloat -* :ghissue:`1302`: Input parsing with %prun clobbers escapes -* :ghissue:`1304`: controller/server load can disrupt heartbeat -* :ghissue:`1317`: Very slow traceback construction from Cython extension -* :ghissue:`1345`: notebook can't save unicode as script -* :ghissue:`1375`: %history -g -f file encoding issue -* :ghissue:`1401`: numpy arrays cannot be used with View.apply() in Python 3 -* :ghissue:`1408`: test_get_home_dir_3 failed on Mac OS X -* :ghissue:`1412`: Input parsing issue with %prun -* :ghissue:`1421`: ipython32 %run -d breaks with NameError name 'execfile' is not defined -* :ghissue:`1484`: unhide .git_commit_info.ini - - -Issues closed in 0.12 ---------------------- - -In this cycle, from August 1 to December 28 2011, we closed a total of 515 -issues, 257 pull requests and 258 regular issues; this is the full list -(generated with the script `tools/github_stats.py`). - -Pull requests (257): - -* `1174 `_: Remove %install_default_config and %install_profiles -* `1178 `_: Correct string type casting in pinfo. -* `1096 `_: Show class init and call tooltips in notebook -* `1176 `_: Modifications to profile list -* `1173 `_: don't load gui/pylab in console frontend -* `1168 `_: Add --script flag as shorthand for notebook save_script option. -* `1165 `_: encode image_tag as utf8 in [x]html export -* `1161 `_: Allow %loadpy to load remote URLs that don't end in .py -* `1158 `_: Add coding header when notebook exported to .py file. -* `1160 `_: don't ignore ctrl-C during `%gui qt` -* `1159 `_: Add encoding header to Python files downloaded from notebooks. -* `1155 `_: minor post-execute fixes (#1154) -* `1153 `_: Pager tearing bug -* `1152 `_: Add support for displaying maptlotlib axes directly. -* `1079 `_: Login/out button cleanups -* `1151 `_: allow access to user_ns in prompt_manager -* `1120 `_: updated vim-ipython (pending) -* `1150 `_: BUG: Scrolling pager in vsplit on Mac OSX tears. -* `1149 `_: #1148 (win32 arg_split) -* `1147 `_: Put qtconsole forground when launching -* `1146 `_: allow saving notebook.py next to notebook.ipynb -* `1128 `_: fix pylab StartMenu item -* `1140 `_: Namespaces for embedding -* `1132 `_: [notebook] read-only: disable name field -* `1125 `_: notebook : update logo -* `1135 `_: allow customized template and static file paths for the notebook web app -* `1122 `_: BUG: Issue #755 qt IPythonWidget.execute_file fails if filename contains... -* `1137 `_: rename MPIExecLaunchers to MPILaunchers -* `1130 `_: optionally ignore shlex's ValueError in arg_split -* `1116 `_: Shlex unicode -* `1073 `_: Storemagic plugin -* `1143 `_: Add post_install script to create start menu entries in Python 3 -* `1138 `_: Fix tests to work when ~/.config/ipython contains a symlink. -* `1121 `_: Don't transform function calls on IPyAutocall objects -* `1118 `_: protect CRLF from carriage-return action -* `1105 `_: Fix for prompts containing newlines. -* `1126 `_: Totally remove pager when read only (notebook) -* `1091 `_: qtconsole : allow copy with shortcut in pager -* `1114 `_: fix magics history in two-process ipython console -* `1113 `_: Fixing #1112 removing failing asserts for test_carriage_return and test_beep -* `1089 `_: Support carriage return ('\r') and beep ('\b') characters in the qtconsole -* `1108 `_: Completer usability 2 (rebased of pr #1082) -* `864 `_: Two-process terminal frontend (ipython core branch) -* `1082 `_: usability and cross browser compat for completer -* `1053 `_: minor improvements to text placement in qtconsole -* `1106 `_: Fix display of errors in compiled code on Python 3 -* `1077 `_: allow the notebook to run without MathJax -* `1072 `_: If object has a getdoc() method, override its normal docstring. -* `1059 `_: Switch to simple `__IPYTHON__` global -* `1070 `_: Execution count after SyntaxError -* `1098 `_: notebook: config section UI -* `1101 `_: workaround spawnb missing from pexpect.__all__ -* `1097 `_: typo, should fix #1095 -* `1099 `_: qtconsole export xhtml/utf8 -* `1083 `_: Prompts -* `1081 `_: Fix wildcard search for updated namespaces -* `1084 `_: write busy in notebook window title... -* `1078 `_: PromptManager fixes -* `1064 `_: Win32 shlex -* `1069 `_: As you type completer, fix on Firefox -* `1039 `_: Base of an as you type completer. -* `1065 `_: Qtconsole fix racecondition -* `507 `_: Prompt manager -* `1056 `_: Warning in code. qtconsole ssh -X -* `1036 `_: Clean up javascript based on js2-mode feedback. -* `1052 `_: Pylab fix -* `648 `_: Usermod -* `969 `_: Pexpect-u -* `1007 `_: Fix paste/cpaste bug and refactor/cleanup that code a lot. -* `506 `_: make ENTER on a previous input field replace current input buffer -* `1040 `_: json/jsonapi cleanup -* `1042 `_: fix firefox (windows) break line on empty prompt number -* `1015 `_: emacs freezes when tab is hit in ipython with latest python-mode -* `1023 `_: flush stdout/stderr at the end of kernel init -* `956 `_: Generate "All magics..." menu live -* `1038 `_: Notebook: don't change cell when selecting code using shift+up/down. -* `987 `_: Add Tooltip to notebook. -* `1028 `_: Cleaner minimum version comparison -* `998 `_: defer to stdlib for path.get_home_dir() -* `1033 `_: update copyright to 2011/20xx-2011 -* `1032 `_: Intercept avoid closing websocket on Firefox -* `1030 `_: use pyzmq tools where appropriate -* `1029 `_: Restore pspersistence, including %store magic, as an extension. -* `1025 `_: Dollar escape -* `999 `_: Fix issue #880 - more useful message to user when %paste fails -* `938 `_: changes to get ipython.el to work with the latest python-mode.el -* `1012 `_: Add logout button. -* `1020 `_: Dollar formatter for ! shell calls -* `1019 `_: Use repr() to make quoted strings -* `1008 `_: don't use crash_handler by default -* `1003 `_: Drop consecutive duplicates when refilling readline history -* `997 `_: don't unregister interrupted post-exec functions -* `996 `_: add Integer traitlet -* `1016 `_: Fix password hashing for Python 3 -* `1014 `_: escape minus signs in manpages -* `1013 `_: [NumPyExampleDocstring] link was pointing to raw file -* `1011 `_: Add hashed password support. -* `1005 `_: Quick fix for os.system requiring str parameter -* `994 `_: Allow latex formulas in HTML output -* `955 `_: Websocket Adjustments -* `979 `_: use system_raw in terminal, even on Windows -* `989 `_: fix arguments for commands in _process_posix -* `991 `_: Show traceback, continuing to start kernel if pylab init fails -* `981 `_: Split likely multiline text when writing JSON notebooks -* `957 `_: allow change of png DPI in inline backend -* `968 `_: add wantDirectory to ipdoctest, so that directories will be checked for e -* `984 `_: Do not expose variables defined at startup to %who etc. -* `985 `_: Fixes for parallel code on Python 3 -* `963 `_: disable calltips in PySide < 1.0.7 to prevent segfault -* `976 `_: Getting started on what's new -* `929 `_: Multiline history -* `964 `_: Default profile -* `961 `_: Disable the pager for the test suite -* `953 `_: Physics extension -* `950 `_: Add directory for startup files -* `940 `_: allow setting HistoryManager.hist_file with config -* `948 `_: Monkeypatch Tornado 2.1.1 so it works with Google Chrome 16. -* `916 `_: Run p ( https://github.com/ipython/ipython/pull/901 ) -* `923 `_: %config magic -* `920 `_: unordered iteration of AsyncMapResults (+ a couple fixes) -* `941 `_: Follow-up to 387dcd6a, `_rl.__doc__` is `None` with pyreadline -* `931 `_: read-only notebook mode -* `921 `_: Show invalid config message on TraitErrors during init -* `815 `_: Fix #481 using custom qt4 input hook -* `936 `_: Start webbrowser in a thread. Prevents lockup with Chrome. -* `937 `_: add dirty trick for readline import on OSX -* `913 `_: Py3 tests2 -* `933 `_: Cancel in qt console closeevent should trigger event.ignore() -* `930 `_: read-only notebook mode -* `910 `_: Make import checks more explicit in %whos -* `926 `_: reincarnate -V cmdline option -* `928 `_: BUG: Set context for font size change shortcuts in ConsoleWidget -* `901 `_: - There is a bug when running the profiler in the magic command (prun) with python3 -* `912 `_: Add magic for cls on windows. Fix for #181. -* `905 `_: enable %gui/%pylab magics in the Kernel -* `909 `_: Allow IPython to run without sqlite3 -* `887 `_: Qtconsole menu -* `895 `_: notebook download implies save -* `896 `_: Execfile -* `899 `_: Brian's Notebook work -* `892 `_: don't close figures every cycle with inline matplotlib backend -* `893 `_: Adding clear_output to kernel and HTML notebook -* `789 `_: Adding clear_output to kernel and HTML notebook. -* `898 `_: Don't pass unicode sys.argv with %run or `ipython script.py` -* `897 `_: Add tooltips to the notebook via 'title' attr. -* `877 `_: partial fix for issue #678 -* `838 `_: reenable multiline history for terminals -* `872 `_: The constructor of Client() checks for AssertionError in validate_url to open a file instead of connection to a URL if it fails. -* `884 `_: Notebook usability fixes -* `883 `_: User notification if notebook saving fails -* `889 `_: Add drop_by_id method to shell, to remove variables added by extensions. -* `891 `_: Ability to open the notebook in a browser when it starts -* `813 `_: Create menu bar for qtconsole -* `876 `_: protect IPython from bad custom exception handlers -* `856 `_: Backgroundjobs -* `868 `_: Warn user if MathJax can't be fetched from notebook closes #744 -* `878 `_: store_history=False default for run_cell -* `824 `_: History access -* `850 `_: Update codemirror to 2.15 and make the code internally more version-agnostic -* `861 `_: Fix for issue #56 -* `819 `_: Adding -m option to %run, similar to -m for python interpreter. -* `855 `_: promote aliases and flags, to ensure they have priority over config files -* `862 `_: BUG: Completion widget position and pager focus. -* `847 `_: Allow connection to kernels by files -* `708 `_: Two-process terminal frontend -* `857 `_: make sdist flags work again (e.g. --manifest-only) -* `835 `_: Add Tab key to list of keys that scroll down the paging widget. -* `859 `_: Fix for issue #800 -* `848 `_: Python3 setup.py install failiure -* `845 `_: Tests on Python 3 -* `802 `_: DOC: extensions: add documentation for the bundled extensions -* `830 `_: contiguous stdout/stderr in notebook -* `761 `_: Windows: test runner fails if repo path (e.g. home dir) contains spaces -* `801 `_: Py3 notebook -* `809 `_: use CFRunLoop directly in `ipython kernel --pylab osx` -* `841 `_: updated old scipy.org links, other minor doc fixes -* `837 `_: remove all trailling spaces -* `834 `_: Issue https://github.com/ipython/ipython/issues/832 resolution -* `746 `_: ENH: extensions: port autoreload to current API -* `828 `_: fixed permissions (sub-modules should not be executable) + added shebang for run_ipy_in_profiler.py -* `798 `_: pexpect & Python 3 -* `804 `_: Magic 'range' crash if greater than len(input_hist) -* `821 `_: update tornado dependency to 2.1 -* `807 `_: Facilitate ssh tunnel sharing by announcing ports -* `795 `_: Add cluster-id for multiple cluster instances per profile -* `742 `_: Glut -* `668 `_: Greedy completer -* `776 `_: Reworking qtconsole shortcut, add fullscreen -* `790 `_: TST: add future unicode_literals test (#786) -* `775 `_: redirect_in/redirect_out should be constrained to windows only -* `793 `_: Don't use readline in the ZMQShell -* `743 `_: Pyglet -* `774 `_: basic/initial .mailmap for nice shortlog summaries -* `770 `_: #769 (reopened) -* `784 `_: Parse user code to AST using compiler flags. -* `783 `_: always use StringIO, never cStringIO -* `782 `_: flush stdout/stderr on displayhook call -* `622 `_: Make pylab import all configurable -* `745 `_: Don't assume history requests succeed in qtconsole -* `725 `_: don't assume cursor.selectedText() is a string -* `778 `_: don't override execfile on Python 2 -* `663 `_: Python 3 compatilibility work -* `762 `_: qtconsole ipython widget's execute_file fails if filename contains spaces or quotes -* `763 `_: Set context for shortcuts in ConsoleWidget -* `722 `_: PyPy compatibility -* `757 `_: ipython.el is broken in 0.11 -* `764 `_: fix "--colors=" option in py-python-command-args. -* `758 `_: use ROUTER/DEALER socket names instead of XREP/XREQ -* `736 `_: enh: added authentication ability for webapp -* `748 `_: Check for tornado before running frontend.html tests. -* `754 `_: restore msg_id/msg_type aliases in top level of msg dict -* `769 `_: Don't treat bytes objects as json-safe -* `753 `_: DOC: msg['msg_type'] removed -* `766 `_: fix "--colors=" option in py-python-command-args. -* `765 `_: fix "--colors=" option in py-python-command-args. -* `741 `_: Run PyOs_InputHook in pager to keep plot windows interactive. -* `664 `_: Remove ipythonrc references from documentation -* `750 `_: Tiny doc fixes -* `433 `_: ZMQ terminal frontend -* `734 `_: Allow %magic argument filenames with spaces to be specified with quotes under win32 -* `731 `_: respect encoding of display data from urls -* `730 `_: doc improvements for running notebook via secure protocol -* `729 `_: use null char to start markdown cell placeholder -* `727 `_: Minor fixes to the htmlnotebook -* `726 `_: use bundled argparse if system argparse is < 1.1 -* `705 `_: Htmlnotebook -* `723 `_: Add 'import time' to IPython/parallel/apps/launcher.py as time.sleep is called without time being imported -* `714 `_: Install mathjax for offline use -* `718 `_: Underline keyboard shortcut characters on appropriate buttons -* `717 `_: Add source highlighting to markdown snippets -* `716 `_: update EvalFormatter to allow arbitrary expressions -* `712 `_: Reset execution counter after cache is cleared -* `713 `_: Align colons in html notebook help dialog -* `709 `_: Allow usage of '.' in notebook names -* `706 `_: Implement static publishing of HTML notebook -* `674 `_: use argparse to parse aliases & flags -* `679 `_: HistoryManager.get_session_info() -* `696 `_: Fix columnize bug, where tab completion with very long filenames would crash Qt console -* `686 `_: add ssh tunnel support to qtconsole -* `685 `_: Add SSH tunneling to engines -* `384 `_: Allow pickling objects defined interactively. -* `647 `_: My fix rpmlint -* `587 `_: don't special case for py3k+numpy -* `703 `_: make config-loading debug messages more explicit -* `699 `_: make calltips configurable in qtconsole -* `666 `_: parallel tests & extra readline escapes -* `683 `_: BF - allow nose with-doctest setting in environment -* `689 `_: Protect ipkernel from bad messages -* `702 `_: Prevent ipython.py launcher from being imported. -* `701 `_: Prevent ipython.py from being imported by accident -* `670 `_: check for writable dirs, not just existence, in utils.path -* `579 `_: Sessionwork -* `687 `_: add `ipython kernel` for starting just a kernel -* `627 `_: Qt Console history search -* `646 `_: Generate package list automatically in find_packages -* `660 `_: i658 -* `659 `_: don't crash on bad config files - -Regular issues (258): - -* `1177 `_: UnicodeDecodeError in py3compat from "xlrd??" -* `1094 `_: Tooltip doesn't show constructor docstrings -* `1170 `_: double pylab greeting with c.InteractiveShellApp.pylab = "tk" in zmqconsole -* `1166 `_: E-mail cpaste broken -* `1164 `_: IPython qtconsole (0.12) can't export to html with external png -* `1103 `_: %loadpy should cut out encoding declaration -* `1156 `_: Notebooks downloaded as Python files require a header stating the encoding -* `1157 `_: Ctrl-C not working when GUI/pylab integration is active -* `1154 `_: We should be less aggressive in de-registering post-execution functions -* `1134 `_: "select-all, kill" leaves qtconsole in unusable state -* `1148 `_: A lot of testerrors -* `803 `_: Make doctests work with Python 3 -* `1119 `_: Start menu shortcuts not created in Python 3 -* `1136 `_: The embedding machinery ignores user_ns -* `607 `_: Use the new IPython logo/font in the notebook header -* `755 `_: qtconsole ipython widget's execute_file fails if filename contains spaces or quotes -* `1115 `_: shlex_split should return unicode -* `1109 `_: timeit with string ending in space gives "ValueError: No closing quotation" -* `1142 `_: Install problems -* `700 `_: Some SVG images render incorrectly in htmlnotebook -* `1117 `_: quit() doesn't work in terminal -* `1111 `_: ls broken after merge of #1089 -* `1104 `_: Prompt spacing weird -* `1124 `_: Seg Fault 11 when calling PySide using "run" command -* `1088 `_: QtConsole : can't copy from pager -* `568 `_: Test error and failure in IPython.core on windows -* `1112 `_: testfailure in IPython.frontend on windows -* `1102 `_: magic in IPythonDemo fails when not located at top of demo file -* `629 `_: \r and \b in qtconsole don't behave as expected -* `1080 `_: Notebook: tab completion should close on "(" -* `973 `_: Qt Console close dialog and on-top Qt Console -* `1087 `_: QtConsole xhtml/Svg export broken ? -* `1067 `_: Parallel test suite hangs on Python 3 -* `1018 `_: Local mathjax breaks install -* `993 `_: `raw_input` redirection to foreign kernels is extremely brittle -* `1100 `_: ipython3 traceback unicode issue from extensions -* `1071 `_: Large html-notebooks hang on load on a slow machine -* `89 `_: %pdoc np.ma.compress shows docstring twice -* `22 `_: Include improvements from anythingipython.el -* `633 `_: Execution count & SyntaxError -* `1095 `_: Uncaught TypeError: Object has no method 'remove_and_cancell_tooltip' -* `1075 `_: We're ignoring prompt customizations -* `1086 `_: Can't open qtconsole from outside source tree -* `1076 `_: namespace changes broke `foo.*bar*?` syntax -* `1074 `_: pprinting old-style class objects fails (TypeError: 'tuple' object is not callable) -* `1063 `_: IPython.utils test error due to missing unicodedata module -* `592 `_: Bug in argument parsing for %run -* `378 `_: Windows path escape issues -* `1068 `_: Notebook tab completion broken in Firefox -* `75 `_: No tab completion after "/ -* `103 `_: customizable cpaste -* `324 `_: Remove code in IPython.testing that is not being used -* `131 `_: Global variables not seen by cprofile.run() -* `851 `_: IPython shell swallows exceptions in certain circumstances -* `882 `_: ipython freezes at start if IPYTHONDIR is on an NFS mount -* `1057 `_: Blocker: Qt console broken after "all magics" menu became dynamic -* `1027 `_: ipython does not like white space at end of file -* `1058 `_: New bug: Notebook asks for confirmation to leave even saved pages. -* `1061 `_: rep (magic recall) under pypy -* `1047 `_: Document the notebook format -* `102 `_: Properties accessed twice for classes defined interactively -* `16 `_: %store raises exception when storing compiled regex -* `67 `_: tab expansion should only take one directory level at the time -* `62 `_: Global variables undefined in interactive use of embedded ipython shell -* `57 `_: debugging with ipython does not work well outside ipython -* `38 `_: Line entry edge case error -* `980 `_: Update parallel docs for new parallel architecture -* `1017 `_: Add small example about ipcluster/ssh startup -* `1041 `_: Proxy Issues -* `967 `_: KernelManagers don't use zmq eventloop properly -* `1055 `_: "All Magics" display on Ubuntu -* `1054 `_: ipython explodes on syntax error -* `1051 `_: ipython3 set_next_input() failure -* `693 `_: "run -i" no longer works after %reset in terminal -* `29 `_: cPickle works in standard interpreter, but not in IPython -* `1050 `_: ipython3 broken by commit 8bb887c8c2c447bf7 -* `1048 `_: Update docs on notebook password -* `1046 `_: Searies of questions/issues? -* `1045 `_: crash when exiting - previously launched embedded sub-shell -* `1043 `_: pylab doesn't work in qtconsole -* `1044 `_: run -p doesn't work in python 3 -* `1010 `_: emacs freezes when ipython-complete is called -* `82 `_: Update devel docs with discussion about good changelogs -* `116 `_: Update release management scipts and release.revision for git -* `1022 `_: Pylab banner shows up with first cell to execute -* `787 `_: Keyboard selection of multiple lines in the notebook behaves inconsistently -* `1037 `_: notepad + jsonlib: TypeError: Only whitespace may be used for indentation. -* `970 `_: Default home not writable, %HOME% does not help (windows) -* `747 `_: HOMESHARE not a good choice for "writable homedir" on Windows -* `810 `_: cleanup utils.path.get_home_dir -* `2 `_: Fix the copyright statement in source code files to be accurate -* `1031 `_: on Firefox crash websocket -* `684 `_: %Store eliminated in configuration and magic commands in 0.11 -* `1026 `_: BUG: wrong default parameter in ask_yes_no -* `880 `_: Better error message if %paste fails -* `1024 `_: autopx magic broken -* `822 `_: Unicode bug in Itpl when expanding shell variables in syscalls with ! -* `1009 `_: Windows: regression in cd magic handling of paths -* `833 `_: Crash python with matplotlib and unequal length arrays -* `695 `_: Crash handler initialization is too aggressive -* `1000 `_: Remove duplicates when refilling readline history -* `992 `_: Interrupting certain matplotlib operations leaves the inline backend 'wedged' -* `942 `_: number traits should cast if value doesn't change -* `1006 `_: ls crashes when run on a UNC path or with non-ascii args -* `944 `_: Decide the default image format for inline figures: SVG or PNG? -* `842 `_: Python 3 on Windows (pyreadline) - expected an object with the buffer interface -* `1002 `_: ImportError due to incorrect version checking -* `1001 `_: Ipython "source" command? -* `954 `_: IPython embed doesn't respect namespaces -* `681 `_: pdb freezes inside qtconsole -* `698 `_: crash report "TypeError: can only concatenate list (not "unicode") to list" -* `978 `_: ipython 0.11 buffers external command output till the cmd is done -* `952 `_: Need user-facing warning in the browser if websocket connection fails -* `988 `_: Error using idlsave -* `990 `_: ipython notebook - kernel dies if matplotlib is not installed -* `752 `_: Matplotlib figures showed only once in notebook -* `54 `_: Exception hook should be optional for embedding IPython in GUIs -* `918 `_: IPython.frontend tests fail without tornado -* `986 `_: Views created with c.direct_view() fail -* `697 `_: Filter out from %who names loaded at initialization time -* `932 `_: IPython 0.11 quickref card has superfluous "%recall and" -* `982 `_: png files with executable permissions -* `914 `_: Simpler system for running code after InteractiveShell is initialised -* `911 `_: ipython crashes on startup if readline is missing -* `971 `_: bookmarks created in 0.11 are corrupt in 0.12 -* `974 `_: object feature tab-completion crash -* `939 `_: ZMQShell always uses default profile -* `946 `_: Multi-tab Close action should offer option to leave all kernels alone -* `949 `_: Test suite must not require any manual interaction -* `643 `_: enable gui eventloop integration in ipkernel -* `965 `_: ipython is crashed without launch.(python3.2) -* `958 `_: Can't use os X clipboard on with qtconsole -* `962 `_: Don't require tornado in the tests -* `960 `_: crash on syntax error on Windows XP -* `934 `_: The latest ipython branch doesn't work in Chrome -* `870 `_: zmq version detection -* `943 `_: HISTIGNORE for IPython -* `947 `_: qtconsole segfaults at startup -* `903 `_: Expose a magic to control config of the inline pylab backend -* `908 `_: bad user config shouldn't crash IPython -* `935 `_: Typing `break` causes IPython to crash. -* `869 `_: Tab completion of `~/` shows no output post 0.10.x -* `904 `_: whos under pypy1.6 -* `773 `_: check_security_dir() and check_pid_dir() fail on network filesystem -* `915 `_: OS X Lion Terminal.app line wrap problem -* `886 `_: Notebook kernel crash when specifying --notebook-dir on commandline -* `636 `_: debugger.py: pydb broken -* `808 `_: Ctrl+C during %reset confirm message crash Qtconsole -* `927 `_: Using return outside a function crashes ipython -* `919 `_: Pop-up segfault when moving cursor out of qtconsole window -* `181 `_: cls command does not work on windows -* `917 `_: documentation typos -* `818 `_: %run does not work with non-ascii characeters in path -* `907 `_: Errors in custom completer functions can crash IPython -* `867 `_: doc: notebook password authentication howto -* `211 `_: paste command not working -* `900 `_: Tab key should insert 4 spaces in qt console -* `513 `_: [Qt console] cannot insert new lines into console functions using tab -* `906 `_: qtconsoleapp 'parse_command_line' doen't like --existing anymore -* `638 `_: Qt console --pylab=inline and getfigs(), etc. -* `710 `_: unwanted unicode passed to args -* `436 `_: Users should see tooltips for all buttons in the notebook UI -* `207 `_: ipython crashes if atexit handler raises exception -* `692 `_: use of Tracer() when debugging works but gives error messages -* `690 `_: debugger does not print error message by default in 0.11 -* `571 `_: history of multiline entries -* `749 `_: IPython.parallel test failure under Windows 7 and XP -* `890 `_: ipclusterapp.py - helep -* `885 `_: `ws-hostname` alias not recognized by notebook -* `881 `_: Missing manual.pdf? -* `744 `_: cannot create notebook in offline mode if mathjax not installed -* `865 `_: Make tracebacks from %paste show the code -* `535 `_: exception unicode handling in %run is faulty in qtconsole -* `817 `_: IPython crashed -* `799 `_: %edit magic not working on windows xp in qtconsole -* `732 `_: QTConsole wrongly promotes the index of the input line on which user presses Enter -* `662 `_: ipython test failures on Mac OS X Lion -* `650 `_: Handle bad config files better -* `829 `_: We should not insert new lines after all print statements in the notebook -* `874 `_: ipython-qtconsole: pyzmq Version Comparison -* `640 `_: matplotlib macosx windows don't respond in qtconsole -* `624 `_: ipython intermittently segfaults when figure is closed (Mac OS X) -* `871 `_: Notebook crashes if a profile is used -* `56 `_: Have %cpaste accept also Ctrl-D as a termination marker -* `849 `_: Command line options to not override profile options -* `806 `_: Provide single-port connection to kernels -* `691 `_: [wishlist] Automatically find existing kernel -* `688 `_: local security vulnerability: all ports visible to any local user. -* `866 `_: DistributionNotFound on running ipython 0.11 on Windows XP x86 -* `673 `_: raw_input appears to be round-robin for qtconsole -* `863 `_: Graceful degradation when home directory not writable -* `800 `_: Timing scripts with run -t -N fails on report output -* `858 `_: Typing 'continue' makes ipython0.11 crash -* `840 `_: all processes run on one CPU core -* `843 `_: "import braces" crashes ipython -* `836 `_: Strange Output after IPython Install -* `839 `_: Qtconsole segfaults when mouse exits window with active tooltip -* `827 `_: Add support for checking several limits before running task on engine -* `826 `_: Add support for creation of parallel task when no engine is running -* `832 `_: Improve error message for %logstop -* `831 `_: %logstart in read-only directory forbid any further command -* `814 `_: ipython does not start -- DistributionNotFound -* `794 `_: Allow >1 controller per profile -* `820 `_: Tab Completion feature -* `812 `_: Qt console crashes on Ubuntu 11.10 -* `816 `_: Import error using Python 2.7 and dateutil2.0 No module named _thread -* `756 `_: qtconsole Windows fails to print error message for '%run nonexistent_file' -* `651 `_: Completion doesn't work on element of a list -* `617 `_: [qtconsole] %hist doesn't show anything in qtconsole -* `786 `_: from __future__ import unicode_literals does not work -* `779 `_: Using irunner from virtual evn uses systemwide ipython -* `768 `_: codepage handling of output from scripts and shellcommands are not handled properly by qtconsole -* `785 `_: Don't strip leading whitespace in repr() in notebook -* `737 `_: in pickleshare.py line52 should be "if not os.path.isdir(self.root):"? -* `738 `_: in ipthon_win_post_install.py line 38 -* `777 `_: print(…, sep=…) raises SyntaxError -* `728 `_: ipcontroller crash with MPI -* `780 `_: qtconsole Out value prints before the print statements that precede it -* `632 `_: IPython Crash Report (0.10.2) -* `253 `_: Unable to install ipython on windows -* `80 `_: Split IPClusterApp into multiple Application subclasses for each subcommand -* `34 `_: non-blocking pendingResult partial results -* `739 `_: Tests fail if tornado not installed -* `719 `_: Better support Pypy -* `667 `_: qtconsole problem with default pylab profile -* `661 `_: ipythonrc referenced in magic command in 0.11 -* `665 `_: Source introspection with ?? is broken -* `724 `_: crash - ipython qtconsole, %quickref -* `655 `_: ipython with qtconsole crashes -* `593 `_: HTML Notebook Prompt can be deleted . . . -* `563 `_: use argparse instead of kvloader for flags&aliases -* `751 `_: Tornado version greater than 2.0 needed for firefox 6 -* `720 `_: Crash report when importing easter egg -* `740 `_: Ctrl-Enter clears line in notebook -* `772 `_: ipengine fails on Windows with "XXX lineno: 355, opcode: 0" -* `771 `_: Add python 3 tag to setup.py -* `767 `_: non-ascii in __doc__ string crashes qtconsole kernel when showing tooltip -* `733 `_: In Windows, %run fails to strip quotes from filename -* `721 `_: no completion in emacs by ipython(ipython.el) -* `669 `_: Do not accept an ipython_dir that's not writeable -* `711 `_: segfault on mac os x -* `500 `_: "RuntimeError: Cannot change input buffer during execution" in console_widget.py -* `707 `_: Copy and paste keyboard shortcuts do not work in Qt Console on OS X -* `478 `_: PyZMQ's use of memoryviews breaks reconstruction of numpy arrays -* `694 `_: Turning off callout tips in qtconsole -* `704 `_: return kills IPython -* `442 `_: Users should have intelligent autoindenting in the notebook -* `615 `_: Wireframe and implement a project dashboard page -* `614 `_: Wireframe and implement a notebook dashboard page -* `606 `_: Users should be able to use the notebook to import/export a notebook to .py or .rst -* `604 `_: A user should be able to leave a kernel running in the notebook and reconnect -* `298 `_: Users should be able to save a notebook and then later reload it -* `649 `_: ipython qtconsole (v0.11): setting "c.IPythonWidget.in_prompt = '>>> ' crashes -* `672 `_: What happened to Exit? -* `658 `_: Put the InteractiveShellApp section first in the auto-generated config files -* `656 `_: [suggestion] dependency checking for pyqt for Windows installer -* `654 `_: broken documentation link on download page -* `653 `_: Test failures in IPython.parallel diff --git a/docs/source/whatsnew/github-stats-0.13.rst b/docs/source/whatsnew/github-stats-0.13.rst deleted file mode 100644 index cdb9415432b..00000000000 --- a/docs/source/whatsnew/github-stats-0.13.rst +++ /dev/null @@ -1,1203 +0,0 @@ -.. _issues_list_013: - -Issues closed in the 0.13 development cycle -=========================================== - -Issues closed in 0.13 ---------------------- - -GitHub stats since IPython 0.12 (2011/12/19 - 2012/06/30) - -These lists are automatically generated, and may be incomplete or contain -duplicates. - -The following 62 authors contributed 1760 commits. - -* Aaron Culich -* Aaron Meurer -* Alex Kramer -* Andrew Giessel -* Andrew Straw -* André Matos -* Aron Ahmadia -* Ben Edwards -* Benjamin Ragan-Kelley -* Bradley M. Froehle -* Brandon Parsons -* Brian E. Granger -* Carlos Cordoba -* David Hirschfeld -* David Zderic -* Ernie French -* Fernando Perez -* Ian Murray -* Jason Grout -* Jens H Nielsen -* Jez Ng -* Jonathan March -* Jonathan Taylor -* Julian Taylor -* Jörgen Stenarson -* Kent Inverarity -* Marc Abramowitz -* Mark Wiebe -* Matthew Brett -* Matthias BUSSONNIER -* Michael Droettboom -* Mike Hansen -* Nathan Rice -* Pankaj Pandey -* Paul -* Paul Ivanov -* Piotr Zolnierczuk -* Piti Ongmongkolkul -* Puneeth Chaganti -* Robert Kern -* Ross Jones -* Roy Hyunjin Han -* Scott Tsai -* Skipper Seabold -* Stefan van der Walt -* Steven Johnson -* Takafumi Arakaki -* Ted Wright -* Thomas Hisch -* Thomas Kluyver -* Thomas Spura -* Thomi Richards -* Tim Couper -* Timo Paulssen -* Toby Gilham -* Tony S Yu -* W. Trevor King -* Walter Doerwald -* anatoly techtonik -* fawce -* mcelrath -* wilsaj - - -We closed a total of 1115 issues, 373 pull requests and 742 regular issues; -this is the full list (generated with the script -:file:`tools/github_stats.py`): - -Pull Requests (373): - -* :ghpull:`1943`: add screenshot and link into releasenotes -* :ghpull:`1954`: update some example notebooks -* :ghpull:`2048`: move _encode_binary to jsonutil.encode_images -* :ghpull:`2050`: only add quotes around xunit-file on Windows -* :ghpull:`2047`: disable auto-scroll on mozilla -* :ghpull:`2015`: Fixes for %paste with special transformations -* :ghpull:`2046`: Iptest unicode -* :ghpull:`1939`: Namespaces -* :ghpull:`2042`: increase auto-scroll threshold to 100 lines -* :ghpull:`2043`: move RemoteError import to top-level -* :ghpull:`2036`: %alias_magic -* :ghpull:`1968`: Proposal of icons for .ipynb files -* :ghpull:`2037`: remove `ipython-qtconsole` gui-script -* :ghpull:`2038`: add extra clear warning to shell doc -* :ghpull:`2029`: Ship unminified js -* :ghpull:`2007`: Add custom_control and custom_page_control variables to override the Qt widgets used by qtconsole -* :ghpull:`2034`: fix&test push/pull recarrays -* :ghpull:`2028`: Reduce unhelpful information shown by pinfo -* :ghpull:`2030`: check wxPython version in inputhook -* :ghpull:`2024`: Make interactive_usage a bit more rst friendly -* :ghpull:`2031`: disable ^C^C confirmation on Windows -* :ghpull:`2027`: match stdin encoding in frontend readline test -* :ghpull:`2025`: Fix parallel test on WinXP - wait for resource cleanup. -* :ghpull:`2016`: BUG: test runner fails in Windows if filenames contain spaces. -* :ghpull:`2020`: Fix home path expansion test in Windows. -* :ghpull:`2021`: Fix Windows pathname issue in 'odd encoding' test. -* :ghpull:`2022`: don't check writability in test for get_home_dir when HOME is undefined -* :ghpull:`1996`: frontend test tweaks -* :ghpull:`2014`: relax profile regex in notebook -* :ghpull:`2012`: Mono cursor offset -* :ghpull:`2004`: Clarify generic message spec vs. Python message API in docs -* :ghpull:`2010`: notebook: Print a warning (but do not abort) if no webbrowser can be found. -* :ghpull:`2002`: Refactor %magic into a lsmagic_docs API function. -* :ghpull:`1999`: `%magic` help: display line and cell magics in alphabetical order. -* :ghpull:`1981`: Clean BG processes created by %%script on kernel exit -* :ghpull:`1994`: Fix RST misformatting. -* :ghpull:`1951`: minor notebook startup/notebook-dir adjustments -* :ghpull:`1974`: Allow path completion on notebook. -* :ghpull:`1964`: allow multiple instances of a Magic -* :ghpull:`1991`: fix _ofind attr in %page -* :ghpull:`1988`: check for active frontend in update_restart_checkbox -* :ghpull:`1979`: Add support for tox (https://tox.readthedocs.io/) and Travis CI (http://travis-ci.org/) -* :ghpull:`1970`: dblclick to restore size of images -* :ghpull:`1978`: Notebook names truncating at the first period -* :ghpull:`1825`: second attempt at scrolled long output -* :ghpull:`1934`: Cell/Worksheet metadata -* :ghpull:`1746`: Confirm restart (configuration option, and checkbox UI) -* :ghpull:`1944`: [qtconsole] take %,%% prefix into account for completion -* :ghpull:`1973`: fix another FreeBSD $HOME symlink issue -* :ghpull:`1967`: Fix psums example description in docs -* :ghpull:`1965`: fix for #1678, undo no longer clears cells -* :ghpull:`1952`: avoid duplicate "Websockets closed" dialog on ws close -* :ghpull:`1962`: Support unicode prompts -* :ghpull:`1955`: update to latest version of vim-ipython -* :ghpull:`1945`: Add --proc option to %%script -* :ghpull:`1956`: move import RemoteError after get_exc_info -* :ghpull:`1950`: Fix for copy action (Ctrl+C) when there is no pager defined in qtconsole -* :ghpull:`1948`: Fix help string for InteractiveShell.ast_node_interactivity -* :ghpull:`1942`: swallow stderr of which in utils.process.find_cmd -* :ghpull:`1940`: fix completer css on some Chrome versions -* :ghpull:`1938`: remove remaining references to deprecated XREP/XREQ names -* :ghpull:`1925`: Fix styling of superscripts and subscripts. Closes #1924. -* :ghpull:`1936`: increase duration of save messages -* :ghpull:`1937`: add %save -f -* :ghpull:`1935`: add version checking to pyreadline import test -* :ghpull:`1849`: Octave magics -* :ghpull:`1759`: github, merge PR(s) just by number(s) -* :ghpull:`1931`: Win py3fixes -* :ghpull:`1933`: oinspect.find_file: Additional safety if file cannot be found. -* :ghpull:`1932`: Fix adding functions to CommandChainDispatcher with equal priority on Py 3 -* :ghpull:`1928`: Select NoDB by default -* :ghpull:`1923`: Add IPython syntax support to the %timeit magic, in line and cell mode -* :ghpull:`1926`: Make completer recognize escaped quotes in strings. -* :ghpull:`1893`: Update Parallel Magics and Exception Display -* :ghpull:`1921`: magic_arguments: dedent but otherwise preserve indentation. -* :ghpull:`1919`: Use oinspect in CodeMagics._find_edit_target -* :ghpull:`1918`: don't warn in iptest if deathrow/quarantine are missing -* :ghpull:`1917`: Fix for %pdef on Python 3 -* :ghpull:`1913`: Fix for #1428 -* :ghpull:`1911`: temporarily skip autoreload tests -* :ghpull:`1909`: Fix for #1908, use os.path.normcase for safe filename comparisons -* :ghpull:`1907`: py3compat fixes for %%script and tests -* :ghpull:`1906`: ofind finds non-unique cell magics -* :ghpull:`1845`: Fixes to inspection machinery for magics -* :ghpull:`1902`: Workaround fix for gh-1632; minimal revert of gh-1424 -* :ghpull:`1900`: Cython libs -* :ghpull:`1899`: add ScriptMagics to class list for generated config -* :ghpull:`1898`: minimize manpages -* :ghpull:`1897`: use glob for bad exclusion warning -* :ghpull:`1855`: %%script and %%file magics -* :ghpull:`1870`: add %%capture for capturing stdout/err -* :ghpull:`1861`: Use dvipng to format sympy.Matrix -* :ghpull:`1867`: Fix 1px margin bouncing of selected menu item. -* :ghpull:`1889`: Reconnect when the websocket connection closes unexpectedly -* :ghpull:`1886`: Fix a bug in renaming notebook -* :ghpull:`1895`: Fix error in test suite with ip.system() -* :ghpull:`1762`: add `locate` entry points -* :ghpull:`1883`: Fix vertical offset due to bold/italics, and bad browser fonts. -* :ghpull:`1875`: re-write columnize, with intermediate step. -* :ghpull:`1851`: new completer for qtconsole. -* :ghpull:`1892`: Remove suspicious quotes in interactiveshell.py -* :ghpull:`1864`: Rmagic exceptions -* :ghpull:`1829`: [notebook] don't care about leading prct in completion -* :ghpull:`1832`: Make svg, jpeg and png images resizable in notebook. -* :ghpull:`1674`: HTML Notebook carriage-return handling, take 2 -* :ghpull:`1882`: Remove importlib dependency which not available in Python 2.6. -* :ghpull:`1879`: Correct stack depth for variable expansion in !system commands -* :ghpull:`1841`: [notebook] deduplicate completion results -* :ghpull:`1850`: Remove args/kwargs handling in TryNext, fix %paste error messages. -* :ghpull:`1663`: Keep line-endings in ipynb -* :ghpull:`1815`: Make : invalid in filenames in the Notebook JS code. -* :ghpull:`1819`: doc: cleanup the parallel psums example a little -* :ghpull:`1839`: External cleanup -* :ghpull:`1782`: fix Magic menu in qtconsole, split in groups -* :ghpull:`1862`: Minor bind_kernel improvements -* :ghpull:`1857`: Prevent jumping of window to input when output is clicked. -* :ghpull:`1856`: Fix 1px jumping of cells and menus in Notebook. -* :ghpull:`1852`: fix chained resubmissions -* :ghpull:`1780`: Rmagic extension -* :ghpull:`1847`: add InlineBackend to ConsoleApp class list -* :ghpull:`1836`: preserve header for resubmitted tasks -* :ghpull:`1828`: change default extension to .ipy for %save -r -* :ghpull:`1800`: Reintroduce recall -* :ghpull:`1830`: lsmagic lists magics in alphabetical order -* :ghpull:`1773`: Update SymPy profile: SymPy's latex() can now print set and frozenset -* :ghpull:`1761`: Edited documentation to use IPYTHONDIR in place of ~/.ipython -* :ghpull:`1822`: aesthetics pass on AsyncResult.display_outputs -* :ghpull:`1821`: ENTER submits the rename notebook dialog. -* :ghpull:`1820`: NotebookApp: Make the number of ports to retry user configurable. -* :ghpull:`1816`: Always use filename as the notebook name. -* :ghpull:`1813`: Add assert_in method to nose for Python 2.6 -* :ghpull:`1711`: New Tooltip, New Completer and JS Refactor -* :ghpull:`1798`: a few simple fixes for docs/parallel -* :ghpull:`1812`: Ensure AsyncResult.display_outputs doesn't display empty streams -* :ghpull:`1811`: warn on nonexistent exclusions in iptest -* :ghpull:`1810`: fix for #1809, failing tests in IPython.zmq -* :ghpull:`1808`: Reposition alternate upload for firefox [need cross browser/OS/language test] -* :ghpull:`1742`: Check for custom_exceptions only once -* :ghpull:`1807`: add missing cython exclusion in iptest -* :ghpull:`1805`: Fixed a vcvarsall.bat error on win32/Py2.7 when trying to compile with m... -* :ghpull:`1739`: Dashboard improvement (necessary merge of #1658 and #1676 + fix #1492) -* :ghpull:`1770`: Cython related magic functions -* :ghpull:`1707`: Accept --gui=<...> switch in IPython qtconsole. -* :ghpull:`1797`: Fix comment which breaks Emacs syntax highlighting. -* :ghpull:`1795`: fix %gui magic -* :ghpull:`1793`: Raise repr limit for strings to 80 characters (from 30). -* :ghpull:`1794`: don't use XDG path on OS X -* :ghpull:`1792`: Unicode-aware logger -* :ghpull:`1791`: update zmqshell magics -* :ghpull:`1787`: DOC: Remove regression from qt-console docs. -* :ghpull:`1758`: test_pr, fallback on http if git protocol fail, and SSL errors... -* :ghpull:`1748`: Fix some tests for Python 3.3 -* :ghpull:`1755`: test for pygments before running qt tests -* :ghpull:`1771`: Make default value of interactivity passed to run_ast_nodes configurable -* :ghpull:`1784`: restore loadpy to load -* :ghpull:`1768`: Update parallel magics -* :ghpull:`1779`: Tidy up error raising in magic decorators. -* :ghpull:`1769`: Allow cell mode timeit without setup code. -* :ghpull:`1716`: Fix for fake filenames in verbose traceback -* :ghpull:`1763`: [qtconsole] fix append_plain_html -> append_html -* :ghpull:`1732`: Refactoring of the magics system and implementation of cell magics -* :ghpull:`1630`: Merge divergent Kernel implementations -* :ghpull:`1705`: [notebook] Make pager resizable, and remember size... -* :ghpull:`1606`: Share code for %pycat and %loadpy, make %pycat aware of URLs -* :ghpull:`1757`: Open IPython notebook hyperlinks in a new window using target=_blank -* :ghpull:`1754`: Fix typo enconters->encounters -* :ghpull:`1753`: Clear window title when kernel is restarted -* :ghpull:`1449`: Fix for bug #735 : Images missing from XML/SVG export -* :ghpull:`1743`: Tooltip completer js refactor -* :ghpull:`1681`: add qt config option to clear_on_kernel_restart -* :ghpull:`1733`: Tooltip completer js refactor -* :ghpull:`1727`: terminate kernel after embed_kernel tests -* :ghpull:`1737`: add HistoryManager to ipapp class list -* :ghpull:`1686`: ENH: Open a notebook from the command line -* :ghpull:`1709`: fixes #1708, failing test in arg_split on windows -* :ghpull:`1718`: Use CRegExp trait for regular expressions. -* :ghpull:`1729`: Catch failure in repr() for %whos -* :ghpull:`1726`: use eval for command-line args instead of exec -* :ghpull:`1724`: fix scatter/gather with targets='all' -* :ghpull:`1725`: add --no-ff to git pull in test_pr -* :ghpull:`1721`: Tooltip completer js refactor -* :ghpull:`1657`: Add `wait` optional argument to `hooks.editor` -* :ghpull:`1717`: Define generic sys.ps{1,2,3}, for use by scripts. -* :ghpull:`1691`: Finish PR #1446 -* :ghpull:`1710`: update MathJax CDN url for https -* :ghpull:`1713`: Make autocall regexp's configurable. -* :ghpull:`1703`: Allow TryNext to have an error message without it affecting the command chain -* :ghpull:`1714`: minor adjustments to test_pr -* :ghpull:`1704`: ensure all needed qt parts can be imported before settling for one -* :ghpull:`1706`: Mark test_push_numpy_nocopy as a known failure for Python 3 -* :ghpull:`1698`: fix tooltip on token with number -* :ghpull:`1245`: pythonw py3k fixes for issue #1226 -* :ghpull:`1685`: Add script to test pull request -* :ghpull:`1693`: deprecate IPYTHON_DIR in favor of IPYTHONDIR -* :ghpull:`1695`: Avoid deprecated warnings from ipython-qtconsole.desktop. -* :ghpull:`1694`: Add quote to notebook to allow it to load -* :ghpull:`1689`: Fix sys.path missing '' as first entry in `ipython kernel`. -* :ghpull:`1687`: import Binary from bson instead of pymongo -* :ghpull:`1616`: Make IPython.core.display.Image less notebook-centric -* :ghpull:`1684`: CLN: Remove redundant function definition. -* :ghpull:`1670`: Point %pastebin to gist -* :ghpull:`1669`: handle pyout messages in test_message_spec -* :ghpull:`1295`: add binary-tree engine interconnect example -* :ghpull:`1642`: Cherry-picked commits from 0.12.1 release -* :ghpull:`1659`: Handle carriage return characters ("\r") in HTML notebook output. -* :ghpull:`1656`: ensure kernels are cleaned up in embed_kernel tests -* :ghpull:`1664`: InteractiveShell.run_code: Update docstring. -* :ghpull:`1662`: Delay flushing softspace until after cell finishes -* :ghpull:`1643`: handle jpg/jpeg in the qtconsole -* :ghpull:`1652`: add patch_pyzmq() for backporting a few changes from newer pyzmq -* :ghpull:`1650`: DOC: moving files with SSH launchers -* :ghpull:`1357`: add IPython.embed_kernel() -* :ghpull:`1640`: Finish up embed_kernel -* :ghpull:`1651`: Remove bundled Itpl module -* :ghpull:`1634`: incremental improvements to SSH launchers -* :ghpull:`1649`: move examples/test_embed into examples/tests/embed -* :ghpull:`1633`: Fix installing extension from local file on Windows -* :ghpull:`1645`: Exclude UserDict when deep reloading NumPy. -* :ghpull:`1637`: Removed a ':' which shouldn't have been there -* :ghpull:`1631`: TST: QApplication doesn't quit early enough with PySide. -* :ghpull:`1629`: evaluate a few dangling validate_message generators -* :ghpull:`1621`: clear In[] prompt numbers on "Clear All Output" -* :ghpull:`1627`: Test the Message Spec -* :ghpull:`1624`: Fixes for byte-compilation on Python 3 -* :ghpull:`1615`: Add show() method to figure objects. -* :ghpull:`1625`: Fix deepreload on Python 3 -* :ghpull:`1620`: pyin message now have execution_count -* :ghpull:`1457`: Update deepreload to use a rewritten knee.py. Fixes dreload(numpy). -* :ghpull:`1613`: allow map / parallel function for single-engine views -* :ghpull:`1609`: exit notebook cleanly on SIGINT, SIGTERM -* :ghpull:`1607`: cleanup sqlitedb temporary db file after tests -* :ghpull:`1608`: don't rely on timedelta.total_seconds in AsyncResult -* :ghpull:`1599`: Fix for %run -d on Python 3 -* :ghpull:`1602`: Fix %env magic on Python 3. -* :ghpull:`1603`: Remove python3 profile -* :ghpull:`1604`: Exclude IPython.quarantine from installation -* :ghpull:`1600`: Specify encoding for io.open in notebook_reformat tests -* :ghpull:`1605`: Small fixes for Animation and Progress notebook -* :ghpull:`1529`: __all__ feature, improvement to dir2, and tests for both -* :ghpull:`1548`: add sugar methods/properties to AsyncResult -* :ghpull:`1535`: Fix pretty printing dispatch -* :ghpull:`1399`: Use LaTeX to print various built-in types with the SymPy printing extension -* :ghpull:`1597`: re-enter kernel.eventloop after catching SIGINT -* :ghpull:`1490`: rename plaintext cell -> raw cell -* :ghpull:`1480`: Fix %notebook magic, etc. nbformat unicode tests and fixes -* :ghpull:`1588`: Gtk3 integration with ipython works. -* :ghpull:`1595`: Examples syntax (avoid errors installing on Python 3) -* :ghpull:`1526`: Find encoding for Python files -* :ghpull:`1594`: Fix writing git commit ID to a file on build with Python 3 -* :ghpull:`1556`: shallow-copy DictDB query results -* :ghpull:`1502`: small changes in response to pyflakes pass -* :ghpull:`1445`: Don't build sphinx docs for sdists -* :ghpull:`1538`: store git commit hash in utils._sysinfo instead of hidden data file -* :ghpull:`1546`: attempt to suppress exceptions in channel threads at shutdown -* :ghpull:`1559`: update tools/github_stats.py to use GitHub API v3 -* :ghpull:`1563`: clear_output improvements -* :ghpull:`1560`: remove obsolete discussion of Twisted/trial from testing docs -* :ghpull:`1569`: BUG: qtconsole -- non-standard handling of \a and \b. [Fixes #1561] -* :ghpull:`1573`: BUG: Ctrl+C crashes wx pylab kernel in qtconsole. -* :ghpull:`1568`: fix PR #1567 -* :ghpull:`1567`: Fix: openssh_tunnel did not parse port in `server` -* :ghpull:`1565`: fix AsyncResult.abort -* :ghpull:`1552`: use os.getcwdu in NotebookManager -* :ghpull:`1541`: display_pub flushes stdout/err -* :ghpull:`1544`: make MultiKernelManager.kernel_manager_class configurable -* :ghpull:`1517`: Fix indentation bug in IPython/lib/pretty.py -* :ghpull:`1519`: BUG: Include the name of the exception type in its pretty format. -* :ghpull:`1489`: Fix zero-copy push -* :ghpull:`1477`: fix dangling `buffer` in IPython.parallel.util -* :ghpull:`1514`: DOC: Fix references to IPython.lib.pretty instead of the old location -* :ghpull:`1481`: BUG: Improve placement of CallTipWidget -* :ghpull:`1496`: BUG: LBYL when clearing the output history on shutdown. -* :ghpull:`1508`: fix sorting profiles in clustermanager -* :ghpull:`1495`: BUG: Fix pretty-printing for overzealous objects -* :ghpull:`1472`: more general fix for #662 -* :ghpull:`1483`: updated magic_history docstring -* :ghpull:`1383`: First version of cluster web service. -* :ghpull:`1398`: fix %tb after SyntaxError -* :ghpull:`1440`: Fix for failing testsuite when using --with-xml-coverage on windows. -* :ghpull:`1419`: Add %install_ext magic function. -* :ghpull:`1424`: Win32 shell interactivity -* :ghpull:`1468`: Simplify structure of a Job in the TaskScheduler -* :ghpull:`1447`: 1107 - Tab autocompletion can suggest invalid syntax -* :ghpull:`1469`: Fix typo in comment (insert space) -* :ghpull:`1463`: Fix completion when importing modules in the cwd. -* :ghpull:`1466`: Fix for issue #1437, unfriendly windows qtconsole error handling -* :ghpull:`1432`: Fix ipython directive -* :ghpull:`1465`: allow `ipython help subcommand` syntax -* :ghpull:`1416`: Conditional import of ctypes in inputhook -* :ghpull:`1462`: expedite parallel tests -* :ghpull:`1410`: Add javascript library and css stylesheet loading to JS class. -* :ghpull:`1448`: Fix for #875 Never build unicode error messages -* :ghpull:`1458`: use eval to uncan References -* :ghpull:`1450`: load mathjax from CDN via https -* :ghpull:`1451`: include heading level in JSON -* :ghpull:`1444`: Fix pyhton -> python typos -* :ghpull:`1414`: ignore errors in shell.var_expand -* :ghpull:`1430`: Fix for tornado check for tornado < 1.1.0 -* :ghpull:`1413`: get_home_dir expands symlinks, adjust test accordingly -* :ghpull:`1385`: updated and prettified magic doc strings -* :ghpull:`1406`: Browser selection -* :ghpull:`1377`: Saving non-ascii history -* :ghpull:`1402`: fix symlinked /home issue for FreeBSD -* :ghpull:`1405`: Only monkeypatch xunit when the tests are run using it. -* :ghpull:`1395`: Xunit & KnownFailure -* :ghpull:`1396`: Fix for %tb magic. -* :ghpull:`1386`: Jsd3 -* :ghpull:`1388`: Add simple support for running inside a virtualenv -* :ghpull:`1391`: Improve Hub/Scheduler when no engines are registered -* :ghpull:`1369`: load header with engine id when engine dies in TaskScheduler -* :ghpull:`1353`: Save notebook as script using unicode file handle. -* :ghpull:`1352`: Add '-m mod : run library module as a script' option. -* :ghpull:`1363`: Fix some minor color/style config issues in the qtconsole -* :ghpull:`1371`: Adds a quiet keyword to sync_imports -* :ghpull:`1387`: Fixing Cell menu to update cell type select box. -* :ghpull:`1296`: Wx gui example: fixes the broken example for `%gui wx`. -* :ghpull:`1372`: ipcontroller cleans up connection files unless reuse=True -* :ghpull:`1374`: remove calls to meaningless ZMQStream.on_err -* :ghpull:`1370`: allow draft76 websockets (Safari) -* :ghpull:`1368`: Ensure handler patterns are str, not unicode -* :ghpull:`1361`: Notebook bug fix branch -* :ghpull:`1364`: avoid jsonlib returning Decimal -* :ghpull:`1362`: Don't log complete contents of history replies, even in debug -* :ghpull:`1347`: fix weird magic completion in notebook -* :ghpull:`1346`: fixups for alternate URL prefix stuff -* :ghpull:`1336`: crack at making notebook.html use the layout.html template -* :ghpull:`1331`: RST and heading cells -* :ghpull:`1247`: fixes a bug causing extra newlines after comments. -* :ghpull:`1332`: notebook - allow prefixes in URL path. -* :ghpull:`1341`: Don't attempt to tokenize binary files for tracebacks -* :ghpull:`1334`: added key handler for control-s to notebook, seems to work pretty well -* :ghpull:`1338`: Fix see also in docstrings so API docs build -* :ghpull:`1335`: Notebook toolbar UI -* :ghpull:`1299`: made notebook.html extend layout.html -* :ghpull:`1318`: make Ctrl-D in qtconsole act same as in terminal (ready to merge) -* :ghpull:`1328`: Coverage -* :ghpull:`1206`: don't preserve fixConsole output in json -* :ghpull:`1330`: Add linewrapping to text cells (new feature in CodeMirror). -* :ghpull:`1309`: Inoculate clearcmd extension into %reset functionality -* :ghpull:`1327`: Updatecm2 -* :ghpull:`1326`: Removing Ace edit capability. -* :ghpull:`1325`: forgotten selected_cell -> get_selected_cell -* :ghpull:`1316`: Pass subprocess test runners a suitable location for xunit output -* :ghpull:`1303`: Updatecm -* :ghpull:`1312`: minor heartbeat tweaks -* :ghpull:`1306`: Fix %prun input parsing for escaped characters (closes #1302) -* :ghpull:`1301`: New "Fix for issue #1202" based on current master. -* :ghpull:`1289`: Make autoreload extension work on Python 3. -* :ghpull:`1288`: Don't ask for confirmation when stdin isn't available -* :ghpull:`1294`: TaskScheduler.hwm default to 1 instead of 0 -* :ghpull:`1283`: HeartMonitor.period should be an Integer -* :ghpull:`1264`: Aceify -* :ghpull:`1284`: a fix for GH 1269 -* :ghpull:`1213`: BUG: Minor typo in history_console_widget.py -* :ghpull:`1267`: add NoDB for non-recording Hub -* :ghpull:`1222`: allow Reference as callable in map/apply -* :ghpull:`1257`: use self.kernel_manager_class in qtconsoleapp -* :ghpull:`1253`: set auto_create flag for notebook apps -* :ghpull:`1262`: Heartbeat no longer shares the app's Context -* :ghpull:`1229`: Fix display of SyntaxError in Python 3 -* :ghpull:`1256`: Dewijmoize -* :ghpull:`1246`: Skip tests that require X, when importing pylab results in RuntimeError. -* :ghpull:`1211`: serve local files in notebook-dir -* :ghpull:`1224`: edit text cells on double-click instead of single-click -* :ghpull:`1187`: misc notebook: connection file cleanup, first heartbeat, startup flush -* :ghpull:`1207`: fix loadpy duplicating newlines -* :ghpull:`1129`: Unified setup.py -* :ghpull:`1199`: Reduce IPython.external.* -* :ghpull:`1218`: Added -q option to %prun for suppression of the output, along with editing the dochelp string. -* :ghpull:`1217`: Added -q option to %prun for suppression of the output, along with editing the dochelp string -* :ghpull:`1175`: core.completer: Clean up excessive and unused code. -* :ghpull:`1196`: docs: looks like a file path might have been accidentally pasted in the middle of a word -* :ghpull:`1190`: Fix link to Chris Fonnesbeck blog post about 0.11 highlights. - -Issues (742): - -* :ghissue:`1943`: add screenshot and link into releasenotes -* :ghissue:`1570`: [notebook] remove 'left panel' references from example. -* :ghissue:`1954`: update some example notebooks -* :ghissue:`2048`: move _encode_binary to jsonutil.encode_images -* :ghissue:`2050`: only add quotes around xunit-file on Windows -* :ghissue:`2047`: disable auto-scroll on mozilla -* :ghissue:`1258`: Magic %paste error -* :ghissue:`2015`: Fixes for %paste with special transformations -* :ghissue:`760`: Windows: test runner fails if repo path contains spaces -* :ghissue:`2046`: Iptest unicode -* :ghissue:`1939`: Namespaces -* :ghissue:`2042`: increase auto-scroll threshold to 100 lines -* :ghissue:`2043`: move RemoteError import to top-level -* :ghissue:`641`: In %magic help, remove duplicate aliases -* :ghissue:`2036`: %alias_magic -* :ghissue:`1968`: Proposal of icons for .ipynb files -* :ghissue:`825`: keyboardinterrupt crashes gtk gui when gtk.set_interactive is not available -* :ghissue:`1971`: Remove duplicate magics docs -* :ghissue:`2040`: Namespaces for cleaner public APIs -* :ghissue:`2039`: ipython parallel import exception -* :ghissue:`2035`: Getdefaultencoding test error with sympy 0.7.1_git -* :ghissue:`2037`: remove `ipython-qtconsole` gui-script -* :ghissue:`1516`: ipython-qtconsole script isn't installed for Python 2.x -* :ghissue:`1297`: "ipython -p sh" is in documentation but doesn't work -* :ghissue:`2038`: add extra clear warning to shell doc -* :ghissue:`1265`: please ship unminified js and css sources -* :ghissue:`2029`: Ship unminified js -* :ghissue:`1920`: Provide an easy way to override the Qt widget used by qtconsole -* :ghissue:`2007`: Add custom_control and custom_page_control variables to override the Qt widgets used by qtconsole -* :ghissue:`2009`: In %magic help, remove duplicate aliases -* :ghissue:`2033`: ipython parallel pushing and pulling recarrays -* :ghissue:`2034`: fix&test push/pull recarrays -* :ghissue:`2028`: Reduce unhelpful information shown by pinfo -* :ghissue:`1992`: Tab completion fails with many spaces in filename -* :ghissue:`1885`: handle too old wx -* :ghissue:`2030`: check wxPython version in inputhook -* :ghissue:`2024`: Make interactive_usage a bit more rst friendly -* :ghissue:`2031`: disable ^C^C confirmation on Windows -* :ghissue:`2023`: Unicode test failure on OS X -* :ghissue:`2027`: match stdin encoding in frontend readline test -* :ghissue:`1901`: Windows: parallel test fails assert, leaves 14 python processes alive -* :ghissue:`2025`: Fix parallel test on WinXP - wait for resource cleanup. -* :ghissue:`1986`: Line magic function `%R` not found. (Rmagic) -* :ghissue:`1712`: test failure in ubuntu package daily build -* :ghissue:`1183`: 0.12 testsuite failures -* :ghissue:`2016`: BUG: test runner fails in Windows if filenames contain spaces. -* :ghissue:`1806`: Alternate upload methods in firefox -* :ghissue:`2019`: Windows: home directory expansion test fails -* :ghissue:`2020`: Fix home path expansion test in Windows. -* :ghissue:`2017`: Windows core test error - filename quoting -* :ghissue:`2021`: Fix Windows pathname issue in 'odd encoding' test. -* :ghissue:`1998`: call to nt.assert_true(path._writable_dir(home)) returns false in test_path.py -* :ghissue:`2022`: don't check writability in test for get_home_dir when HOME is undefined -* :ghissue:`1589`: Test failures and docs don't build on Mac OS X Lion -* :ghissue:`1996`: frontend test tweaks -* :ghissue:`2011`: Notebook server can't start cluster with hyphen-containing profile name -* :ghissue:`2014`: relax profile regex in notebook -* :ghissue:`2013`: brew install pyqt -* :ghissue:`2005`: Strange output artifacts in footer of notebook -* :ghissue:`2012`: Mono cursor offset -* :ghissue:`2004`: Clarify generic message spec vs. Python message API in docs -* :ghissue:`2006`: Don't crash when starting notebook server if runnable browser not found -* :ghissue:`2010`: notebook: Print a warning (but do not abort) if no webbrowser can be found. -* :ghissue:`2008`: pip install virtualenv -* :ghissue:`2003`: Wrong case of rmagic in docs -* :ghissue:`2002`: Refactor %magic into a lsmagic_docs API function. -* :ghissue:`2000`: kernel.js consistency with generic IPython message format. -* :ghissue:`1999`: `%magic` help: display line and cell magics in alphabetical order. -* :ghissue:`1635`: test_prun_quotes fails on Windows -* :ghissue:`1984`: Cannot restart Notebook when using `%%script --bg` -* :ghissue:`1981`: Clean BG processes created by %%script on kernel exit -* :ghissue:`1994`: Fix RST misformatting. -* :ghissue:`1949`: Introduce Notebook Magics -* :ghissue:`1985`: Kernels should start in notebook dir when manually specified -* :ghissue:`1980`: Notebook should check that --notebook-dir exists -* :ghissue:`1951`: minor notebook startup/notebook-dir adjustments -* :ghissue:`1969`: tab completion in notebook for paths not triggered -* :ghissue:`1974`: Allow path completion on notebook. -* :ghissue:`1964`: allow multiple instances of a Magic -* :ghissue:`1960`: %page not working -* :ghissue:`1991`: fix _ofind attr in %page -* :ghissue:`1982`: Shutdown qtconsole problem? -* :ghissue:`1988`: check for active frontend in update_restart_checkbox -* :ghissue:`1979`: Add support for tox (https://tox.readthedocs.io/) and Travis CI (http://travis-ci.org/) -* :ghissue:`1989`: Parallel: output of %px and %px${suffix} is inconsistent -* :ghissue:`1966`: ValueError: packer could not serialize a simple message -* :ghissue:`1987`: Notebook: MathJax offline install not recognized -* :ghissue:`1970`: dblclick to restore size of images -* :ghissue:`1983`: Notebook does not save heading level -* :ghissue:`1978`: Notebook names truncating at the first period -* :ghissue:`1553`: Limited size of output cells and provide scroll bars for such output cells -* :ghissue:`1825`: second attempt at scrolled long output -* :ghissue:`1915`: add cell-level metadata -* :ghissue:`1934`: Cell/Worksheet metadata -* :ghissue:`1746`: Confirm restart (configuration option, and checkbox UI) -* :ghissue:`1790`: Commenting function. -* :ghissue:`1767`: Tab completion problems with cell magics -* :ghissue:`1944`: [qtconsole] take %,%% prefix into account for completion -* :ghissue:`1973`: fix another FreeBSD $HOME symlink issue -* :ghissue:`1972`: Fix completion of '%tim' in the Qt console -* :ghissue:`1887`: Make it easy to resize jpeg/png images back to original size. -* :ghissue:`1967`: Fix psums example description in docs -* :ghissue:`1678`: ctrl-z clears cell output in notebook when pressed enough times -* :ghissue:`1965`: fix for #1678, undo no longer clears cells -* :ghissue:`1952`: avoid duplicate "Websockets closed" dialog on ws close -* :ghissue:`1961`: UnicodeDecodeError on directory with unicode chars in prompt -* :ghissue:`1963`: styling prompt, {color.Normal} excepts -* :ghissue:`1962`: Support unicode prompts -* :ghissue:`1959`: %page not working on qtconsole for Windows XP 32-bit -* :ghissue:`1955`: update to latest version of vim-ipython -* :ghissue:`1945`: Add --proc option to %%script -* :ghissue:`1957`: fix indentation in kernel.js -* :ghissue:`1956`: move import RemoteError after get_exc_info -* :ghissue:`1950`: Fix for copy action (Ctrl+C) when there is no pager defined in qtconsole -* :ghissue:`1948`: Fix help string for InteractiveShell.ast_node_interactivity -* :ghissue:`1941`: script magics cause terminal spam -* :ghissue:`1942`: swallow stderr of which in utils.process.find_cmd -* :ghissue:`1833`: completer draws slightly too small on Chrome -* :ghissue:`1940`: fix completer css on some Chrome versions -* :ghissue:`1938`: remove remaining references to deprecated XREP/XREQ names -* :ghissue:`1924`: HTML superscripts not shown raised in the notebook -* :ghissue:`1925`: Fix styling of superscripts and subscripts. Closes #1924. -* :ghissue:`1461`: User notification if notebook saving fails -* :ghissue:`1936`: increase duration of save messages -* :ghissue:`1542`: %save magic fails in clients without stdin if file already exists -* :ghissue:`1937`: add %save -f -* :ghissue:`1572`: pyreadline version dependency not correctly checked -* :ghissue:`1935`: add version checking to pyreadline import test -* :ghissue:`1849`: Octave magics -* :ghissue:`1759`: github, merge PR(s) just by number(s) -* :ghissue:`1931`: Win py3fixes -* :ghissue:`1646`: Meaning of restart parameter in client.shutdown() unclear -* :ghissue:`1933`: oinspect.find_file: Additional safety if file cannot be found. -* :ghissue:`1916`: %paste doesn't work on py3 -* :ghissue:`1932`: Fix adding functions to CommandChainDispatcher with equal priority on Py 3 -* :ghissue:`1928`: Select NoDB by default -* :ghissue:`1923`: Add IPython syntax support to the %timeit magic, in line and cell mode -* :ghissue:`1926`: Make completer recognize escaped quotes in strings. -* :ghissue:`1929`: Ipython-qtconsole (0.12.1) hangs with Python 2.7.3, Windows 7 64 bit -* :ghissue:`1409`: [qtconsole] forward delete bring completion into current line -* :ghissue:`1922`: py3k compatibility for setupegg.py -* :ghissue:`1598`: document that sync_imports() can't handle "import foo as bar" -* :ghissue:`1893`: Update Parallel Magics and Exception Display -* :ghissue:`1890`: Docstrings for magics that use @magic_arguments are rendered wrong -* :ghissue:`1921`: magic_arguments: dedent but otherwise preserve indentation. -* :ghissue:`1919`: Use oinspect in CodeMagics._find_edit_target -* :ghissue:`1918`: don't warn in iptest if deathrow/quarantine are missing -* :ghissue:`1914`: %pdef failing on python3 -* :ghissue:`1917`: Fix for %pdef on Python 3 -* :ghissue:`1428`: Failing test that prun does not clobber string escapes -* :ghissue:`1913`: Fix for #1428 -* :ghissue:`1911`: temporarily skip autoreload tests -* :ghissue:`1549`: autoreload extension crashes ipython -* :ghissue:`1908`: find_file errors on windows -* :ghissue:`1909`: Fix for #1908, use os.path.normcase for safe filename comparisons -* :ghissue:`1907`: py3compat fixes for %%script and tests -* :ghissue:`1904`: %%px? doesn't work, shows info for %px, general cell magic problem -* :ghissue:`1906`: ofind finds non-unique cell magics -* :ghissue:`1894`: Win64 binary install fails -* :ghissue:`1799`: Source file not found for magics -* :ghissue:`1845`: Fixes to inspection machinery for magics -* :ghissue:`1774`: Some magics seems broken -* :ghissue:`1586`: Clean up tight coupling between Notebook, CodeCell and Kernel Javascript objects -* :ghissue:`1632`: Win32 shell interactivity apparently broke qtconsole "cd" magic -* :ghissue:`1902`: Workaround fix for gh-1632; minimal revert of gh-1424 -* :ghissue:`1900`: Cython libs -* :ghissue:`1503`: Cursor is offset in notebook in Chrome 17 on Linux -* :ghissue:`1426`: Qt console doesn't handle the `--gui` flag correctly. -* :ghissue:`1180`: Can't start IPython kernel in Spyder -* :ghissue:`581`: test IPython.zmq -* :ghissue:`1593`: Name embedded in notebook overrides filename -* :ghissue:`1899`: add ScriptMagics to class list for generated config -* :ghissue:`1618`: generate or minimize manpages -* :ghissue:`1898`: minimize manpages -* :ghissue:`1896`: Windows: apparently spurious warning 'Excluding nonexistent file' ... test_exampleip -* :ghissue:`1897`: use glob for bad exclusion warning -* :ghissue:`1215`: updated %quickref to show short-hand for %sc and %sx -* :ghissue:`1855`: %%script and %%file magics -* :ghissue:`1863`: Ability to silence a cell in the notebook -* :ghissue:`1870`: add %%capture for capturing stdout/err -* :ghissue:`1861`: Use dvipng to format sympy.Matrix -* :ghissue:`1867`: Fix 1px margin bouncing of selected menu item. -* :ghissue:`1889`: Reconnect when the websocket connection closes unexpectedly -* :ghissue:`1577`: If a notebook loses its network connection WebSockets won't reconnect -* :ghissue:`1886`: Fix a bug in renaming notebook -* :ghissue:`1895`: Fix error in test suite with ip.system() -* :ghissue:`1762`: add `locate` entry points -* :ghissue:`1883`: Fix vertical offset due to bold/italics, and bad browser fonts. -* :ghissue:`1875`: re-write columnize, with intermediate step. -* :ghissue:`1860`: IPython.utils.columnize sometime wrong... -* :ghissue:`1851`: new completer for qtconsole. -* :ghissue:`1892`: Remove suspicious quotes in interactiveshell.py -* :ghissue:`1854`: Class `%hierarchy` and graphiz `%%dot` magics -* :ghissue:`1827`: Sending tracebacks over ZMQ should protect against unicode failure -* :ghissue:`1864`: Rmagic exceptions -* :ghissue:`1829`: [notebook] don't care about leading prct in completion -* :ghissue:`1832`: Make svg, jpeg and png images resizable in notebook. -* :ghissue:`1674`: HTML Notebook carriage-return handling, take 2 -* :ghissue:`1874`: cython_magic uses importlib, which doesn't ship with py2.6 -* :ghissue:`1882`: Remove importlib dependency which not available in Python 2.6. -* :ghissue:`1878`: shell access using ! will not fill class or function scope vars -* :ghissue:`1879`: Correct stack depth for variable expansion in !system commands -* :ghissue:`1840`: New JS completer should merge completions before display -* :ghissue:`1841`: [notebook] deduplicate completion results -* :ghissue:`1736`: no good error message on missing tkinter and %paste -* :ghissue:`1741`: Display message from TryNext error in magic_paste -* :ghissue:`1850`: Remove args/kwargs handling in TryNext, fix %paste error messages. -* :ghissue:`1663`: Keep line-endings in ipynb -* :ghissue:`1872`: Matplotlib window freezes using intreractive plot in qtconsole -* :ghissue:`1869`: Improve CodeMagics._find_edit_target -* :ghissue:`1781`: Colons in notebook name causes notebook deletion without warning -* :ghissue:`1815`: Make : invalid in filenames in the Notebook JS code. -* :ghissue:`1819`: doc: cleanup the parallel psums example a little -* :ghissue:`1838`: externals cleanup -* :ghissue:`1839`: External cleanup -* :ghissue:`1782`: fix Magic menu in qtconsole, split in groups -* :ghissue:`1862`: Minor bind_kernel improvements -* :ghissue:`1859`: kernmagic during console startup -* :ghissue:`1857`: Prevent jumping of window to input when output is clicked. -* :ghissue:`1856`: Fix 1px jumping of cells and menus in Notebook. -* :ghissue:`1848`: task fails with "AssertionError: not enough buffers!" after second resubmit -* :ghissue:`1852`: fix chained resubmissions -* :ghissue:`1780`: Rmagic extension -* :ghissue:`1853`: Fix jumpy notebook behavior -* :ghissue:`1842`: task with UnmetDependency error still owned by engine -* :ghissue:`1847`: add InlineBackend to ConsoleApp class list -* :ghissue:`1846`: Exceptions within multiprocessing crash Ipython notebook kernel -* :ghissue:`1843`: Notebook does not exist and permalinks -* :ghissue:`1837`: edit magic broken in head -* :ghissue:`1834`: resubmitted tasks doesn't have same session name -* :ghissue:`1836`: preserve header for resubmitted tasks -* :ghissue:`1776`: fix magic menu in qtconsole -* :ghissue:`1828`: change default extension to .ipy for %save -r -* :ghissue:`1800`: Reintroduce recall -* :ghissue:`1671`: __future__ environments -* :ghissue:`1830`: lsmagic lists magics in alphabetical order -* :ghissue:`1835`: Use Python import in ipython profile config -* :ghissue:`1773`: Update SymPy profile: SymPy's latex() can now print set and frozenset -* :ghissue:`1761`: Edited documentation to use IPYTHONDIR in place of ~/.ipython -* :ghissue:`1772`: notebook autocomplete fail when typing number -* :ghissue:`1822`: aesthetics pass on AsyncResult.display_outputs -* :ghissue:`1460`: Redirect http to https for notebook -* :ghissue:`1287`: Refactor the notebook tab completion/tooltip -* :ghissue:`1596`: In rename dialog, should submit -* :ghissue:`1821`: ENTER submits the rename notebook dialog. -* :ghissue:`1750`: Let the user disable random port selection -* :ghissue:`1820`: NotebookApp: Make the number of ports to retry user configurable. -* :ghissue:`1816`: Always use filename as the notebook name. -* :ghissue:`1775`: assert_in not present on Python 2.6 -* :ghissue:`1813`: Add assert_in method to nose for Python 2.6 -* :ghissue:`1498`: Add tooltip keyboard shortcuts -* :ghissue:`1711`: New Tooltip, New Completer and JS Refactor -* :ghissue:`1798`: a few simple fixes for docs/parallel -* :ghissue:`1818`: possible bug with latex / markdown -* :ghissue:`1647`: Aborted parallel tasks can't be resubmitted -* :ghissue:`1817`: Change behavior of ipython notebook --port=... -* :ghissue:`1738`: IPython.embed_kernel issues -* :ghissue:`1610`: Basic bold and italic in HTML output cells -* :ghissue:`1576`: Start and stop kernels from the notebook dashboard -* :ghissue:`1515`: impossible to shutdown notebook kernels -* :ghissue:`1812`: Ensure AsyncResult.display_outputs doesn't display empty streams -* :ghissue:`1811`: warn on nonexistent exclusions in iptest -* :ghissue:`1809`: test suite error in IPython.zmq on windows -* :ghissue:`1810`: fix for #1809, failing tests in IPython.zmq -* :ghissue:`1808`: Reposition alternate upload for firefox [need cross browser/OS/language test] -* :ghissue:`1742`: Check for custom_exceptions only once -* :ghissue:`1802`: cythonmagic tests should be skipped if Cython not available -* :ghissue:`1062`: warning message in IPython.extensions test -* :ghissue:`1807`: add missing cython exclusion in iptest -* :ghissue:`1805`: Fixed a vcvarsall.bat error on win32/Py2.7 when trying to compile with m... -* :ghissue:`1803`: MPI parallel %px bug -* :ghissue:`1804`: Fixed a vcvarsall.bat error on win32/Py2.7 when trying to compile with mingw. -* :ghissue:`1492`: Drag target very small if IPython Dashboard has no notebooks -* :ghissue:`1562`: Offer a method other than drag-n-drop to upload notebooks -* :ghissue:`1739`: Dashboard improvement (necessary merge of #1658 and #1676 + fix #1492) -* :ghissue:`1770`: Cython related magic functions -* :ghissue:`1532`: qtconsole does not accept --gui switch -* :ghissue:`1707`: Accept --gui=<...> switch in IPython qtconsole. -* :ghissue:`1797`: Fix comment which breaks Emacs syntax highlighting. -* :ghissue:`1796`: %gui magic broken -* :ghissue:`1795`: fix %gui magic -* :ghissue:`1788`: extreme truncating of return values -* :ghissue:`1793`: Raise repr limit for strings to 80 characters (from 30). -* :ghissue:`1794`: don't use XDG path on OS X -* :ghissue:`1777`: ipython crash on wrong encoding -* :ghissue:`1792`: Unicode-aware logger -* :ghissue:`1791`: update zmqshell magics -* :ghissue:`1787`: DOC: Remove regression from qt-console docs. -* :ghissue:`1785`: IPython.utils.tests.test_process.SubProcessTestCase -* :ghissue:`1758`: test_pr, fallback on http if git protocol fail, and SSL errors... -* :ghissue:`1786`: Make notebook save failures more salient -* :ghissue:`1748`: Fix some tests for Python 3.3 -* :ghissue:`1755`: test for pygments before running qt tests -* :ghissue:`1771`: Make default value of interactivity passed to run_ast_nodes configurable -* :ghissue:`1783`: part of PR #1606 (loadpy -> load) erased by magic refactoring. -* :ghissue:`1784`: restore loadpy to load -* :ghissue:`1768`: Update parallel magics -* :ghissue:`1778`: string exception in IPython/core/magic.py:232 -* :ghissue:`1779`: Tidy up error raising in magic decorators. -* :ghissue:`1769`: Allow cell mode timeit without setup code. -* :ghissue:`1716`: Fix for fake filenames in verbose traceback -* :ghissue:`1763`: [qtconsole] fix append_plain_html -> append_html -* :ghissue:`1766`: Test failure in IPython.parallel -* :ghissue:`1611`: IPEP1: Cell magics and general cleanup of the Magic system -* :ghissue:`1732`: Refactoring of the magics system and implementation of cell magics -* :ghissue:`1765`: test_pr should clearn PYTHONPATH for the subprocesses -* :ghissue:`1630`: Merge divergent Kernel implementations -* :ghissue:`1705`: [notebook] Make pager resizable, and remember size... -* :ghissue:`1606`: Share code for %pycat and %loadpy, make %pycat aware of URLs -* :ghissue:`1720`: Adding interactive inline plotting to notebooks with flot -* :ghissue:`1701`: [notebook] Open HTML links in a new window by default -* :ghissue:`1757`: Open IPython notebook hyperlinks in a new window using target=_blank -* :ghissue:`1735`: Open IPython notebook hyperlinks in a new window using target=_blank -* :ghissue:`1754`: Fix typo enconters->encounters -* :ghissue:`1753`: Clear window title when kernel is restarted -* :ghissue:`735`: Images missing from XML/SVG export (for me) -* :ghissue:`1449`: Fix for bug #735 : Images missing from XML/SVG export -* :ghissue:`1752`: Reconnect Websocket when it closes unexpectedly -* :ghissue:`1751`: Reconnect Websocket when it closes unexpectedly -* :ghissue:`1749`: Load MathJax.js using HTTPS when IPython notebook server is HTTPS -* :ghissue:`1743`: Tooltip completer js refactor -* :ghissue:`1700`: A module for sending custom user messages from the kernel. -* :ghissue:`1745`: htmlnotebook: Cursor is off -* :ghissue:`1728`: ipython crash with matplotlib during picking -* :ghissue:`1681`: add qt config option to clear_on_kernel_restart -* :ghissue:`1733`: Tooltip completer js refactor -* :ghissue:`1676`: Kernel status/shutdown from dashboard -* :ghissue:`1658`: Alternate notebook upload methods -* :ghissue:`1727`: terminate kernel after embed_kernel tests -* :ghissue:`1737`: add HistoryManager to ipapp class list -* :ghissue:`945`: Open a notebook from the command line -* :ghissue:`1686`: ENH: Open a notebook from the command line -* :ghissue:`1709`: fixes #1708, failing test in arg_split on windows -* :ghissue:`1718`: Use CRegExp trait for regular expressions. -* :ghissue:`1729`: Catch failure in repr() for %whos -* :ghissue:`1726`: use eval for command-line args instead of exec -* :ghissue:`1723`: scatter/gather fail with targets='all' -* :ghissue:`1724`: fix scatter/gather with targets='all' -* :ghissue:`1725`: add --no-ff to git pull in test_pr -* :ghissue:`1722`: unicode exception when evaluating expression with non-ascii characters -* :ghissue:`1721`: Tooltip completer js refactor -* :ghissue:`1657`: Add `wait` optional argument to `hooks.editor` -* :ghissue:`123`: Define sys.ps{1,2} -* :ghissue:`1717`: Define generic sys.ps{1,2,3}, for use by scripts. -* :ghissue:`1442`: cache-size issue in qtconsole -* :ghissue:`1691`: Finish PR #1446 -* :ghissue:`1446`: Fixing Issue #1442 -* :ghissue:`1710`: update MathJax CDN url for https -* :ghissue:`81`: Autocall fails if first function argument begins with "-" or "+ -* :ghissue:`1713`: Make autocall regexp's configurable. -* :ghissue:`211`: paste command not working -* :ghissue:`1703`: Allow TryNext to have an error message without it affecting the command chain -* :ghissue:`1714`: minor adjustments to test_pr -* :ghissue:`1509`: New tooltip for notebook -* :ghissue:`1697`: Major refactoring of the Notebook, Kernel and CodeCell JavaScript. -* :ghissue:`788`: Progress indicator in the notebook (and perhaps the Qt console) -* :ghissue:`1034`: Single process Qt console -* :ghissue:`1557`: magic function conflict while using --pylab -* :ghissue:`1476`: Pylab figure objects not properly updating -* :ghissue:`1704`: ensure all needed qt parts can be imported before settling for one -* :ghissue:`1708`: test failure in arg_split on windows -* :ghissue:`1706`: Mark test_push_numpy_nocopy as a known failure for Python 3 -* :ghissue:`1696`: notebook tooltip fail on function with number -* :ghissue:`1698`: fix tooltip on token with number -* :ghissue:`1226`: Windows GUI only (pythonw) bug for IPython on Python 3.x -* :ghissue:`1245`: pythonw py3k fixes for issue #1226 -* :ghissue:`1417`: Notebook Completer Class -* :ghissue:`1690`: [Bogus] Deliberately make a test fail -* :ghissue:`1685`: Add script to test pull request -* :ghissue:`1167`: Settle on a choice for $IPYTHONDIR -* :ghissue:`1693`: deprecate IPYTHON_DIR in favor of IPYTHONDIR -* :ghissue:`1672`: ipython-qtconsole.desktop is using a deprecated format -* :ghissue:`1695`: Avoid deprecated warnings from ipython-qtconsole.desktop. -* :ghissue:`1694`: Add quote to notebook to allow it to load -* :ghissue:`1240`: sys.path missing `''` as first entry when kernel launched without interface -* :ghissue:`1689`: Fix sys.path missing '' as first entry in `ipython kernel`. -* :ghissue:`1683`: Parallel controller failing with Pymongo 2.2 -* :ghissue:`1687`: import Binary from bson instead of pymongo -* :ghissue:`1614`: Display Image in Qtconsole -* :ghissue:`1616`: Make IPython.core.display.Image less notebook-centric -* :ghissue:`1684`: CLN: Remove redundant function definition. -* :ghissue:`1655`: Add %open magic command to open editor in non-blocking manner -* :ghissue:`1677`: middle-click paste broken in notebook -* :ghissue:`1670`: Point %pastebin to gist -* :ghissue:`1667`: Test failure in test_message_spec -* :ghissue:`1668`: Test failure in IPython.zmq.tests.test_message_spec.test_complete "'pyout' != 'status'" -* :ghissue:`1669`: handle pyout messages in test_message_spec -* :ghissue:`1295`: add binary-tree engine interconnect example -* :ghissue:`1642`: Cherry-picked commits from 0.12.1 release -* :ghissue:`1659`: Handle carriage return characters ("\r") in HTML notebook output. -* :ghissue:`1313`: Figure out MathJax 2 support -* :ghissue:`1653`: Test failure in IPython.zmq -* :ghissue:`1656`: ensure kernels are cleaned up in embed_kernel tests -* :ghissue:`1666`: pip install ipython==dev installs version 0.8 from an old svn repo -* :ghissue:`1664`: InteractiveShell.run_code: Update docstring. -* :ghissue:`1512`: `print stuff,` should avoid newline -* :ghissue:`1662`: Delay flushing softspace until after cell finishes -* :ghissue:`1643`: handle jpg/jpeg in the qtconsole -* :ghissue:`966`: dreload fails on Windows XP with IPython 0.11 "Unexpected Error" -* :ghissue:`1500`: dreload doesn't seem to exclude numpy -* :ghissue:`1520`: kernel crash when showing tooltip (?) -* :ghissue:`1652`: add patch_pyzmq() for backporting a few changes from newer pyzmq -* :ghissue:`1650`: DOC: moving files with SSH launchers -* :ghissue:`1357`: add IPython.embed_kernel() -* :ghissue:`1640`: Finish up embed_kernel -* :ghissue:`1651`: Remove bundled Itpl module -* :ghissue:`1634`: incremental improvements to SSH launchers -* :ghissue:`1649`: move examples/test_embed into examples/tests/embed -* :ghissue:`1171`: Recognise virtualenvs -* :ghissue:`1479`: test_extension failing in Windows -* :ghissue:`1633`: Fix installing extension from local file on Windows -* :ghissue:`1644`: Update copyright date to 2012 -* :ghissue:`1636`: Test_deepreload breaks pylab irunner tests -* :ghissue:`1645`: Exclude UserDict when deep reloading NumPy. -* :ghissue:`1454`: make it possible to start engine in 'disabled' mode and 'enable' later -* :ghissue:`1641`: Escape code for the current time in PromptManager -* :ghissue:`1638`: ipython console clobbers custom sys.path -* :ghissue:`1637`: Removed a ':' which shouldn't have been there -* :ghissue:`1536`: ipython 0.12 embed shell won't run startup scripts -* :ghissue:`1628`: error: QApplication already exists in TestKillRing -* :ghissue:`1631`: TST: QApplication doesn't quit early enough with PySide. -* :ghissue:`1629`: evaluate a few dangling validate_message generators -* :ghissue:`1621`: clear In[] prompt numbers on "Clear All Output" -* :ghissue:`1627`: Test the Message Spec -* :ghissue:`1470`: SyntaxError on setup.py install with Python 3 -* :ghissue:`1624`: Fixes for byte-compilation on Python 3 -* :ghissue:`1612`: pylab=inline fig.show() non-existent in notebook -* :ghissue:`1615`: Add show() method to figure objects. -* :ghissue:`1622`: deepreload fails on Python 3 -* :ghissue:`1625`: Fix deepreload on Python 3 -* :ghissue:`1626`: Failure in new `dreload` tests under Python 3.2 -* :ghissue:`1623`: IPython / matplotlib Memory error with imshow -* :ghissue:`1619`: pyin messages should have execution_count -* :ghissue:`1620`: pyin message now have execution_count -* :ghissue:`32`: dreload produces spurious traceback when numpy is involved -* :ghissue:`1457`: Update deepreload to use a rewritten knee.py. Fixes dreload(numpy). -* :ghissue:`1613`: allow map / parallel function for single-engine views -* :ghissue:`1609`: exit notebook cleanly on SIGINT, SIGTERM -* :ghissue:`1531`: Function keyword completion fails if cursor is in the middle of the complete parentheses -* :ghissue:`1607`: cleanup sqlitedb temporary db file after tests -* :ghissue:`1608`: don't rely on timedelta.total_seconds in AsyncResult -* :ghissue:`1421`: ipython32 %run -d breaks with NameError name 'execfile' is not defined -* :ghissue:`1599`: Fix for %run -d on Python 3 -* :ghissue:`1201`: %env magic fails with Python 3.2 -* :ghissue:`1602`: Fix %env magic on Python 3. -* :ghissue:`1603`: Remove python3 profile -* :ghissue:`1604`: Exclude IPython.quarantine from installation -* :ghissue:`1601`: Security file is not removed after shutdown by Ctrl+C or kill -INT -* :ghissue:`1600`: Specify encoding for io.open in notebook_reformat tests -* :ghissue:`1605`: Small fixes for Animation and Progress notebook -* :ghissue:`1452`: Bug fix for approval -* :ghissue:`13`: Improve robustness and debuggability of test suite -* :ghissue:`70`: IPython should prioritize __all__ during tab completion -* :ghissue:`1529`: __all__ feature, improvement to dir2, and tests for both -* :ghissue:`1475`: Custom namespace for %run -* :ghissue:`1564`: calling .abort on AsyncMapResult results in traceback -* :ghissue:`1548`: add sugar methods/properties to AsyncResult -* :ghissue:`1535`: Fix pretty printing dispatch -* :ghissue:`1522`: Discussion: some potential Qt console refactoring -* :ghissue:`1399`: Use LaTeX to print various built-in types with the SymPy printing extension -* :ghissue:`1597`: re-enter kernel.eventloop after catching SIGINT -* :ghissue:`1490`: rename plaintext cell -> raw cell -* :ghissue:`1487`: %notebook fails in qtconsole -* :ghissue:`1545`: trailing newline not preserved in splitline ipynb -* :ghissue:`1480`: Fix %notebook magic, etc. nbformat unicode tests and fixes -* :ghissue:`1588`: Gtk3 integration with ipython works. -* :ghissue:`1595`: Examples syntax (avoid errors installing on Python 3) -* :ghissue:`1526`: Find encoding for Python files -* :ghissue:`1594`: Fix writing git commit ID to a file on build with Python 3 -* :ghissue:`1556`: shallow-copy DictDB query results -* :ghissue:`1499`: various pyflakes issues -* :ghissue:`1502`: small changes in response to pyflakes pass -* :ghissue:`1445`: Don't build sphinx docs for sdists -* :ghissue:`1484`: unhide .git_commit_info.ini -* :ghissue:`1538`: store git commit hash in utils._sysinfo instead of hidden data file -* :ghissue:`1546`: attempt to suppress exceptions in channel threads at shutdown -* :ghissue:`1524`: unhide git_commit_info.ini -* :ghissue:`1559`: update tools/github_stats.py to use GitHub API v3 -* :ghissue:`1563`: clear_output improvements -* :ghissue:`1558`: Ipython testing documentation still mentions twisted and trial -* :ghissue:`1560`: remove obsolete discussion of Twisted/trial from testing docs -* :ghissue:`1561`: Qtconsole - nonstandard \a and \b -* :ghissue:`1569`: BUG: qtconsole -- non-standard handling of \a and \b. [Fixes #1561] -* :ghissue:`1574`: BUG: Ctrl+C crashes wx pylab kernel in qtconsole -* :ghissue:`1573`: BUG: Ctrl+C crashes wx pylab kernel in qtconsole. -* :ghissue:`1590`: 'IPython3 qtconsole' doesn't work in Windows 7 -* :ghissue:`602`: User test the html notebook -* :ghissue:`613`: Implement Namespace panel section -* :ghissue:`879`: How to handle Javascript output in the notebook -* :ghissue:`1255`: figure.show() raises an error with the inline backend -* :ghissue:`1467`: Document or bundle a git-integrated facility for stripping VCS-unfriendly binary data -* :ghissue:`1237`: Kernel status and logout button overlap -* :ghissue:`1319`: Running a cell with ctrl+Enter selects text in cell -* :ghissue:`1571`: module member autocomplete should respect __all__ -* :ghissue:`1566`: ipython3 doesn't run in Win7 with Python 3.2 -* :ghissue:`1568`: fix PR #1567 -* :ghissue:`1567`: Fix: openssh_tunnel did not parse port in `server` -* :ghissue:`1565`: fix AsyncResult.abort -* :ghissue:`1550`: Crash when starting notebook in a non-ascii path -* :ghissue:`1552`: use os.getcwdu in NotebookManager -* :ghissue:`1554`: wrong behavior of the all function on iterators -* :ghissue:`1541`: display_pub flushes stdout/err -* :ghissue:`1539`: Asynchrous issue when using clear_display and print x,y,z -* :ghissue:`1544`: make MultiKernelManager.kernel_manager_class configurable -* :ghissue:`1494`: Untrusted Secure Websocket broken on latest chrome dev -* :ghissue:`1521`: only install ipython-qtconsole gui script on Windows -* :ghissue:`1528`: Tab completion optionally respects __all__ (+ dir2() cleanup) -* :ghissue:`1527`: Making a progress bar work in IPython Notebook -* :ghissue:`1497`: __all__ functionality added to dir2(obj) -* :ghissue:`1518`: Pretty printing exceptions is broken -* :ghissue:`811`: Fixes for ipython unhandeled OSError exception on failure of os.getcwdu() -* :ghissue:`1517`: Fix indentation bug in IPython/lib/pretty.py -* :ghissue:`1519`: BUG: Include the name of the exception type in its pretty format. -* :ghissue:`1525`: A hack for auto-complete numpy recarray -* :ghissue:`1489`: Fix zero-copy push -* :ghissue:`1401`: numpy arrays cannot be used with View.apply() in Python 3 -* :ghissue:`1477`: fix dangling `buffer` in IPython.parallel.util -* :ghissue:`1514`: DOC: Fix references to IPython.lib.pretty instead of the old location -* :ghissue:`1511`: Version comparison error ( '2.1.11' < '2.1.4' ==> True) -* :ghissue:`1506`: "Fixing" the Notebook scroll to help in visually comparing outputs -* :ghissue:`1481`: BUG: Improve placement of CallTipWidget -* :ghissue:`1241`: When our debugger class is used standalone `_oh` key errors are thrown -* :ghissue:`676`: IPython.embed() from ipython crashes twice on exit -* :ghissue:`1496`: BUG: LBYL when clearing the output history on shutdown. -* :ghissue:`1507`: python3 notebook: TypeError: unorderable types -* :ghissue:`1508`: fix sorting profiles in clustermanager -* :ghissue:`1495`: BUG: Fix pretty-printing for overzealous objects -* :ghissue:`1505`: SQLite objects created in a thread can only be used in that same thread -* :ghissue:`1482`: %history documentation out of date? -* :ghissue:`1501`: dreload doesn't seem to exclude numpy -* :ghissue:`1472`: more general fix for #662 -* :ghissue:`1486`: save state of qtconsole -* :ghissue:`1485`: add history search to qtconsole -* :ghissue:`1483`: updated magic_history docstring -* :ghissue:`1383`: First version of cluster web service. -* :ghissue:`482`: test_run.test_tclass fails on Windows -* :ghissue:`1398`: fix %tb after SyntaxError -* :ghissue:`1478`: key function or lambda in sorted function doesn't find global variables -* :ghissue:`1415`: handle exit/quit/exit()/quit() variants in zmqconsole -* :ghissue:`1440`: Fix for failing testsuite when using --with-xml-coverage on windows. -* :ghissue:`1419`: Add %install_ext magic function. -* :ghissue:`1424`: Win32 shell interactivity -* :ghissue:`1434`: Controller should schedule tasks of multiple clients at the same time -* :ghissue:`1268`: notebook %reset magic fails with StdinNotImplementedError -* :ghissue:`1438`: from cherrypy import expose fails when running script form parent directory -* :ghissue:`1468`: Simplify structure of a Job in the TaskScheduler -* :ghissue:`875`: never build unicode error messages -* :ghissue:`1107`: Tab autocompletion can suggest invalid syntax -* :ghissue:`1447`: 1107 - Tab autocompletion can suggest invalid syntax -* :ghissue:`1469`: Fix typo in comment (insert space) -* :ghissue:`1463`: Fix completion when importing modules in the cwd. -* :ghissue:`1437`: unfriendly error handling with pythonw and ipython-qtconsole -* :ghissue:`1466`: Fix for issue #1437, unfriendly windows qtconsole error handling -* :ghissue:`1432`: Fix ipython directive -* :ghissue:`1465`: allow `ipython help subcommand` syntax -* :ghissue:`1394`: Wishlist: Remove hard dependency on ctypes -* :ghissue:`1416`: Conditional import of ctypes in inputhook -* :ghissue:`1462`: expedite parallel tests -* :ghissue:`1418`: Strict mode in javascript -* :ghissue:`1410`: Add javascript library and css stylesheet loading to JS class. -* :ghissue:`1427`: #922 again -* :ghissue:`1448`: Fix for #875 Never build unicode error messages -* :ghissue:`1458`: use eval to uncan References -* :ghissue:`1455`: Python3 install fails -* :ghissue:`1450`: load mathjax from CDN via https -* :ghissue:`1182`: Qtconsole, multiwindow -* :ghissue:`1439`: Notebook not storing heading celltype information -* :ghissue:`1451`: include heading level in JSON -* :ghissue:`1444`: Fix pyhton -> python typos -* :ghissue:`1412`: Input parsing issue with %prun -* :ghissue:`1414`: ignore errors in shell.var_expand -* :ghissue:`1441`: (1) Enable IPython.notebook.kernel.execute to publish display_* even it is not called with a code cell and (2) remove empty html element when execute "display_*" -* :ghissue:`1431`: Beginner Error: ipython qtconsole -* :ghissue:`1436`: "ipython-qtconsole --gui qt" hangs on 64-bit win7 -* :ghissue:`1433`: websocket connection fails on Chrome -* :ghissue:`1430`: Fix for tornado check for tornado < 1.1.0 -* :ghissue:`1408`: test_get_home_dir_3 failed on Mac OS X -* :ghissue:`1413`: get_home_dir expands symlinks, adjust test accordingly -* :ghissue:`1420`: fixes #922 -* :ghissue:`823`: KnownFailure tests appearing as errors -* :ghissue:`1385`: updated and prettified magic doc strings -* :ghissue:`1406`: Browser selection -* :ghissue:`1411`: ipcluster starts 8 engines "successfully" but Client only finds two -* :ghissue:`1375`: %history -g -f file encoding issue -* :ghissue:`1377`: Saving non-ascii history -* :ghissue:`797`: Source introspection needs to be smarter in python 3.2 -* :ghissue:`846`: Autoreload extension doesn't work with Python 3.2 -* :ghissue:`1360`: IPython notebook not starting on winXP -* :ghissue:`1407`: Qtconsole segfaults on OSX when displaying some pop-up function tooltips -* :ghissue:`1402`: fix symlinked /home issue for FreeBSD -* :ghissue:`1403`: pyreadline cyclic dependency with pdb++/pdbpp module -* :ghissue:`1405`: Only monkeypatch xunit when the tests are run using it. -* :ghissue:`1404`: Feature Request: List/Dictionary tab completion -* :ghissue:`1395`: Xunit & KnownFailure -* :ghissue:`1396`: Fix for %tb magic. -* :ghissue:`1397`: Stay or leave message not working, Safari session lost. -* :ghissue:`1389`: pylab=inline inoperant through ssh tunnelling? -* :ghissue:`1386`: Jsd3 -* :ghissue:`1388`: Add simple support for running inside a virtualenv -* :ghissue:`826`: Add support for creation of parallel task when no engine is running -* :ghissue:`1391`: Improve Hub/Scheduler when no engines are registered -* :ghissue:`1369`: load header with engine id when engine dies in TaskScheduler -* :ghissue:`1345`: notebook can't save unicode as script -* :ghissue:`1353`: Save notebook as script using unicode file handle. -* :ghissue:`1352`: Add '-m mod : run library module as a script' option. -* :ghissue:`1363`: Fix some minor color/style config issues in the qtconsole -* :ghissue:`1371`: Adds a quiet keyword to sync_imports -* :ghissue:`1390`: Blank screen for notebooks on Safari -* :ghissue:`1387`: Fixing Cell menu to update cell type select box. -* :ghissue:`645`: Standalone WX GUI support is broken -* :ghissue:`1296`: Wx gui example: fixes the broken example for `%gui wx`. -* :ghissue:`1254`: typo in notebooklist.js breaks links -* :ghissue:`781`: Users should be able to clone a notebook -* :ghissue:`1372`: ipcontroller cleans up connection files unless reuse=True -* :ghissue:`1374`: remove calls to meaningless ZMQStream.on_err -* :ghissue:`1382`: Update RO for Notebook -* :ghissue:`1370`: allow draft76 websockets (Safari) -* :ghissue:`1368`: Ensure handler patterns are str, not unicode -* :ghissue:`1379`: Sage link on website homepage broken -* :ghissue:`1376`: FWIW does not work with Chrome 16.0.912.77 Ubuntu 10.10 -* :ghissue:`1358`: Cannot install ipython on Windows 7 64-bit -* :ghissue:`1367`: Ctrl - m t does not toggle output in chrome -* :ghissue:`1359`: [sympyprinting] MathJax can't render \root{m}{n} -* :ghissue:`1337`: Tab in the notebook after `(` should not indent, only give a tooltip -* :ghissue:`1339`: Notebook printing broken -* :ghissue:`1344`: Ctrl + M + L does not toggle line numbering in htmlnotebook -* :ghissue:`1348`: Ctrl + M + M does not switch to markdown cell -* :ghissue:`1361`: Notebook bug fix branch -* :ghissue:`1364`: avoid jsonlib returning Decimal -* :ghissue:`1362`: Don't log complete contents of history replies, even in debug -* :ghissue:`888`: ReST support in notebooks -* :ghissue:`1205`: notebook stores HTML escaped text in the file -* :ghissue:`1351`: add IPython.embed_kernel() -* :ghissue:`1243`: magic commands without % are not completed properly in htmlnotebook -* :ghissue:`1347`: fix weird magic completion in notebook -* :ghissue:`1355`: notebook.html extends layout.html now -* :ghissue:`1354`: min and max in the notebook -* :ghissue:`1346`: fixups for alternate URL prefix stuff -* :ghissue:`1336`: crack at making notebook.html use the layout.html template -* :ghissue:`1331`: RST and heading cells -* :ghissue:`1350`: Add '-m mod : run library module as a script' option -* :ghissue:`1247`: fixes a bug causing extra newlines after comments. -* :ghissue:`1329`: add base_url to notebook configuration options -* :ghissue:`1332`: notebook - allow prefixes in URL path. -* :ghissue:`1317`: Very slow traceback construction from Cython extension -* :ghissue:`1341`: Don't attempt to tokenize binary files for tracebacks -* :ghissue:`1300`: Cell Input collapse -* :ghissue:`1334`: added key handler for control-s to notebook, seems to work pretty well -* :ghissue:`1338`: Fix see also in docstrings so API docs build -* :ghissue:`1335`: Notebook toolbar UI -* :ghissue:`1299`: made notebook.html extend layout.html -* :ghissue:`1318`: make Ctrl-D in qtconsole act same as in terminal (ready to merge) -* :ghissue:`873`: ReST support in notebook frontend -* :ghissue:`1139`: Notebook webkit notification -* :ghissue:`1314`: Insertcell -* :ghissue:`1328`: Coverage -* :ghissue:`1206`: don't preserve fixConsole output in json -* :ghissue:`1330`: Add linewrapping to text cells (new feature in CodeMirror). -* :ghissue:`1309`: Inoculate clearcmd extension into %reset functionality -* :ghissue:`1327`: Updatecm2 -* :ghissue:`1326`: Removing Ace edit capability. -* :ghissue:`1325`: forgotten selected_cell -> get_selected_cell -* :ghissue:`1316`: Pass subprocess test runners a suitable location for xunit output -* :ghissue:`1315`: Collect results from subprocess runners and spit out Xunit XML output. -* :ghissue:`1233`: Update CodeMirror to the latest version -* :ghissue:`1234`: Refactor how the notebook focuses cells -* :ghissue:`1235`: After upgrading CodeMirror check the status of some bugs -* :ghissue:`1236`: Review how select is called when notebook cells are inserted -* :ghissue:`1303`: Updatecm -* :ghissue:`1311`: Fixing CM related indentation problems. -* :ghissue:`1304`: controller/server load can disrupt heartbeat -* :ghissue:`1312`: minor heartbeat tweaks -* :ghissue:`1302`: Input parsing with %prun clobbers escapes -* :ghissue:`1306`: Fix %prun input parsing for escaped characters (closes #1302) -* :ghissue:`1251`: IPython-0.12 can't import map module on Python 3.1 -* :ghissue:`1202`: Pyreadline install exclusion for 64 bit windows no longer required, version dependency not correctly specified. -* :ghissue:`1301`: New "Fix for issue #1202" based on current master. -* :ghissue:`1242`: changed key map name to match changes to python mode -* :ghissue:`1203`: Fix for issue #1202 -* :ghissue:`1289`: Make autoreload extension work on Python 3. -* :ghissue:`1263`: Different 'C-x' for shortcut, 'C-m c' not toCodeCell anymore -* :ghissue:`1259`: Replace "from (.|..) import" with absolute imports. -* :ghissue:`1278`: took a crack at making notebook.html extend layout.html -* :ghissue:`1210`: Add 'quiet' option to suppress screen output during %prun calls, edited dochelp -* :ghissue:`1288`: Don't ask for confirmation when stdin isn't available -* :ghissue:`1290`: Cell-level cut & paste overwrites multiple cells -* :ghissue:`1291`: Minor, but important fixes to cut/copy/paste. -* :ghissue:`1293`: TaskScheduler.hwm default value -* :ghissue:`1294`: TaskScheduler.hwm default to 1 instead of 0 -* :ghissue:`1281`: in Hub: registration_timeout must be an integer, but heartmonitor.period is CFloat -* :ghissue:`1283`: HeartMonitor.period should be an Integer -* :ghissue:`1162`: Allow merge/split adjacent cells in notebook -* :ghissue:`1264`: Aceify -* :ghissue:`1261`: Mergesplit -* :ghissue:`1269`: Another strange input handling error -* :ghissue:`1284`: a fix for GH 1269 -* :ghissue:`1232`: Dead kernel loop -* :ghissue:`1279`: ImportError: cannot import name S1 (from logging) -* :ghissue:`1276`: notebook menu item to send a KeyboardInterrupt to the kernel -* :ghissue:`1213`: BUG: Minor typo in history_console_widget.py -* :ghissue:`1248`: IPython notebook doesn't work with lastest version of tornado -* :ghissue:`1267`: add NoDB for non-recording Hub -* :ghissue:`1222`: allow Reference as callable in map/apply -* :ghissue:`1257`: use self.kernel_manager_class in qtconsoleapp -* :ghissue:`1220`: Open a new notebook while connecting to an existing kernel (opened by qtconsole or terminal or standalone) -* :ghissue:`1253`: set auto_create flag for notebook apps -* :ghissue:`1260`: heartbeat failure on long gil-holding operation -* :ghissue:`1262`: Heartbeat no longer shares the app's Context -* :ghissue:`1225`: SyntaxError display broken in Python 3 -* :ghissue:`1229`: Fix display of SyntaxError in Python 3 -* :ghissue:`1256`: Dewijmoize -* :ghissue:`1246`: Skip tests that require X, when importing pylab results in RuntimeError. -* :ghissue:`1250`: Wijmoize -* :ghissue:`1244`: can not imput chinese word "造" , exit right now -* :ghissue:`1194`: Adding Opera 11 as a compatible browser for ipython notebook -* :ghissue:`1198`: Kernel Has Died error in Notebook -* :ghissue:`1211`: serve local files in notebook-dir -* :ghissue:`1224`: edit text cells on double-click instead of single-click -* :ghissue:`1187`: misc notebook: connection file cleanup, first heartbeat, startup flush -* :ghissue:`1207`: fix loadpy duplicating newlines -* :ghissue:`1060`: Always save the .py file to disk next to the .ipynb -* :ghissue:`1066`: execute cell in place should preserve the current insertion-point in the notebook -* :ghissue:`1141`: "In" numbers are not invalidated when restarting kernel -* :ghissue:`1231`: pip on OSX tries to install files in /System directory. -* :ghissue:`1129`: Unified setup.py -* :ghissue:`1199`: Reduce IPython.external.* -* :ghissue:`1219`: Make all the static files path absolute. -* :ghissue:`1218`: Added -q option to %prun for suppression of the output, along with editing the dochelp string. -* :ghissue:`1217`: Added -q option to %prun for suppression of the output, along with editing the dochelp string -* :ghissue:`1216`: Pdb tab completion does not work in QtConsole -* :ghissue:`1197`: Interactive shell trying to: from ... import history -* :ghissue:`1175`: core.completer: Clean up excessive and unused code. -* :ghissue:`1208`: should dv.sync_import print failed imports ? -* :ghissue:`1186`: payloadpage.py not used by qtconsole -* :ghissue:`1204`: double newline from %loadpy in python notebook (at least on mac) -* :ghissue:`1192`: Invalid JSON data -* :ghissue:`1196`: docs: looks like a file path might have been accidentally pasted in the middle of a word -* :ghissue:`1189`: Right justify of 'in' prompt in variable prompt size configurations -* :ghissue:`1185`: ipython console not work proper with stdout... -* :ghissue:`1191`: profile/startup files not executed with "notebook" -* :ghissue:`1190`: Fix link to Chris Fonnesbeck blog post about 0.11 highlights. -* :ghissue:`1174`: Remove %install_default_config and %install_profiles diff --git a/docs/source/whatsnew/github-stats-1.0.rst b/docs/source/whatsnew/github-stats-1.0.rst deleted file mode 100644 index 03622ece1d6..00000000000 --- a/docs/source/whatsnew/github-stats-1.0.rst +++ /dev/null @@ -1,1883 +0,0 @@ -.. _issues_list_100: - -Issues closed in the 1.0 development cycle -========================================== - - -Issues closed in 1.2 --------------------- - -GitHub stats for 2013/09/09 - 2014/02/21 - -These lists are automatically generated, and may be incomplete or contain duplicates. - -The following 13 authors contributed 84 commits. - -* Benjamin Ragan-Kelley -* Daryl Herzmann -* Doug Blank -* Fernando Perez -* James Porter -* Juergen Hasch -* Julian Taylor -* Kyle Kelley -* Lawrence Fu -* Matthias Bussonnier -* Paul Ivanov -* Pascal Schetelat -* Puneeth Chaganti -* Takeshi Kanmae -* Thomas Kluyver - -We closed a total of 55 issues, 38 pull requests and 17 regular issues; -this is the full list (generated with the script :file:`tools/github_stats.py`): - -Pull Requests (38): - -1.2.1: - -* :ghpull:`4372`: Don't assume that SyntaxTB is always called with a SyntaxError -* :ghpull:`5166`: remove mktemp usage -* :ghpull:`5163`: Simplify implementation of TemporaryWorkingDirectory. -* :ghpull:`5105`: add index to format to support py2.6 - -1.2.0: - -* :ghpull:`4972`: Work around problem in doctest discovery in Python 3.4 with PyQt -* :ghpull:`4934`: `ipython profile create` respects `--ipython-dir` -* :ghpull:`4845`: Add Origin Checking. -* :ghpull:`4928`: use importlib.machinery when available -* :ghpull:`4849`: Various unicode fixes (mostly on Windows) -* :ghpull:`4880`: set profile name from profile_dir -* :ghpull:`4908`: detect builtin docstrings in oinspect -* :ghpull:`4909`: sort dictionary keys before comparison, ordering is not guaranteed -* :ghpull:`4903`: use https for all embeds -* :ghpull:`4868`: Static path fixes -* :ghpull:`4820`: fix regex for cleaning old logs with ipcluster -* :ghpull:`4840`: Error in Session.send_raw() -* :ghpull:`4762`: whitelist alphanumeric characters for cookie_name -* :ghpull:`4748`: fix race condition in profiledir creation. -* :ghpull:`4720`: never use ssh multiplexer in tunnels -* :ghpull:`4738`: don't inject help into user_ns -* :ghpull:`4722`: allow purging local results as long as they are not outstanding -* :ghpull:`4668`: Make non-ASCII docstring unicode -* :ghpull:`4639`: Minor import fix to get qtconsole with --pylab=qt working -* :ghpull:`4453`: Play nice with App Nap -* :ghpull:`4609`: Fix bytes regex for Python 3. -* :ghpull:`4488`: fix typo in message spec doc -* :ghpull:`4346`: getpass() on Windows & Python 2 needs bytes prompt -* :ghpull:`4230`: Switch correctly to the user's default matplotlib backend after inline. -* :ghpull:`4214`: engine ID metadata should be unicode, not bytes -* :ghpull:`4232`: no highlight if no language specified -* :ghpull:`4218`: Fix display of SyntaxError when .py file is modified -* :ghpull:`4217`: avoid importing numpy at the module level -* :ghpull:`4213`: fixed dead link in examples/notebooks readme to Part 3 -* :ghpull:`4183`: ESC should be handled by CM if tooltip is not on -* :ghpull:`4193`: Update for #3549: Append Firefox overflow-x fix -* :ghpull:`4205`: use TextIOWrapper when communicating with pandoc subprocess -* :ghpull:`4204`: remove some extraneous print statements from IPython.parallel -* :ghpull:`4201`: HeadingCells cannot be split or merged - -1.2.1: - -* :ghissue:`5101`: IPython 1.2.0: notebook fail with "500 Internal Server Error" - -1.2.0: - -* :ghissue:`4892`: IPython.qt test failure with python3.4 -* :ghissue:`4810`: ipcluster bug in clean_logs flag -* :ghissue:`4765`: missing build script for highlight.js -* :ghissue:`4761`: ipv6 address triggers cookie exception -* :ghissue:`4721`: purge_results with jobid crashing - looking for insight -* :ghissue:`4602`: "ipcluster stop" fails after "ipcluster start --daemonize" using python3.3 -* :ghissue:`3386`: Magic %paste not working in Python 3.3.2. TypeError: Type str doesn't support the buffer API -* :ghissue:`4485`: Incorrect info in "Messaging in IPython" documentation. -* :ghissue:`4351`: /parallel/apps/launcher.py error -* :ghissue:`4334`: NotebookApp.webapp_settings static_url_prefix causes crash -* :ghissue:`4039`: Celltoolbar example issue -* :ghissue:`4256`: IPython no longer handles unicode file names -* :ghissue:`4122`: Nbconvert [windows]: Inconsistent line endings in markdown cells exported to latex -* :ghissue:`3819`: nbconvert add extra blank line to code block on Windows. -* :ghissue:`4203`: remove spurious print statement from parallel annoted functions -* :ghissue:`4200`: Notebook: merging a heading cell and markdown cell cannot be undone - - -Issues closed in 1.1 --------------------- - -GitHub stats for 2013/08/08 - 2013/09/09 (since 1.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -The following 25 authors contributed 337 commits. - -* Benjamin Ragan-Kelley -* Bing Xia -* Bradley M. Froehle -* Brian E. Granger -* Damián Avila -* dhirschfeld -* Dražen Lučanin -* gmbecker -* Jake Vanderplas -* Jason Grout -* Jonathan Frederic -* Kevin Burke -* Kyle Kelley -* Matt Henderson -* Matthew Brett -* Matthias Bussonnier -* Pankaj Pandey -* Paul Ivanov -* rossant -* Samuel Ainsworth -* Stephan Rave -* stonebig -* Thomas Kluyver -* Yaroslav Halchenko -* Zachary Sailer - - -We closed a total of 76 issues, 58 pull requests and 18 regular issues; -this is the full list (generated with the script :file:`tools/github_stats.py`): - -Pull Requests (58): - -* :ghpull:`4188`: Allow user_ns trait to be None -* :ghpull:`4189`: always fire LOCAL_IPS.extend(PUBLIC_IPS) -* :ghpull:`4174`: various issues in markdown and rst templates -* :ghpull:`4178`: add missing data_javascript -* :ghpull:`4181`: nbconvert: Fix, sphinx template not removing new lines from headers -* :ghpull:`4043`: don't 'restore_bytes' in from_JSON -* :ghpull:`4163`: Fix for incorrect default encoding on Windows. -* :ghpull:`4136`: catch javascript errors in any output -* :ghpull:`4171`: add nbconvert config file when creating profiles -* :ghpull:`4125`: Basic exercise of `ipython [subcommand] -h` and help-all -* :ghpull:`4085`: nbconvert: Fix sphinx preprocessor date format string for Windows -* :ghpull:`4159`: don't split `.cell` and `div.cell` CSS -* :ghpull:`4158`: generate choices for `--gui` configurable from real mapping -* :ghpull:`4065`: do not include specific css in embedable one -* :ghpull:`4092`: nbconvert: Fix for unicode html headers, Windows + Python 2.x -* :ghpull:`4074`: close Client sockets if connection fails -* :ghpull:`4064`: Store default codemirror mode in only 1 place -* :ghpull:`4104`: Add way to install MathJax to a particular profile -* :ghpull:`4144`: help_end transformer shouldn't pick up ? in multiline string -* :ghpull:`4143`: update example custom.js -* :ghpull:`4142`: DOC: unwrap openssl line in public_server doc -* :ghpull:`4141`: add files with a separate `add` call in backport_pr -* :ghpull:`4137`: Restore autorestore option for storemagic -* :ghpull:`4098`: pass profile-dir instead of profile name to Kernel -* :ghpull:`4120`: support `input` in Python 2 kernels -* :ghpull:`4088`: nbconvert: Fix coalescestreams line with incorrect nesting causing strange behavior -* :ghpull:`4060`: only strip continuation prompts if regular prompts seen first -* :ghpull:`4132`: Fixed name error bug in function safe_unicode in module py3compat. -* :ghpull:`4121`: move test_kernel from IPython.zmq to IPython.kernel -* :ghpull:`4118`: ZMQ heartbeat channel: catch EINTR exceptions and continue. -* :ghpull:`4054`: use unicode for HTML export -* :ghpull:`4106`: fix a couple of default block values -* :ghpull:`4115`: Update docs on declaring a magic function -* :ghpull:`4101`: restore accidentally removed EngineError -* :ghpull:`4096`: minor docs changes -* :ghpull:`4056`: respect `pylab_import_all` when `--pylab` specified at the command-line -* :ghpull:`4091`: Make Qt console banner configurable -* :ghpull:`4086`: fix missing errno import -* :ghpull:`4030`: exclude `.git` in MANIFEST.in -* :ghpull:`4047`: Use istype() when checking if canned object is a dict -* :ghpull:`4031`: don't close_fds on Windows -* :ghpull:`4029`: bson.Binary moved -* :ghpull:`4035`: Fixed custom jinja2 templates being ignored when setting template_path -* :ghpull:`4026`: small doc fix in nbconvert -* :ghpull:`4016`: Fix IPython.start_* functions -* :ghpull:`4021`: Fix parallel.client.View map() on numpy arrays -* :ghpull:`4022`: DOC: fix links to matplotlib, notebook docs -* :ghpull:`4018`: Fix warning when running IPython.kernel tests -* :ghpull:`4019`: Test skipping without unicode paths -* :ghpull:`4008`: Transform code before %prun/%%prun runs -* :ghpull:`4014`: Fix typo in ipapp -* :ghpull:`3987`: get files list in backport_pr -* :ghpull:`3974`: nbconvert: Fix app tests on Window7 w/ Python 3.3 -* :ghpull:`3978`: fix `--existing` with non-localhost IP -* :ghpull:`3939`: minor checkpoint cleanup -* :ghpull:`3981`: BF: fix nbconvert rst input prompt spacing -* :ghpull:`3960`: Don't make sphinx a dependency for importing nbconvert -* :ghpull:`3973`: logging.Formatter is not new-style in 2.6 - -Issues (18): - -* :ghissue:`4024`: nbconvert markdown issues -* :ghissue:`4095`: Catch js error in append html in stream/pyerr -* :ghissue:`4156`: Specifying --gui=tk at the command line -* :ghissue:`3818`: nbconvert can't handle Heading with Chinese characters on Japanese Windows OS. -* :ghissue:`4134`: multi-line parser fails on ''' in comment, qtconsole and notebook. -* :ghissue:`3998`: sample custom.js needs to be updated -* :ghissue:`4078`: StoreMagic.autorestore not working in 1.0.0 -* :ghissue:`3990`: Buitlin `input` doesn't work over zmq -* :ghissue:`4015`: nbconvert fails to convert all the content of a notebook -* :ghissue:`4059`: Issues with Ellipsis literal in Python 3 -* :ghissue:`4103`: Wrong default argument of DirectView.clear -* :ghissue:`4100`: parallel.client.client references undefined error.EngineError -* :ghissue:`4005`: IPython.start_kernel doesn't work. -* :ghissue:`4020`: IPython parallel map fails on numpy arrays -* :ghissue:`3945`: nbconvert: commandline tests fail Win7x64 Py3.3 -* :ghissue:`3977`: unable to complete remote connections for two-process -* :ghissue:`3980`: nbconvert rst output lacks needed blank lines -* :ghissue:`3968`: TypeError: super() argument 1 must be type, not classobj (Python 2.6.6) - -Issues closed in 1.0 --------------------- - -GitHub stats for 2012/06/30 - 2013/08/08 (since 0.13) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -The following 155 authors contributed 4258 commits. - -* Aaron Meurer -* Adam Davis -* Ahmet Bakan -* Alberto Valverde -* Allen Riddell -* Anders Hovmöller -* Andrea Bedini -* Andrew Spiers -* Andrew Vandever -* Anthony Scopatz -* Anton Akhmerov -* Anton I. Sipos -* Antony Lee -* Aron Ahmadia -* Benedikt Sauer -* Benjamin Jones -* Benjamin Ragan-Kelley -* Benjie Chen -* Boris de Laage -* Brad Reisfeld -* Bradley M. Froehle -* Brian E. Granger -* Cameron Bates -* Cavendish McKay -* chapmanb -* Chris Beaumont -* Chris Laumann -* Christoph Gohlke -* codebraker -* codespaced -* Corran Webster -* DamianHeard -* Damián Avila -* Dan Kilman -* Dan McDougall -* Danny Staple -* David Hirschfeld -* David P. Sanders -* David Warde-Farley -* David Wolever -* David Wyde -* debjan -* Diane Trout -* dkua -* Dominik Dabrowski -* Donald Curtis -* Dražen Lučanin -* drevicko -* Eric O. LEBIGOT -* Erik M. Bray -* Erik Tollerud -* Eugene Van den Bulke -* Evan Patterson -* Fernando Perez -* Francesco Montesano -* Frank Murphy -* Greg Caporaso -* Guy Haskin Fernald -* guziy -* Hans Meine -* Harry Moreno -* henryiii -* Ivan Djokic -* Jack Feser -* Jake Vanderplas -* jakobgager -* James Booth -* Jan Schulz -* Jason Grout -* Jeff Knisley -* Jens Hedegaard Nielsen -* jeremiahbuddha -* Jerry Fowler -* Jessica B. Hamrick -* Jez Ng -* John Zwinck -* Jonathan Frederic -* Jonathan Taylor -* Joon Ro -* Joseph Lansdowne -* Juergen Hasch -* Julian Taylor -* Jussi Sainio -* Jörgen Stenarson -* kevin -* klonuo -* Konrad Hinsen -* Kyle Kelley -* Lars Solberg -* Lessandro Mariano -* Mark Sienkiewicz at STScI -* Martijn Vermaat -* Martin Spacek -* Matthias Bussonnier -* Maxim Grechkin -* Maximilian Albert -* MercuryRising -* Michael Droettboom -* Michael Shuffett -* Michał Górny -* Mikhail Korobov -* mr.Shu -* Nathan Goldbaum -* ocefpaf -* Ohad Ravid -* Olivier Grisel -* Olivier Verdier -* Owen Healy -* Pankaj Pandey -* Paul Ivanov -* Pawel Jasinski -* Pietro Berkes -* Piti Ongmongkolkul -* Puneeth Chaganti -* Rich Wareham -* Richard Everson -* Rick Lupton -* Rob Young -* Robert Kern -* Robert Marchman -* Robert McGibbon -* Rui Pereira -* Rustam Safin -* Ryan May -* s8weber -* Samuel Ainsworth -* Sean Vig -* Siyu Zhang -* Skylar Saveland -* slojo404 -* smithj1 -* Stefan Karpinski -* Stefan van der Walt -* Steven Silvester -* Takafumi Arakaki -* Takeshi Kanmae -* tcmulcahy -* teegaar -* Thomas Kluyver -* Thomas Robitaille -* Thomas Spura -* Thomas Weißschuh -* Timothy O'Donnell -* Tom Dimiduk -* ugurthemaster -* urielshaolin -* v923z -* Valentin Haenel -* Victor Zverovich -* W. Trevor King -* y-p -* Yoav Ram -* Zbigniew Jędrzejewski-Szmek -* Zoltán Vörös - - -We closed a total of 1484 issues, 793 pull requests and 691 regular issues; -this is the full list (generated with the script -:file:`tools/github_stats.py`): - -Pull Requests (793): - -* :ghpull:`3958`: doc update -* :ghpull:`3965`: Fix ansi color code for background yellow -* :ghpull:`3964`: Fix casing of message. -* :ghpull:`3942`: Pass on install docs -* :ghpull:`3962`: exclude IPython.lib.kernel in iptest -* :ghpull:`3961`: Longpath test fix -* :ghpull:`3905`: Remove references to 0.11 and 0.12 from config/overview.rst -* :ghpull:`3951`: nbconvert: fixed latex characters not escaped properly in nbconvert -* :ghpull:`3949`: log fatal error when PDF conversion fails -* :ghpull:`3947`: nbconvert: Make writer & post-processor aliases case insensitive. -* :ghpull:`3938`: Recompile css. -* :ghpull:`3948`: sphinx and PDF tweaks -* :ghpull:`3943`: nbconvert: Serve post-processor Windows fix -* :ghpull:`3934`: nbconvert: fix logic of verbose flag in PDF post processor -* :ghpull:`3929`: swallow enter event in rename dialog -* :ghpull:`3924`: nbconvert: Backport fixes -* :ghpull:`3925`: Replace --pylab flag with --matplotlib in usage -* :ghpull:`3910`: Added explicit error message for missing configuration arguments. -* :ghpull:`3913`: grffile to support spaces in notebook names -* :ghpull:`3918`: added check_for_tornado, closes #3916 -* :ghpull:`3917`: change docs/examples refs to be just examples -* :ghpull:`3908`: what's new tweaks -* :ghpull:`3896`: two column quickhelp dialog, closes #3895 -* :ghpull:`3911`: explicitly load python mode before IPython mode -* :ghpull:`3901`: don't force . relative path, fix #3897 -* :ghpull:`3891`: fix #3889 -* :ghpull:`3892`: Fix documentation of Kernel.stop_channels -* :ghpull:`3888`: posixify paths for Windows latex -* :ghpull:`3882`: quick fix for #3881 -* :ghpull:`3877`: don't use `shell=True` in PDF export -* :ghpull:`3878`: minor template loading cleanup -* :ghpull:`3855`: nbconvert: Filter tests -* :ghpull:`3879`: finish 3870 -* :ghpull:`3870`: Fix for converting notebooks that contain unicode characters. -* :ghpull:`3876`: Update parallel_winhpc.rst -* :ghpull:`3872`: removing vim-ipython, since it has it's own repo -* :ghpull:`3871`: updating docs -* :ghpull:`3873`: remove old examples -* :ghpull:`3868`: update CodeMirror component to 3.15 -* :ghpull:`3865`: Escape filename for pdflatex in nbconvert -* :ghpull:`3861`: remove old external.js -* :ghpull:`3864`: add keyboard shortcut to docs -* :ghpull:`3834`: This PR fixes a few issues with nbconvert tests -* :ghpull:`3840`: prevent profile_dir from being undefined -* :ghpull:`3859`: Add "An Afternoon Hack" to docs -* :ghpull:`3854`: Catch errors filling readline history on startup -* :ghpull:`3857`: Delete extra auto -* :ghpull:`3845`: nbconvert: Serve from original build directory -* :ghpull:`3846`: Add basic logging to nbconvert -* :ghpull:`3850`: add missing store_history key to Notebook execute_requests -* :ghpull:`3844`: update payload source -* :ghpull:`3830`: mention metadata / display_data similarity in pyout spec -* :ghpull:`3848`: fix incorrect `empty-docstring` -* :ghpull:`3836`: Parse markdown correctly when mathjax is disabled -* :ghpull:`3849`: skip a failing test on windows -* :ghpull:`3828`: signature_scheme lives in Session -* :ghpull:`3831`: update nbconvert doc with new CLI -* :ghpull:`3822`: add output flag to nbconvert -* :ghpull:`3780`: Added serving the output directory if html-based format are selected. -* :ghpull:`3764`: Cleanup nbconvert templates -* :ghpull:`3829`: remove now-duplicate 'this is dev' note -* :ghpull:`3814`: add `ConsoleWidget.execute_on_complete_input` flag -* :ghpull:`3826`: try rtfd -* :ghpull:`3821`: add sphinx prolog -* :ghpull:`3817`: relax timeouts in terminal console and tests -* :ghpull:`3825`: fix more tests that fail when pandoc is missing -* :ghpull:`3824`: don't set target on internal markdown links -* :ghpull:`3816`: s/pylab/matplotlib in docs -* :ghpull:`3812`: Describe differences between start_ipython and embed -* :ghpull:`3805`: Print View has been removed -* :ghpull:`3820`: Make it clear that 1.0 is not released yet -* :ghpull:`3784`: nbconvert: Export flavors & PDF writer (ipy dev meeting) -* :ghpull:`3800`: semantic-versionify version number for non-releases -* :ghpull:`3802`: Documentation .txt to .rst -* :ghpull:`3765`: cleanup terminal console iopub handling -* :ghpull:`3720`: Fix for #3719 -* :ghpull:`3787`: re-raise KeyboardInterrupt in raw_input -* :ghpull:`3770`: Organizing reveal's templates. -* :ghpull:`3751`: Use link(2) when possible in nbconvert -* :ghpull:`3792`: skip tests that require pandoc -* :ghpull:`3782`: add Importing Notebooks example -* :ghpull:`3752`: nbconvert: Add cwd to sys.path -* :ghpull:`3789`: fix raw_input in qtconsole -* :ghpull:`3756`: document the wire protocol -* :ghpull:`3749`: convert IPython syntax to Python syntax in nbconvert python template -* :ghpull:`3793`: Closes #3788 -* :ghpull:`3794`: Change logo link to ipython.org -* :ghpull:`3746`: Raise a named exception when pandoc is missing -* :ghpull:`3781`: comply with the message spec in the notebook -* :ghpull:`3779`: remove bad `if logged_in` preventing new-notebook without login -* :ghpull:`3743`: remove notebook read-only view -* :ghpull:`3732`: add delay to autosave in beforeunload -* :ghpull:`3761`: Added rm_math_space to markdown cells in the basichtml.tpl to be rendered ok by mathjax after the nbconvertion. -* :ghpull:`3758`: nbconvert: Filter names cleanup -* :ghpull:`3769`: Add configurability to tabcompletion timeout -* :ghpull:`3771`: Update px pylab test to match new output of pylab -* :ghpull:`3741`: better message when notebook format is not supported -* :ghpull:`3753`: document Ctrl-C not working in ipython kernel -* :ghpull:`3766`: handle empty metadata in pyout messages more gracefully. -* :ghpull:`3736`: my attempt to fix #3735 -* :ghpull:`3759`: nbconvert: Provide a more useful error for invalid use case. -* :ghpull:`3760`: nbconvert: Allow notebook filenames without their extensions -* :ghpull:`3750`: nbconvert: Add cwd to default templates search path. -* :ghpull:`3748`: Update nbconvert docs -* :ghpull:`3734`: Nbconvert: Export extracted files into `nbname_files` subdirectory -* :ghpull:`3733`: Nicer message when pandoc is missing, closes #3730 -* :ghpull:`3722`: fix two failing test in IPython.lib -* :ghpull:`3704`: Start what's new for 1.0 -* :ghpull:`3705`: Complete rewrite of IPython Notebook documentation: docs/source/interactive/htmlnotebook.txt -* :ghpull:`3709`: Docs cleanup -* :ghpull:`3716`: raw_input fixes for kernel restarts -* :ghpull:`3683`: use `%matplotlib` in example notebooks -* :ghpull:`3686`: remove quarantine -* :ghpull:`3699`: svg2pdf unicode fix -* :ghpull:`3695`: fix SVG2PDF -* :ghpull:`3685`: fix Pager.detach -* :ghpull:`3675`: document new dependencies -* :ghpull:`3690`: Fixing some css minors in full_html and reveal. -* :ghpull:`3671`: nbconvert tests -* :ghpull:`3692`: Fix rename notebook - show error with invalid name -* :ghpull:`3409`: Prevent qtconsole frontend freeze on lots of output. -* :ghpull:`3660`: refocus active cell on dialog close -* :ghpull:`3598`: Statelessify mathjaxutils -* :ghpull:`3673`: enable comment/uncomment selection -* :ghpull:`3677`: remove special-case in get_home_dir for frozen dists -* :ghpull:`3674`: add CONTRIBUTING.md -* :ghpull:`3670`: use Popen command list for ipexec -* :ghpull:`3568`: pylab import adjustments -* :ghpull:`3559`: add create.Cell and delete.Cell js events -* :ghpull:`3606`: push cell magic to the head of the transformer line -* :ghpull:`3607`: NbConvert: Writers, No YAML, and stuff... -* :ghpull:`3665`: Pywin32 skips -* :ghpull:`3669`: set default client_class for QtKernelManager -* :ghpull:`3662`: add strip_encoding_cookie transformer -* :ghpull:`3641`: increase patience for slow kernel startup in tests -* :ghpull:`3651`: remove a bunch of unused `default_config_file` assignments -* :ghpull:`3630`: CSS adjustments -* :ghpull:`3645`: Don't require HistoryManager to have a shell -* :ghpull:`3643`: don't assume tested ipython is on the PATH -* :ghpull:`3654`: fix single-result AsyncResults -* :ghpull:`3601`: Markdown in heading cells (take 2) -* :ghpull:`3652`: Remove old `docs/examples` -* :ghpull:`3621`: catch any exception appending output -* :ghpull:`3585`: don't blacklist builtin names -* :ghpull:`3647`: Fix `frontend` deprecation warnings in several examples -* :ghpull:`3649`: fix AsyncResult.get_dict for single result -* :ghpull:`3648`: Fix store magic test -* :ghpull:`3650`: Fix, config_file_name was ignored -* :ghpull:`3640`: Gcf.get_active() can return None -* :ghpull:`3571`: Added shorcuts to split cell, merge cell above and merge cell below. -* :ghpull:`3635`: Added missing slash to print-pdf call. -* :ghpull:`3487`: Drop patch for compatibility with pyreadline 1.5 -* :ghpull:`3338`: Allow filename with extension in find_cmd in Windows. -* :ghpull:`3628`: Fix test for Python 3 on Windows. -* :ghpull:`3642`: Fix typo in docs -* :ghpull:`3627`: use DEFAULT_STATIC_FILES_PATH in a test instead of package dir -* :ghpull:`3624`: fix some unicode in zmqhandlers -* :ghpull:`3460`: Set calling program to UNKNOWN, when argv not in sys -* :ghpull:`3632`: Set calling program to UNKNOWN, when argv not in sys (take #2) -* :ghpull:`3629`: Use new entry point for python -m IPython -* :ghpull:`3626`: passing cell to showInPager, closes #3625 -* :ghpull:`3618`: expand terminal color support -* :ghpull:`3623`: raise UsageError for unsupported GUI backends -* :ghpull:`3071`: Add magic function %drun to run code in debugger -* :ghpull:`3608`: a nicer error message when using %pylab magic -* :ghpull:`3592`: add extra_config_file -* :ghpull:`3612`: updated .mailmap -* :ghpull:`3616`: Add examples for interactive use of MPI. -* :ghpull:`3615`: fix regular expression for ANSI escapes -* :ghpull:`3586`: Corrected a typo in the format string for strftime the sphinx.py transformer of nbconvert -* :ghpull:`3611`: check for markdown no longer needed, closes #3610 -* :ghpull:`3555`: Simplify caching of modules with %run -* :ghpull:`3583`: notebook small things -* :ghpull:`3594`: Fix duplicate completion in notebook -* :ghpull:`3600`: parallel: Improved logging for errors during BatchSystemLauncher.stop -* :ghpull:`3595`: Revert "allow markdown in heading cells" -* :ghpull:`3538`: add IPython.start_ipython -* :ghpull:`3562`: Allow custom nbconvert template loaders -* :ghpull:`3582`: pandoc adjustments -* :ghpull:`3560`: Remove max_msg_size -* :ghpull:`3591`: Refer to Setuptools instead of Distribute -* :ghpull:`3590`: IPython.sphinxext needs an __init__.py -* :ghpull:`3581`: Added the possibility to read a custom.css file for tweaking the final html in full_html and reveal templates. -* :ghpull:`3576`: Added support for markdown in heading cells when they are nbconverted. -* :ghpull:`3575`: tweak `run -d` message to 'continue execution' -* :ghpull:`3569`: add PYTHONSTARTUP to startup files -* :ghpull:`3567`: Trigger a single event on js app initialized -* :ghpull:`3565`: style.min.css should always exist... -* :ghpull:`3531`: allow markdown in heading cells -* :ghpull:`3577`: Simplify codemirror ipython-mode -* :ghpull:`3495`: Simplified regexp, and suggestions for clearer regexps. -* :ghpull:`3578`: Use adjustbox to specify figure size in nbconvert -> latex -* :ghpull:`3572`: Skip import irunner test on Windows. -* :ghpull:`3574`: correct static path for CM modes autoload -* :ghpull:`3558`: Add IPython.sphinxext -* :ghpull:`3561`: mention double-control-C to stop notebook server -* :ghpull:`3566`: fix event names -* :ghpull:`3564`: Remove trivial nbconvert example -* :ghpull:`3540`: allow cython cache dir to be deleted -* :ghpull:`3527`: cleanup stale, unused exceptions in parallel.error -* :ghpull:`3529`: ensure raw_input returns str in zmq shell -* :ghpull:`3541`: respect image size metadata in qtconsole -* :ghpull:`3550`: Fixing issue preventing the correct read of images by full_html and reveal exporters. -* :ghpull:`3557`: open markdown links in new tabs -* :ghpull:`3556`: remove mention of nonexistent `_margv` in macro -* :ghpull:`3552`: set overflow-x: hidden on Firefox only -* :ghpull:`3554`: Fix missing import os in latex exporter. -* :ghpull:`3546`: Don't hardcode **latex** posix paths in nbconvert -* :ghpull:`3551`: fix path prefix in nbconvert -* :ghpull:`3533`: Use a CDN to get reveal.js library. -* :ghpull:`3498`: When a notebook is written to file, name the metadata name u''. -* :ghpull:`3548`: Change to standard save icon in Notebook toolbar -* :ghpull:`3539`: Don't hardcode posix paths in nbconvert -* :ghpull:`3508`: notebook supports raw_input and %debug now -* :ghpull:`3526`: ensure 'default' is first in cluster profile list -* :ghpull:`3525`: basic timezone info -* :ghpull:`3532`: include nbconvert templates in installation -* :ghpull:`3515`: update CodeMirror component to 3.14 -* :ghpull:`3513`: add 'No Checkpoints' to Revert menu -* :ghpull:`3536`: format positions are required in Python 2.6.x -* :ghpull:`3521`: Nbconvert fix, silent fail if template doesn't exist -* :ghpull:`3530`: update %store magic docstring -* :ghpull:`3528`: fix local mathjax with custom base_project_url -* :ghpull:`3518`: Clear up unused imports -* :ghpull:`3506`: %store -r restores saved aliases and directory history, as well as variables -* :ghpull:`3516`: make css highlight style configurable -* :ghpull:`3523`: Exclude frontend shim from docs build -* :ghpull:`3514`: use bootstrap `disabled` instead of `ui-state-disabled` -* :ghpull:`3520`: Added relative import of RevealExporter to __init__.py inside exporters module -* :ghpull:`3507`: fix HTML capitalization in nbconvert exporter classes -* :ghpull:`3512`: fix nbconvert filter validation -* :ghpull:`3511`: Get Tracer working after ipapi.get replaced with get_ipython -* :ghpull:`3510`: use `window.onbeforeunload=` for nav-away warning -* :ghpull:`3504`: don't use parent=self in handlers -* :ghpull:`3500`: Merge nbconvert into IPython -* :ghpull:`3478`: restore "unsaved changes" warning on unload -* :ghpull:`3493`: add a dialog when the kernel is auto-restarted -* :ghpull:`3488`: Add test suite for autoreload extension -* :ghpull:`3484`: Catch some pathological cases inside oinspect -* :ghpull:`3481`: Display R errors without Python traceback -* :ghpull:`3468`: fix `%magic` output -* :ghpull:`3430`: add parent to Configurable -* :ghpull:`3491`: Remove unexpected keyword parameter to remove_kernel -* :ghpull:`3485`: SymPy has changed its recommended way to initialize printing -* :ghpull:`3486`: Add test for non-ascii characters in docstrings -* :ghpull:`3483`: Inputtransformer: Allow classic prompts without space -* :ghpull:`3482`: Use an absolute path to iptest, because the tests are not always run from $IPYTHONDIR. -* :ghpull:`3381`: enable 2x (retina) display -* :ghpull:`3450`: Flatten IPython.frontend -* :ghpull:`3477`: pass config to subapps -* :ghpull:`3466`: Kernel fails to start when username has non-ascii characters -* :ghpull:`3465`: Add HTCondor bindings to IPython.parallel -* :ghpull:`3463`: fix typo, closes #3462 -* :ghpull:`3456`: Notice for users who disable javascript -* :ghpull:`3453`: fix cell execution in firefox, closes #3447 -* :ghpull:`3393`: [WIP] bootstrapify -* :ghpull:`3440`: Fix installing mathjax from downloaded file via command line -* :ghpull:`3431`: Provide means for starting the Qt console maximized and with the menu bar hidden -* :ghpull:`3425`: base IPClusterApp inherits from BaseIPythonApp -* :ghpull:`3433`: Update IPython\external\path\__init__.py -* :ghpull:`3298`: Some fixes in IPython Sphinx directive -* :ghpull:`3428`: process escapes in mathjax -* :ghpull:`3420`: thansk -> thanks -* :ghpull:`3416`: Fix doc: "principle" not "principal" -* :ghpull:`3413`: more unique filename for test -* :ghpull:`3364`: Inject requirejs in notebook and start using it. -* :ghpull:`3390`: Fix %paste with blank lines -* :ghpull:`3403`: fix creating config objects from dicts -* :ghpull:`3401`: rollback #3358 -* :ghpull:`3373`: make cookie_secret configurable -* :ghpull:`3307`: switch default ws_url logic to js side -* :ghpull:`3392`: Restore anchor link on h2-h6 -* :ghpull:`3369`: Use different threshold for (auto)scroll in output -* :ghpull:`3370`: normalize unicode notebook filenames -* :ghpull:`3372`: base default cookie name on request host+port -* :ghpull:`3378`: disable CodeMirror drag/drop on Safari -* :ghpull:`3358`: workaround spurious CodeMirror scrollbars -* :ghpull:`3371`: make setting the notebook dirty flag an event -* :ghpull:`3366`: remove long-dead zmq frontend.py and completer.py -* :ghpull:`3382`: cull Session digest history -* :ghpull:`3330`: Fix get_ipython_dir when $HOME is / -* :ghpull:`3319`: IPEP 13: user-expressions and user-variables -* :ghpull:`3384`: comments in tools/gitwash_dumper.py changed (''' to """) -* :ghpull:`3387`: Make submodule checks work under Python 3. -* :ghpull:`3357`: move anchor-link off of heading text -* :ghpull:`3351`: start basic tests of ipcluster Launchers -* :ghpull:`3377`: allow class.__module__ to be None -* :ghpull:`3340`: skip submodule check in package managers -* :ghpull:`3328`: decode subprocess output in launchers -* :ghpull:`3368`: Reenable bracket matching -* :ghpull:`3356`: Mpr fixes -* :ghpull:`3336`: Use new input transformation API in %time magic -* :ghpull:`3325`: Organize the JS and less files by component. -* :ghpull:`3342`: fix test_find_cmd_python -* :ghpull:`3354`: catch socket.error in utils.localinterfaces -* :ghpull:`3341`: fix default cluster count -* :ghpull:`3286`: don't use `get_ipython` from builtins in library code -* :ghpull:`3333`: notebookapp: add missing whitespace to warnings -* :ghpull:`3323`: Strip prompts even if the prompt isn't present on the first line. -* :ghpull:`3321`: Reorganize the python/server side of the notebook -* :ghpull:`3320`: define `__file__` in config files -* :ghpull:`3317`: rename `%%file` to `%%writefile` -* :ghpull:`3304`: set unlimited HWM for all relay devices -* :ghpull:`3315`: Update Sympy_printing extension load -* :ghpull:`3310`: further clarify Image docstring -* :ghpull:`3285`: load extensions in builtin trap -* :ghpull:`3308`: Speed up AsyncResult._wait_for_outputs(0) -* :ghpull:`3294`: fix callbacks as optional in js kernel.execute -* :ghpull:`3276`: Fix: "python ABS/PATH/TO/ipython.py" fails -* :ghpull:`3301`: allow python3 tests without python installed -* :ghpull:`3282`: allow view.map to work with a few more things -* :ghpull:`3284`: remove `ipython.py` entry point -* :ghpull:`3281`: fix ignored IOPub messages with no parent -* :ghpull:`3275`: improve submodule messages / git hooks -* :ghpull:`3239`: Allow "x" icon and esc key to close pager in notebook -* :ghpull:`3290`: Improved heartbeat controller to engine monitoring for long running tasks -* :ghpull:`3142`: Better error message when CWD doesn't exist on startup -* :ghpull:`3066`: Add support for relative import to %run -m (fixes #2727) -* :ghpull:`3269`: protect highlight.js against unknown languages -* :ghpull:`3267`: add missing return -* :ghpull:`3101`: use marked / highlight.js instead of pagedown and prettify -* :ghpull:`3264`: use https url for submodule -* :ghpull:`3263`: fix set_last_checkpoint when no checkpoint -* :ghpull:`3258`: Fix submodule location in setup.py -* :ghpull:`3254`: fix a few URLs from previous PR -* :ghpull:`3240`: remove js components from the repo -* :ghpull:`3158`: IPEP 15: autosave the notebook -* :ghpull:`3252`: move images out of _static folder into _images -* :ghpull:`3251`: Fix for cell magics in Qt console -* :ghpull:`3250`: Added a simple __html__() method to the HTML class -* :ghpull:`3249`: remove copy of sphinx inheritance_diagram.py -* :ghpull:`3235`: Remove the unused print notebook view -* :ghpull:`3238`: Improve the design of the tab completion UI -* :ghpull:`3242`: Make changes of Application.log_format effective -* :ghpull:`3219`: Workaround so only one CTRL-C is required for a new prompt in --gui=qt -* :ghpull:`3190`: allow formatters to specify metadata -* :ghpull:`3231`: improve discovery of public IPs -* :ghpull:`3233`: check prefixes for swallowing kernel args -* :ghpull:`3234`: Removing old autogrow JS code. -* :ghpull:`3232`: Update to CodeMirror 3 and start to ship our components -* :ghpull:`3229`: The HTML output type accidentally got removed from the OutputArea. -* :ghpull:`3228`: Typo in IPython.Parallel documentation -* :ghpull:`3226`: Text in rename dialog was way too big - making it

. -* :ghpull:`3225`: Removing old restuctured text handler and web service. -* :ghpull:`3222`: make BlockingKernelClient the default Client -* :ghpull:`3223`: add missing mathjax_url to new settings dict -* :ghpull:`3089`: add stdin to the notebook -* :ghpull:`3221`: Remove references to HTMLCell (dead code) -* :ghpull:`3205`: add ignored ``*args`` to HasTraits constructor -* :ghpull:`3088`: cleanup IPython handler settings -* :ghpull:`3201`: use much faster regexp for ansi coloring -* :ghpull:`3220`: avoid race condition in profile creation -* :ghpull:`3011`: IPEP 12: add KernelClient -* :ghpull:`3217`: informative error when trying to load directories -* :ghpull:`3174`: Simple class -* :ghpull:`2979`: CM configurable Take 2 -* :ghpull:`3215`: Updates storemagic extension to allow for specifying variable name to load -* :ghpull:`3181`: backport If-Modified-Since fix from tornado -* :ghpull:`3200`: IFrame (VimeoVideo, ScribdDocument, ...) -* :ghpull:`3186`: Fix small inconsistency in nbconvert: etype -> ename -* :ghpull:`3212`: Fix issue #2563, "core.profiledir.check_startup_dir() doesn't work inside py2exe'd installation" -* :ghpull:`3211`: Fix inheritance_diagram Sphinx extension for Sphinx 1.2 -* :ghpull:`3208`: Update link to extensions index -* :ghpull:`3203`: Separate InputSplitter for transforming whole cells -* :ghpull:`3189`: Improve completer -* :ghpull:`3194`: finish up PR #3116 -* :ghpull:`3188`: Add new keycodes -* :ghpull:`2695`: Key the root modules cache by sys.path entries. -* :ghpull:`3182`: clarify %%file docstring -* :ghpull:`3163`: BUG: Fix the set and frozenset pretty printer to handle the empty case correctly -* :ghpull:`3180`: better UsageError for cell magic with no body -* :ghpull:`3184`: Cython cache -* :ghpull:`3175`: Added missing s -* :ghpull:`3173`: Little bits of documentation cleanup -* :ghpull:`2635`: Improve Windows start menu shortcuts (#2) -* :ghpull:`3172`: Add missing import in IPython parallel magics example -* :ghpull:`3170`: default application logger shouldn't propagate -* :ghpull:`3159`: Autocompletion for zsh -* :ghpull:`3105`: move DEFAULT_STATIC_FILES_PATH to IPython.html -* :ghpull:`3144`: minor bower tweaks -* :ghpull:`3141`: Default color output for ls on OSX -* :ghpull:`3137`: fix dot syntax error in inheritance diagram -* :ghpull:`3072`: raise UnsupportedOperation on iostream.fileno() -* :ghpull:`3147`: Notebook support for a reverse proxy which handles SSL -* :ghpull:`3152`: make qtconsole size at startup configurable -* :ghpull:`3162`: adding stream kwarg to current.new_output -* :ghpull:`2981`: IPEP 10: kernel side filtering of display formats -* :ghpull:`3058`: add redirect handler for notebooks by name -* :ghpull:`3041`: support non-modules in @require -* :ghpull:`2447`: Stateful line transformers -* :ghpull:`3108`: fix some O(N) and O(N^2) operations in parallel.map -* :ghpull:`2791`: forward stdout from forked processes -* :ghpull:`3157`: use Python 3-style for pretty-printed sets -* :ghpull:`3148`: closes #3045, #3123 for tornado < version 3.0 -* :ghpull:`3143`: minor heading-link tweaks -* :ghpull:`3136`: Strip useless ANSI escape codes in notebook -* :ghpull:`3126`: Prevent errors when pressing arrow keys in an empty notebook -* :ghpull:`3135`: quick dev installation instructions -* :ghpull:`2889`: Push pandas dataframes to R magic -* :ghpull:`3068`: Don't monkeypatch doctest during IPython startup. -* :ghpull:`3133`: fix argparse version check -* :ghpull:`3102`: set `spellcheck=false` in CodeCell inputarea -* :ghpull:`3064`: add anchors to heading cells -* :ghpull:`3097`: PyQt 4.10: use self._document = self.document() -* :ghpull:`3117`: propagate automagic change to shell -* :ghpull:`3118`: don't give up on weird os names -* :ghpull:`3115`: Fix example -* :ghpull:`2640`: fix quarantine/ipy_editors.py -* :ghpull:`3070`: Add info make target that was missing in old Sphinx -* :ghpull:`3082`: A few small patches to image handling -* :ghpull:`3078`: fix regular expression for detecting links in stdout -* :ghpull:`3054`: restore default behavior for automatic cluster size -* :ghpull:`3073`: fix ipython usage text -* :ghpull:`3083`: fix DisplayMagics.html docstring -* :ghpull:`3080`: noted sub_channel being renamed to iopub_channel -* :ghpull:`3079`: actually use IPKernelApp.kernel_class -* :ghpull:`3076`: Improve notebook.js documentation -* :ghpull:`3063`: add missing `%%html` magic -* :ghpull:`3075`: check for SIGUSR1 before using it, closes #3074 -* :ghpull:`3051`: add width:100% to vbox for webkit / FF consistency -* :ghpull:`2999`: increase registration timeout -* :ghpull:`2997`: fix DictDB default size limit -* :ghpull:`3033`: on resume, print server info again -* :ghpull:`3062`: test double pyximport -* :ghpull:`3046`: cast kernel cwd to bytes on Python 2 on Windows -* :ghpull:`3038`: remove xml from notebook magic docstrings -* :ghpull:`3032`: fix time format to international time format -* :ghpull:`3022`: Fix test for Windows -* :ghpull:`3024`: changed instances of 'outout' to 'output' in alt texts -* :ghpull:`3013`: py3 workaround for reload in cythonmagic -* :ghpull:`2961`: time magic: shorten unnecessary output on windows -* :ghpull:`2987`: fix local files examples in markdown -* :ghpull:`2998`: fix css in .output_area pre -* :ghpull:`3003`: add $include /etc/inputrc to suggested ~/.inputrc -* :ghpull:`2957`: Refactor qt import logic. Fixes #2955 -* :ghpull:`2994`: expanduser on %%file targets -* :ghpull:`2983`: fix run-all (that-> this) -* :ghpull:`2964`: fix count when testing composite error output -* :ghpull:`2967`: shows entire session history when only startsess is given -* :ghpull:`2942`: Move CM IPython theme out of codemirror folder -* :ghpull:`2929`: Cleanup cell insertion -* :ghpull:`2933`: Minordocupdate -* :ghpull:`2968`: fix notebook deletion. -* :ghpull:`2966`: Added assert msg to extract_hist_ranges() -* :ghpull:`2959`: Add command to trim the history database. -* :ghpull:`2681`: Don't enable pylab mode, when matplotlib is not importable -* :ghpull:`2901`: Fix inputhook_wx on osx -* :ghpull:`2871`: truncate potentially long CompositeErrors -* :ghpull:`2951`: use istype on lists/tuples -* :ghpull:`2946`: fix qtconsole history logic for end-of-line -* :ghpull:`2954`: fix logic for append_javascript -* :ghpull:`2941`: fix baseUrl -* :ghpull:`2903`: Specify toggle value on cell line number -* :ghpull:`2911`: display order in output area configurable -* :ghpull:`2897`: Don't rely on BaseProjectUrl data in body tag -* :ghpull:`2894`: Cm configurable -* :ghpull:`2927`: next release will be 1.0 -* :ghpull:`2932`: Simplify using notebook static files from external code -* :ghpull:`2915`: added small config section to notebook docs page -* :ghpull:`2924`: safe_run_module: Silence SystemExit codes 0 and None. -* :ghpull:`2906`: Unpatch/Monkey patch CM -* :ghpull:`2921`: add menu item for undo delete cell -* :ghpull:`2917`: Don't add logging handler if one already exists. -* :ghpull:`2910`: Respect DB_IP and DB_PORT in mongodb tests -* :ghpull:`2926`: Don't die if stderr/stdout do not support set_parent() #2925 -* :ghpull:`2885`: get monospace pager back -* :ghpull:`2876`: fix celltoolbar layout on FF -* :ghpull:`2904`: Skip remaining IPC test on Windows -* :ghpull:`2908`: fix last remaining KernelApp reference -* :ghpull:`2905`: fix a few remaining KernelApp/IPKernelApp changes -* :ghpull:`2900`: Don't assume test case for %time will finish in 0 time -* :ghpull:`2893`: exclude fabfile from tests -* :ghpull:`2884`: Correct import for kernelmanager on Windows -* :ghpull:`2882`: Utils cleanup -* :ghpull:`2883`: Don't call ast.fix_missing_locations unless the AST could have been modified -* :ghpull:`2855`: time(it) magic: Implement minutes/hour formatting and "%%time" cell magic -* :ghpull:`2874`: Empty cell warnings -* :ghpull:`2819`: tweak history prefix search (up/^p) in qtconsole -* :ghpull:`2868`: Import performance -* :ghpull:`2877`: minor css fixes -* :ghpull:`2880`: update examples docs with kernel move -* :ghpull:`2878`: Pass host environment on to kernel -* :ghpull:`2599`: func_kw_complete for builtin and cython with embededsignature=True using docstring -* :ghpull:`2792`: Add key "unique" to history_request protocol -* :ghpull:`2872`: fix payload keys -* :ghpull:`2869`: Fixing styling of toolbar selects on FF. -* :ghpull:`2708`: Less css -* :ghpull:`2854`: Move kernel code into IPython.kernel -* :ghpull:`2864`: Fix %run -t -N TypeError -* :ghpull:`2852`: future pyzmq compatibility -* :ghpull:`2863`: whatsnew/version0.9.txt: Fix '~./ipython' -> '~/.ipython' typo -* :ghpull:`2861`: add missing KernelManager to ConsoleApp class list -* :ghpull:`2850`: Consolidate host IP detection in utils.localinterfaces -* :ghpull:`2859`: Correct docstring of ipython.py -* :ghpull:`2831`: avoid string version comparisons in external.qt -* :ghpull:`2844`: this should address the failure in #2732 -* :ghpull:`2849`: utils/data: Use list comprehension for uniq_stable() -* :ghpull:`2839`: add jinja to install docs / setup.py -* :ghpull:`2841`: Miscellaneous docs fixes -* :ghpull:`2811`: Still more KernelManager cleanup -* :ghpull:`2820`: add '=' to greedy completer delims -* :ghpull:`2818`: log user tracebacks in the kernel (INFO-level) -* :ghpull:`2828`: Clean up notebook Javascript -* :ghpull:`2829`: avoid comparison error in dictdb hub history -* :ghpull:`2830`: BUG: Opening parenthesis after non-callable raises ValueError -* :ghpull:`2718`: try to fallback to pysqlite2.dbapi2 as sqlite3 in core.history -* :ghpull:`2816`: in %edit, don't save "last_call" unless last call succeeded -* :ghpull:`2817`: change ol format order -* :ghpull:`2537`: Organize example notebooks -* :ghpull:`2815`: update release/authors -* :ghpull:`2808`: improve patience for slow Hub in client tests -* :ghpull:`2812`: remove nonfunctional `-la` short arg in cython magic -* :ghpull:`2810`: remove dead utils.upgradedir -* :ghpull:`1671`: __future__ environments -* :ghpull:`2804`: skip ipc tests on Windows -* :ghpull:`2789`: Fixing styling issues with CellToolbar. -* :ghpull:`2805`: fix KeyError creating ZMQStreams in notebook -* :ghpull:`2775`: General cleanup of kernel manager code. -* :ghpull:`2340`: Initial Code to reduce parallel.Client caching -* :ghpull:`2799`: Exit code -* :ghpull:`2800`: use `type(obj) is cls` as switch when canning -* :ghpull:`2801`: Fix a breakpoint bug -* :ghpull:`2795`: Remove outdated code from extensions.autoreload -* :ghpull:`2796`: P3K: fix cookie parsing under Python 3.x (+ duplicate import is removed) -* :ghpull:`2724`: In-process kernel support (take 3) -* :ghpull:`2687`: [WIP] Metaui slideshow -* :ghpull:`2788`: Chrome frame awareness -* :ghpull:`2649`: Add version_request/reply messaging protocol -* :ghpull:`2753`: add `%%px --local` for local execution -* :ghpull:`2783`: Prefilter shouldn't touch execution_count -* :ghpull:`2333`: UI For Metadata -* :ghpull:`2396`: create a ipynbv3 json schema and a validator -* :ghpull:`2757`: check for complete pyside presence before trying to import -* :ghpull:`2782`: Allow the %run magic with '-b' to specify a file. -* :ghpull:`2778`: P3K: fix DeprecationWarning under Python 3.x -* :ghpull:`2776`: remove non-functional View.kill method -* :ghpull:`2755`: can interactively defined classes -* :ghpull:`2774`: Removing unused code in the notebook MappingKernelManager. -* :ghpull:`2773`: Fixed minor typo causing AttributeError to be thrown. -* :ghpull:`2609`: Add 'unique' option to history_request messaging protocol -* :ghpull:`2769`: Allow shutdown when no engines are registered -* :ghpull:`2766`: Define __file__ when we %edit a real file. -* :ghpull:`2476`: allow %edit to work when interactively defined -* :ghpull:`2763`: Reset readline delimiters after loading rmagic. -* :ghpull:`2460`: Better handling of `__file__` when running scripts. -* :ghpull:`2617`: Fix for `units` argument. Adds a `res` argument. -* :ghpull:`2738`: Unicode content crashes the pager (console) -* :ghpull:`2749`: Tell Travis CI to test on Python 3.3 as well -* :ghpull:`2744`: Don't show 'try %paste' message while using magics -* :ghpull:`2728`: shift tab for tooltip -* :ghpull:`2741`: Add note to `%cython` Black-Scholes example warning of missing erf. -* :ghpull:`2743`: BUG: Octavemagic inline plots not working on Windows: Fixed -* :ghpull:`2740`: Following #2737 this error is now a name error -* :ghpull:`2737`: Rmagic: error message when moving an non-existant variable from python to R -* :ghpull:`2723`: diverse fixes for project url -* :ghpull:`2731`: %Rpush: Look for variables in the local scope first. -* :ghpull:`2544`: Infinite loop when multiple debuggers have been attached. -* :ghpull:`2726`: Add qthelp docs creation -* :ghpull:`2730`: added blockquote CSS -* :ghpull:`2729`: Fix Read the doc build, Again -* :ghpull:`2446`: [alternate 2267] Offline mathjax -* :ghpull:`2716`: remove unexisting headings level -* :ghpull:`2717`: One liner to fix debugger printing stack traces when lines of context are larger than source. -* :ghpull:`2713`: Doc bugfix: user_ns is not an attribute of Magic objects. -* :ghpull:`2690`: Fix 'import '... completion for py3 & egg files. -* :ghpull:`2691`: Document OpenMP in %%cython magic -* :ghpull:`2699`: fix jinja2 rendering for password protected notebooks -* :ghpull:`2700`: Skip notebook testing if jinja2 is not available. -* :ghpull:`2692`: Add %%cython magics to generated documentation. -* :ghpull:`2685`: Fix pretty print of types when `__module__` is not available. -* :ghpull:`2686`: Fix tox.ini -* :ghpull:`2604`: Backslashes are misinterpreted as escape-sequences by the R-interpreter. -* :ghpull:`2689`: fix error in doc (arg->kwarg) and pep-8 -* :ghpull:`2683`: for downloads, replaced window.open with window.location.assign -* :ghpull:`2659`: small bugs in js are fixed -* :ghpull:`2363`: Refactor notebook templates to use Jinja2 -* :ghpull:`2662`: qtconsole: wrap argument list in tooltip to match width of text body -* :ghpull:`2328`: addition of classes to generate a link or list of links from files local to the IPython HTML notebook -* :ghpull:`2668`: pylab_not_importable: Catch all exceptions, not just RuntimeErrors. -* :ghpull:`2663`: Fix issue #2660: parsing of help and version arguments -* :ghpull:`2656`: Fix irunner tests when $PYTHONSTARTUP is set -* :ghpull:`2312`: Add bracket matching to code cells in notebook -* :ghpull:`2571`: Start to document Javascript -* :ghpull:`2641`: undefinied that -> this -* :ghpull:`2638`: Fix %paste in Python 3 on Mac -* :ghpull:`2301`: Ast transfomers -* :ghpull:`2616`: Revamp API docs -* :ghpull:`2572`: Make 'Paste Above' the default paste behavior. -* :ghpull:`2574`: Fix #2244 -* :ghpull:`2582`: Fix displaying history when output cache is disabled. -* :ghpull:`2591`: Fix for Issue #2584 -* :ghpull:`2526`: Don't kill paramiko tunnels when receiving ^C -* :ghpull:`2559`: Add psource, pfile, pinfo2 commands to ipdb. -* :ghpull:`2546`: use 4 Pythons to build 4 Windows installers -* :ghpull:`2561`: Fix display of plain text containing multiple carriage returns before line feed -* :ghpull:`2549`: Add a simple 'undo' for cell deletion. -* :ghpull:`2525`: Add event to kernel execution/shell reply. -* :ghpull:`2554`: Avoid stopping in ipdb until we reach the main script. -* :ghpull:`2404`: Option to limit search result in history magic command -* :ghpull:`2294`: inputhook_qt4: Use QEventLoop instead of starting up the QCoreApplication -* :ghpull:`2233`: Refactored Drag and Drop Support in Qt Console -* :ghpull:`1747`: switch between hsplit and vsplit paging (request for feedback) -* :ghpull:`2530`: Adding time offsets to the video -* :ghpull:`2542`: Allow starting IPython as `python -m IPython`. -* :ghpull:`2534`: Do not unescape backslashes in Windows (shellglob) -* :ghpull:`2517`: Improved MathJax, bug fixes -* :ghpull:`2511`: trigger default remote_profile_dir when profile_dir is set -* :ghpull:`2491`: color is supported in ironpython -* :ghpull:`2462`: Track which extensions are loaded -* :ghpull:`2464`: Locate URLs in text output and convert them to hyperlinks. -* :ghpull:`2490`: add ZMQInteractiveShell to IPEngineApp class list -* :ghpull:`2498`: Don't catch tab press when something selected -* :ghpull:`2527`: Run All Above and Run All Below -* :ghpull:`2513`: add GitHub uploads to release script -* :ghpull:`2529`: Windows aware tests for shellglob -* :ghpull:`2478`: Fix doctest_run_option_parser for Windows -* :ghpull:`2519`: clear In[ ] prompt numbers again -* :ghpull:`2467`: Clickable links -* :ghpull:`2500`: Add `encoding` attribute to `OutStream` class. -* :ghpull:`2349`: ENH: added StackExchange-style MathJax filtering -* :ghpull:`2503`: Fix traceback handling of SyntaxErrors without line numbers. -* :ghpull:`2492`: add missing 'qtconsole' extras_require -* :ghpull:`2480`: Add deprecation warnings for sympyprinting -* :ghpull:`2334`: Make the ipengine monitor the ipcontroller heartbeat and die if the ipcontroller goes down -* :ghpull:`2479`: use new _winapi instead of removed _subprocess -* :ghpull:`2474`: fix bootstrap name conflicts -* :ghpull:`2469`: Treat __init__.pyc same as __init__.py in module_list -* :ghpull:`2165`: Add -g option to %run to glob expand arguments -* :ghpull:`2468`: Tell git to ignore __pycache__ directories. -* :ghpull:`2421`: Some notebook tweaks. -* :ghpull:`2291`: Remove old plugin system -* :ghpull:`2127`: Ability to build toolbar in JS -* :ghpull:`2445`: changes for ironpython -* :ghpull:`2420`: Pass ipython_dir to __init__() method of TerminalInteractiveShell's superclass. -* :ghpull:`2432`: Revert #1831, the `__file__` injection in safe_execfile / safe_execfile_ipy. -* :ghpull:`2216`: Autochange highlight with cell magics -* :ghpull:`1946`: Add image message handler in ZMQTerminalInteractiveShell -* :ghpull:`2424`: skip find_cmd when setting up script magics -* :ghpull:`2389`: Catch sqlite DatabaseErrors in more places when reading the history database -* :ghpull:`2395`: Don't catch ImportError when trying to unpack module functions -* :ghpull:`1868`: enable IPC transport for kernels -* :ghpull:`2437`: don't let log cleanup prevent engine start -* :ghpull:`2441`: `sys.maxsize` is the maximum length of a container. -* :ghpull:`2442`: allow iptest to be interrupted -* :ghpull:`2240`: fix message built for engine dying during task -* :ghpull:`2369`: Block until kernel termination after sending a kill signal -* :ghpull:`2439`: Py3k: Octal (0777 -> 0o777) -* :ghpull:`2326`: Detachable pager in notebook. -* :ghpull:`2377`: Fix installation of man pages in Python 3 -* :ghpull:`2407`: add IPython version to message headers -* :ghpull:`2408`: Fix Issue #2366 -* :ghpull:`2405`: clarify TaskScheduler.hwm doc -* :ghpull:`2399`: IndentationError display -* :ghpull:`2400`: Add scroll_to_cell(cell_number) to the notebook -* :ghpull:`2401`: unmock read-the-docs modules -* :ghpull:`2311`: always perform requested trait assignments -* :ghpull:`2393`: New option `n` to limit history search hits -* :ghpull:`2386`: Adapt inline backend to changes in matplotlib -* :ghpull:`2392`: Remove suspicious double quote -* :ghpull:`2387`: Added -L library search path to cythonmagic cell magic -* :ghpull:`2370`: qtconsole: Create a prompt newline by inserting a new block (w/o formatting) -* :ghpull:`1715`: Fix for #1688, traceback-unicode issue -* :ghpull:`2378`: use Singleton.instance() for embed() instead of manual global -* :ghpull:`2373`: fix missing imports in core.interactiveshell -* :ghpull:`2368`: remove notification widget leftover -* :ghpull:`2327`: Parallel: Support get/set of nested objects in view (e.g. dv['a.b']) -* :ghpull:`2362`: Clean up ProgressBar class in example notebook -* :ghpull:`2346`: Extra xterm identification in set_term_title -* :ghpull:`2352`: Notebook: Store the username in a cookie whose name is unique. -* :ghpull:`2358`: add backport_pr to tools -* :ghpull:`2365`: fix names of notebooks for download/save -* :ghpull:`2364`: make clients use 'location' properly (fixes #2361) -* :ghpull:`2354`: Refactor notebook templates to use Jinja2 -* :ghpull:`2339`: add bash completion example -* :ghpull:`2345`: Remove references to 'version' no longer in argparse. Github issue #2343. -* :ghpull:`2347`: adjust division error message checking to account for Python 3 -* :ghpull:`2305`: RemoteError._render_traceback_ calls self.render_traceback -* :ghpull:`2338`: Normalize line endings for ipexec_validate, fix for #2315. -* :ghpull:`2192`: Introduce Notification Area -* :ghpull:`2329`: Better error messages for common magic commands. -* :ghpull:`2337`: ENH: added StackExchange-style MathJax filtering -* :ghpull:`2331`: update css for qtconsole in doc -* :ghpull:`2317`: adding cluster_id to parallel.Client.__init__ -* :ghpull:`2130`: Add -l option to %R magic to allow passing in of local namespace -* :ghpull:`2196`: Fix for bad command line argument to latex -* :ghpull:`2300`: bug fix: was crashing when sqlite3 is not installed -* :ghpull:`2184`: Expose store_history to execute_request messages. -* :ghpull:`2308`: Add welcome_message option to enable_pylab -* :ghpull:`2302`: Fix variable expansion on 'self' -* :ghpull:`2299`: Remove code from prefilter that duplicates functionality in inputsplitter -* :ghpull:`2295`: allow pip install from github repository directly -* :ghpull:`2280`: fix SSH passwordless check for OpenSSH -* :ghpull:`2290`: nbmanager -* :ghpull:`2288`: s/assertEquals/assertEqual (again) -* :ghpull:`2287`: Removed outdated dev docs. -* :ghpull:`2218`: Use redirect for new notebooks -* :ghpull:`2277`: nb: up/down arrow keys move to begin/end of line at top/bottom of cell -* :ghpull:`2045`: Refactoring notebook managers and adding Azure backed storage. -* :ghpull:`2271`: use display instead of send_figure in inline backend hooks -* :ghpull:`2278`: allow disabling SQLite history -* :ghpull:`2225`: Add "--annotate" option to `%%cython` magic. -* :ghpull:`2246`: serialize individual args/kwargs rather than the containers -* :ghpull:`2274`: CLN: Use name to id mapping of notebooks instead of searching. -* :ghpull:`2270`: SSHLauncher tweaks -* :ghpull:`2269`: add missing location when disambiguating controller IP -* :ghpull:`2263`: Allow docs to build on http://readthedocs.io/ -* :ghpull:`2256`: Adding data publication example notebook. -* :ghpull:`2255`: better flush iopub with AsyncResults -* :ghpull:`2261`: Fix: longest_substr([]) -> '' -* :ghpull:`2260`: fix mpr again -* :ghpull:`2242`: Document globbing in `%history -g `. -* :ghpull:`2250`: fix html in notebook example -* :ghpull:`2245`: Fix regression in embed() from pull-request #2096. -* :ghpull:`2248`: track sha of master in test_pr messages -* :ghpull:`2238`: Fast tests -* :ghpull:`2211`: add data publication message -* :ghpull:`2236`: minor test_pr tweaks -* :ghpull:`2231`: Improve Image format validation and add html width,height -* :ghpull:`2232`: Reapply monkeypatch to inspect.findsource() -* :ghpull:`2235`: remove spurious print statement from setupbase.py -* :ghpull:`2222`: adjust how canning deals with import strings -* :ghpull:`2224`: fix css typo -* :ghpull:`2223`: Custom tracebacks -* :ghpull:`2214`: use KernelApp.exec_lines/files in IPEngineApp -* :ghpull:`2199`: Wrap JS published by %%javascript in try/catch -* :ghpull:`2212`: catch errors in markdown javascript -* :ghpull:`2190`: Update code mirror 2.22 to 2.32 -* :ghpull:`2200`: documentation build broken in bb429da5b -* :ghpull:`2194`: clean nan/inf in json_clean -* :ghpull:`2198`: fix mpr for earlier git version -* :ghpull:`2175`: add FileFindHandler for Notebook static files -* :ghpull:`1990`: can func_defaults -* :ghpull:`2069`: start improving serialization in parallel code -* :ghpull:`2202`: Create a unique & temporary IPYTHONDIR for each testing group. -* :ghpull:`2204`: Work around lack of os.kill in win32. -* :ghpull:`2148`: win32 iptest: Use subprocess.Popen() instead of os.system(). -* :ghpull:`2179`: Pylab switch -* :ghpull:`2124`: Add an API for registering magic aliases. -* :ghpull:`2169`: ipdb: pdef, pdoc, pinfo magics all broken -* :ghpull:`2174`: Ensure consistent indentation in `%magic`. -* :ghpull:`1930`: add size-limiting to the DictDB backend -* :ghpull:`2189`: Fix IPython.lib.latextools for Python 3 -* :ghpull:`2186`: removed references to h5py dependence in octave magic documentation -* :ghpull:`2183`: Include the kernel object in the event object passed to kernel events -* :ghpull:`2185`: added test for %store, fixed storemagic -* :ghpull:`2138`: Use breqn.sty in dvipng backend if possible -* :ghpull:`2182`: handle undefined param in notebooklist -* :ghpull:`1831`: fix #1814 set __file__ when running .ipy files -* :ghpull:`2051`: Add a metadata attribute to messages -* :ghpull:`1471`: simplify IPython.parallel connections and enable Controller Resume -* :ghpull:`2181`: add %%javascript, %%svg, and %%latex display magics -* :ghpull:`2116`: different images in 00_notebook-tour -* :ghpull:`2092`: %prun: Restore `stats.stream` after running `print_stream`. -* :ghpull:`2159`: show message on notebook list if server is unreachable -* :ghpull:`2176`: fix git mpr -* :ghpull:`2152`: [qtconsole] Namespace not empty at startup -* :ghpull:`2177`: remove numpy install from travis/tox scripts -* :ghpull:`2090`: New keybinding for code cell execution + cell insertion -* :ghpull:`2160`: Updating the parallel options pricing example -* :ghpull:`2168`: expand line in cell magics -* :ghpull:`2170`: Fix tab completion with IPython.embed_kernel(). -* :ghpull:`2096`: embed(): Default to the future compiler flags of the calling frame. -* :ghpull:`2163`: fix 'remote_profie_dir' typo in SSH launchers -* :ghpull:`2158`: [2to3 compat ] Tuple params in func defs -* :ghpull:`2089`: Fix unittest DeprecationWarnings -* :ghpull:`2142`: Refactor test_pr.py -* :ghpull:`2140`: 2to3: Apply `has_key` fixer. -* :ghpull:`2131`: Add option append (-a) to %save -* :ghpull:`2117`: use explicit url in notebook example -* :ghpull:`2133`: Tell git that ``*.py`` files contain Python code, for use in word-diffs. -* :ghpull:`2134`: Apply 2to3 `next` fix. -* :ghpull:`2126`: ipcluster broken with any batch launcher (PBS/LSF/SGE) -* :ghpull:`2104`: Windows make file for Sphinx documentation -* :ghpull:`2074`: Make BG color of inline plot configurable -* :ghpull:`2123`: BUG: Look up the `_repr_pretty_` method on the class within the MRO rath... -* :ghpull:`2100`: [in progress] python 2 and 3 compatibility without 2to3, second try -* :ghpull:`2128`: open notebook copy in different tabs -* :ghpull:`2073`: allows password and prefix for notebook -* :ghpull:`1993`: Print View -* :ghpull:`2086`: re-aliad %ed to %edit in qtconsole -* :ghpull:`2110`: Fixes and improvements to the input splitter -* :ghpull:`2101`: fix completer deletting newline -* :ghpull:`2102`: Fix logging on interactive shell. -* :ghpull:`2088`: Fix (some) Python 3.2 ResourceWarnings -* :ghpull:`2064`: conform to pep 3110 -* :ghpull:`2076`: Skip notebook 'static' dir in test suite. -* :ghpull:`2063`: Remove umlauts so py3 installations on LANG=C systems succeed. -* :ghpull:`2068`: record sysinfo in sdist -* :ghpull:`2067`: update tools/release_windows.py -* :ghpull:`2065`: Fix parentheses typo -* :ghpull:`2062`: Remove duplicates and auto-generated files from repo. -* :ghpull:`2061`: use explicit tuple in exception -* :ghpull:`2060`: change minus to \- or \(hy in manpages - -Issues (691): - -* :ghissue:`3940`: Install process documentation overhaul -* :ghissue:`3946`: The PDF option for `--post` should work with lowercase -* :ghissue:`3957`: Notebook help page broken in Firefox -* :ghissue:`3894`: nbconvert test failure -* :ghissue:`3887`: 1.0.0a1 shows blank screen in both firefox and chrome (windows 7) -* :ghissue:`3703`: `nbconvert`: Output options -- names and documentation -* :ghissue:`3931`: Tab completion not working during debugging in the notebook -* :ghissue:`3936`: Ipcluster plugin is not working with Ipython 1.0dev -* :ghissue:`3941`: IPython Notebook kernel crash on Win7x64 -* :ghissue:`3926`: Ending Notebook renaming dialog with return creates new-line -* :ghissue:`3932`: Incorrect empty docstring -* :ghissue:`3928`: Passing variables to script from the workspace -* :ghissue:`3774`: Notebooks with spaces in their names breaks nbconvert latex graphics -* :ghissue:`3916`: tornado needs its own check -* :ghissue:`3915`: Link to Parallel examples "found on GitHub" broken in docs -* :ghissue:`3895`: Keyboard shortcuts box in notebook doesn't fit the screen -* :ghissue:`3912`: IPython.utils fails automated test for RC1 1.0.0 -* :ghissue:`3636`: Code cell missing highlight on load -* :ghissue:`3897`: under Windows, "ipython3 nbconvert "C:/blabla/first_try.ipynb" --to latex --post PDF" POST processing action fails because of a bad parameter -* :ghissue:`3900`: python3 install syntax errors (OS X 10.8.4) -* :ghissue:`3899`: nbconvert to latex fails on notebooks with spaces in file name -* :ghissue:`3881`: Temporary Working Directory Test Fails -* :ghissue:`2750`: A way to freeze code cells in the notebook -* :ghissue:`3893`: Resize Local Image Files in Notebook doesn't work -* :ghissue:`3823`: nbconvert on windows: tex and paths -* :ghissue:`3885`: under Windows, "ipython3 nbconvert "C:/blabla/first_try.ipynb" --to latex" write "\" instead of "/" to reference file path in the .tex file -* :ghissue:`3889`: test_qt fails due to assertion error 'qt4' != 'qt' -* :ghissue:`3890`: double post, disregard this issue -* :ghissue:`3689`: nbconvert, remaining tests -* :ghissue:`3874`: Up/Down keys don't work to "Search previous command history" (besides Ctrl-p/Ctrl-n) -* :ghissue:`3853`: CodeMirror locks up in the notebook -* :ghissue:`3862`: can only connect to an ipcluster started with v1.0.0-dev (master branch) using an older ipython (v0.13.2), but cannot connect using ipython (v1.0.0-dev) -* :ghissue:`3869`: custom css not working. -* :ghissue:`2960`: Keyboard shortcuts -* :ghissue:`3795`: ipcontroller process goes to 100% CPU, ignores connection requests -* :ghissue:`3553`: Ipython and pylab crashes in windows and canopy -* :ghissue:`3837`: Cannot set custom mathjax url, crash notebook server. -* :ghissue:`3808`: "Naming" releases ? -* :ghissue:`2431`: TypeError: must be string without null bytes, not str -* :ghissue:`3856`: `?` at end of comment causes line to execute -* :ghissue:`3731`: nbconvert: add logging for the different steps of nbconvert -* :ghissue:`3835`: Markdown cells do not render correctly when mathjax is disabled -* :ghissue:`3843`: nbconvert to rst: leftover "In[ ]" -* :ghissue:`3799`: nbconvert: Ability to specify name of output file -* :ghissue:`3726`: Document when IPython.start_ipython() should be used versus IPython.embed() -* :ghissue:`3778`: Add no more readonly view in what's new -* :ghissue:`3754`: No Print View in Notebook in 1.0dev -* :ghissue:`3798`: IPython 0.12.1 Crashes on autocompleting sqlalchemy.func.row_number properties -* :ghissue:`3811`: Opening notebook directly from the command line with multi-directory support installed -* :ghissue:`3775`: Annoying behavior when clicking on cell after execution (Ctrl+Enter) -* :ghissue:`3809`: Possible to add some bpython features? -* :ghissue:`3810`: Printing the contents of an image file messes up shell text -* :ghissue:`3702`: `nbconvert`: Default help message should be that of --help -* :ghissue:`3735`: Nbconvert 1.0.0a1 does not take into account the pdf extensions in graphs -* :ghissue:`3719`: Bad strftime format, for windows, in nbconvert exporter -* :ghissue:`3786`: Zmq errors appearing with `Ctrl-C` in console/qtconsole -* :ghissue:`3019`: disappearing scrollbar on tooltip in Chrome 24 on Ubuntu 12.04 -* :ghissue:`3785`: ipdb completely broken in Qt console -* :ghissue:`3796`: Document the meaning of milestone/issues-tags for users. -* :ghissue:`3788`: Do not auto show tooltip if docstring empty. -* :ghissue:`1366`: [Web page] No link to front page from documentation -* :ghissue:`3739`: nbconvert (to slideshow) misses some of the math in markdown cells -* :ghissue:`3768`: increase and make timeout configurable in console completion. -* :ghissue:`3724`: ipcluster only running on one cpu -* :ghissue:`1592`: better message for unsupported nbformat -* :ghissue:`2049`: Can not stop "ipython kernel" on windows -* :ghissue:`3757`: Need direct entry point to given notebook -* :ghissue:`3745`: ImportError: cannot import name check_linecache_ipython -* :ghissue:`3701`: `nbconvert`: Final output file should be in same directory as input file -* :ghissue:`3738`: history -o works but history with -n produces identical results -* :ghissue:`3740`: error when attempting to run 'make' in docs directory -* :ghissue:`3737`: ipython nbconvert crashes with ValueError: Invalid format string. -* :ghissue:`3730`: nbconvert: unhelpful error when pandoc isn't installed -* :ghissue:`3718`: markdown cell cursor misaligned in notebook -* :ghissue:`3710`: multiple input fields for %debug in the notebook after resetting the kernel -* :ghissue:`3713`: PyCharm has problems with IPython working inside PyPy created by virtualenv -* :ghissue:`3712`: Code completion: Complete on dictionary keys -* :ghissue:`3680`: --pylab and --matplotlib flag -* :ghissue:`3698`: nbconvert: Unicode error with minus sign -* :ghissue:`3693`: nbconvert does not process SVGs into PDFs -* :ghissue:`3688`: nbconvert, figures not extracting with Python 3.x -* :ghissue:`3542`: note new dependencies in docs / setup.py -* :ghissue:`2556`: [pagedown] do not target_blank anchor link -* :ghissue:`3684`: bad message when %pylab fails due import *other* than matplotlib -* :ghissue:`3682`: ipython notebook pylab inline import_all=False -* :ghissue:`3596`: MathjaxUtils race condition? -* :ghissue:`1540`: Comment/uncomment selection in notebook -* :ghissue:`2702`: frozen setup: permission denied for default ipython_dir -* :ghissue:`3672`: allow_none on Number-like traits. -* :ghissue:`2411`: add CONTRIBUTING.md -* :ghissue:`481`: IPython terminal issue with Qt4Agg on XP SP3 -* :ghissue:`2664`: How to preserve user variables from import clashing? -* :ghissue:`3436`: enable_pylab(import_all=False) still imports np -* :ghissue:`2630`: lib.pylabtools.figsize : NameError when using Qt4Agg backend and %pylab magic. -* :ghissue:`3154`: Notebook: no event triggered when a Cell is created -* :ghissue:`3579`: Nbconvert: SVG are not transformed to PDF anymore -* :ghissue:`3604`: MathJax rendering problem in `%%latex` cell -* :ghissue:`3668`: AttributeError: 'BlockingKernelClient' object has no attribute 'started_channels' -* :ghissue:`3245`: SyntaxError: encoding declaration in Unicode string -* :ghissue:`3639`: %pylab inline in IPYTHON notebook throws "RuntimeError: Cannot activate multiple GUI eventloops" -* :ghissue:`3663`: frontend deprecation warnings -* :ghissue:`3661`: run -m not behaving like python -m -* :ghissue:`3597`: re-do PR #3531 - allow markdown in Header cell -* :ghissue:`3053`: Markdown in header cells is not rendered -* :ghissue:`3655`: IPython finding its way into pasted strings. -* :ghissue:`3620`: uncaught errors in HTML output -* :ghissue:`3646`: get_dict() error -* :ghissue:`3004`: `%load_ext rmagic` fails when legacy ipy_user_conf.py is installed (in ipython 0.13.1 / OSX 10.8) -* :ghissue:`3638`: setp() issue in ipython notebook with figure references -* :ghissue:`3634`: nbconvert reveal to pdf conversion ignores styling, prints only a single page. -* :ghissue:`1307`: Remove pyreadline workarounds, we now require pyreadline >= 1.7.1 -* :ghissue:`3316`: find_cmd test failure on Windows -* :ghissue:`3494`: input() in notebook doesn't work in Python 3 -* :ghissue:`3427`: Deprecate `$` as mathjax delimiter -* :ghissue:`3625`: Pager does not open from button -* :ghissue:`3149`: Miscellaneous small nbconvert feedback -* :ghissue:`3617`: 256 color escapes support -* :ghissue:`3609`: %pylab inline blows up for single process ipython -* :ghissue:`2934`: Publish the Interactive MPI Demo Notebook -* :ghissue:`3614`: ansi escapes broken in master (ls --color) -* :ghissue:`3610`: If you don't have markdown, python setup.py install says no pygments -* :ghissue:`3547`: %run modules clobber each other -* :ghissue:`3602`: import_item fails when one tries to use DottedObjectName instead of a string -* :ghissue:`3563`: Duplicate tab completions in the notebook -* :ghissue:`3599`: Problems trying to run IPython on python3 without installing... -* :ghissue:`2937`: too long completion in notebook -* :ghissue:`3479`: Write empty name for the notebooks -* :ghissue:`3505`: nbconvert: Failure in specifying user filter -* :ghissue:`1537`: think a bit about namespaces -* :ghissue:`3124`: Long multiline strings in Notebook -* :ghissue:`3464`: run -d message unclear -* :ghissue:`2706`: IPython 0.13.1 ignoring $PYTHONSTARTUP -* :ghissue:`3587`: LaTeX escaping bug in nbconvert when exporting to HTML -* :ghissue:`3213`: Long running notebook died with a coredump -* :ghissue:`3580`: Running ipython with pypy on windows -* :ghissue:`3573`: custom.js not working -* :ghissue:`3544`: IPython.lib test failure on Windows -* :ghissue:`3352`: Install Sphinx extensions -* :ghissue:`2971`: [notebook]user needs to press ctrl-c twice to stop notebook server should be put into terminal window -* :ghissue:`2413`: ipython3 qtconsole fails to install: ipython 0.13 has no such extra feature 'qtconsole' -* :ghissue:`2618`: documentation is incorrect for install process -* :ghissue:`2595`: mac 10.8 qtconsole export history -* :ghissue:`2586`: cannot store aliases -* :ghissue:`2714`: ipython qtconsole print unittest messages in console instead his own window. -* :ghissue:`2669`: cython magic failing to work with openmp. -* :ghissue:`3256`: Vagrant pandas instance of IPython Notebook does not respect additional plotting arguments -* :ghissue:`3010`: cython magic fail if cache dir is deleted while in session -* :ghissue:`2044`: prune unused names from parallel.error -* :ghissue:`1145`: Online help utility broken in QtConsole -* :ghissue:`3439`: Markdown links no longer open in new window (with change from pagedown to marked) -* :ghissue:`3476`: _margv for macros seems to be missing -* :ghissue:`3499`: Add reveal.js library (version 2.4.0) inside IPython -* :ghissue:`2771`: Wiki Migration to GitHub -* :ghissue:`2887`: ipcontroller purging some engines during connect -* :ghissue:`626`: Enable Resuming Controller -* :ghissue:`2824`: Kernel restarting after message "Kernel XXXX failed to respond to heartbeat" -* :ghissue:`2823`: %%cython magic gives ImportError: dlopen(long_file_name.so, 2): image not found -* :ghissue:`2891`: In IPython for Python 3, system site-packages comes before user site-packages -* :ghissue:`2928`: Add magic "watch" function (example) -* :ghissue:`2931`: Problem rendering pandas dataframe in Firefox for Windows -* :ghissue:`2939`: [notebook] Figure legend not shown in inline backend if ouside the box of the axes -* :ghissue:`2972`: [notebook] in Markdown mode, press Enter key at the end of , the next line is indented unexpectly -* :ghissue:`3069`: Instructions for installing IPython notebook on Windows -* :ghissue:`3444`: Encoding problem: cannot use if user's name is not ascii? -* :ghissue:`3335`: Reenable bracket matching -* :ghissue:`3386`: Magic %paste not working in Python 3.3.2. TypeError: Type str doesn't support the buffer API -* :ghissue:`3543`: Exception shutting down kernel from notebook dashboard (0.13.1) -* :ghissue:`3549`: Codecell size changes with selection -* :ghissue:`3445`: Adding newlines in %%latex cell -* :ghissue:`3237`: [notebook] Can't close a notebook without errors -* :ghissue:`2916`: colon invokes auto(un)indent in markdown cells -* :ghissue:`2167`: Indent and dedent in htmlnotebook -* :ghissue:`3545`: Notebook save button icon not clear -* :ghissue:`3534`: nbconvert incompatible with Windows? -* :ghissue:`3489`: Update example notebook that raw_input is allowed -* :ghissue:`3396`: Notebook checkpoint time is displayed an hour out -* :ghissue:`3261`: Empty revert to checkpoint menu if no checkpoint... -* :ghissue:`2984`: "print" magic does not work in Python 3 -* :ghissue:`3524`: Issues with pyzmq and ipython on EPD update -* :ghissue:`2434`: %store magic not auto-restoring -* :ghissue:`2720`: base_url and static path -* :ghissue:`2234`: Update various low resolution graphics for retina displays -* :ghissue:`2842`: Remember passwords for pw-protected notebooks -* :ghissue:`3244`: qtconsole: ValueError('close_fds is not supported on Windows platforms if you redirect stdin/stdout/stderr',) -* :ghissue:`2215`: AsyncResult.wait(0) can hang waiting for the client to get results? -* :ghissue:`2268`: provide mean to retrieve static data path -* :ghissue:`1905`: Expose UI for worksheets within each notebook -* :ghissue:`2380`: Qt inputhook prevents modal dialog boxes from displaying -* :ghissue:`3185`: prettify on double // -* :ghissue:`2821`: Test failure: IPython.parallel.tests.test_client.test_resubmit_header -* :ghissue:`2475`: [Notebook] Line is deindented when typing eg a colon in markdown mode -* :ghissue:`2470`: Do not destroy valid notebooks -* :ghissue:`860`: Allow the standalone export of a notebook to HTML -* :ghissue:`2652`: notebook with qt backend crashes at save image location popup -* :ghissue:`1587`: Improve kernel restarting in the notebook -* :ghissue:`2710`: Saving a plot in Mac OS X backend crashes IPython -* :ghissue:`2596`: notebook "Last saved:" is misleading on file opening. -* :ghissue:`2671`: TypeError :NoneType when executed "ipython qtconsole" in windows console -* :ghissue:`2703`: Notebook scrolling breaks after pager is shown -* :ghissue:`2803`: KernelManager and KernelClient should be two separate objects -* :ghissue:`2693`: TerminalIPythonApp configuration fails without ipython_config.py -* :ghissue:`2531`: IPython 0.13.1 python 2 32-bit installer includes 64-bit ipython*.exe launchers in the scripts folder -* :ghissue:`2520`: Control-C kills port forwarding -* :ghissue:`2279`: Setting `__file__` to None breaks Mayavi import -* :ghissue:`2161`: When logged into notebook, long titles are incorrectly positioned -* :ghissue:`1292`: Notebook, Print view should not be editable... -* :ghissue:`1731`: test parallel launchers -* :ghissue:`3227`: Improve documentation of ipcontroller and possible BUG -* :ghissue:`2896`: IPController very unstable -* :ghissue:`3517`: documentation build broken in head -* :ghissue:`3522`: UnicodeDecodeError: 'ascii' codec can't decode byte on Pycharm on Windows -* :ghissue:`3448`: Please include MathJax fonts with IPython Notebook -* :ghissue:`3519`: IPython Parallel map mysteriously turns pandas Series into numpy ndarray -* :ghissue:`3345`: IPython embedded shells ask if I want to exit, but I set confirm_exit = False -* :ghissue:`3509`: IPython won't close without asking "Are you sure?" in Firefox -* :ghissue:`3471`: Notebook jinja2/markupsafe dependencies in manual -* :ghissue:`3502`: Notebook broken in master -* :ghissue:`3302`: autoreload does not work in ipython 0.13.x, python 3.3 -* :ghissue:`3475`: no warning when leaving/closing notebook on master without saved changes -* :ghissue:`3490`: No obvious feedback when kernel crashes -* :ghissue:`1912`: Move all autoreload tests to their own group -* :ghissue:`2577`: sh.py and ipython for python 3.3 -* :ghissue:`3467`: %magic doesn't work -* :ghissue:`3501`: Editing markdown cells that wrap has off-by-one errors in cursor positioning -* :ghissue:`3492`: IPython for Python3 -* :ghissue:`3474`: unexpected keyword argument to remove_kernel -* :ghissue:`2283`: TypeError when using '?' after a string in a %logstart session -* :ghissue:`2787`: rmagic and pandas DataFrame -* :ghissue:`2605`: Ellipsis literal triggers AttributeError -* :ghissue:`1179`: Test unicode source in pinfo -* :ghissue:`2055`: drop Python 3.1 support -* :ghissue:`2293`: IPEP 2: Input transformations -* :ghissue:`2790`: %paste and %cpaste not removing "..." lines -* :ghissue:`3480`: Testing fails because iptest.py cannot be found -* :ghissue:`2580`: will not run within PIL build directory -* :ghissue:`2797`: RMagic, Dataframe Conversion Problem -* :ghissue:`2838`: Empty lines disappear from triple-quoted literals. -* :ghissue:`3050`: Broken link on IPython.core.display page -* :ghissue:`3473`: Config not passed down to subcommands -* :ghissue:`3462`: Setting log_format in config file results in error (and no format changes) -* :ghissue:`3311`: Notebook (occasionally) not working on windows (Sophos AV) -* :ghissue:`3461`: Cursor positioning off by a character in auto-wrapped lines -* :ghissue:`3454`: _repr_html_ error -* :ghissue:`3457`: Space in long Paragraph Markdown cell with Chinese or Japanese -* :ghissue:`3447`: Run Cell Does not Work -* :ghissue:`1373`: Last lines in long cells are hidden -* :ghissue:`1504`: Revisit serialization in IPython.parallel -* :ghissue:`1459`: Can't connect to 2 HTTPS notebook servers on the same host -* :ghissue:`678`: Input prompt stripping broken with multiline data structures -* :ghissue:`3001`: IPython.notebook.dirty flag is not set when a cell has unsaved changes -* :ghissue:`3077`: Multiprocessing semantics in parallel.view.map -* :ghissue:`3056`: links across notebooks -* :ghissue:`3120`: Tornado 3.0 -* :ghissue:`3156`: update pretty to use Python 3 style for sets -* :ghissue:`3197`: Can't escape multiple dollar signs in a markdown cell -* :ghissue:`3309`: `Image()` signature/doc improvements -* :ghissue:`3415`: Bug in IPython/external/path/__init__.py -* :ghissue:`3446`: Feature suggestion: Download matplotlib figure to client browser -* :ghissue:`3295`: autoexported notebooks: only export explicitly marked cells -* :ghissue:`3442`: Notebook: Summary table extracted from markdown headers -* :ghissue:`3438`: Zooming notebook in chrome is broken in master -* :ghissue:`1378`: Implement autosave in notebook -* :ghissue:`3437`: Highlighting matching parentheses -* :ghissue:`3435`: module search segfault -* :ghissue:`3424`: ipcluster --version -* :ghissue:`3434`: 0.13.2 Ipython/genutils.py doesn't exist -* :ghissue:`3426`: Feature request: Save by cell and not by line #: IPython %save magic -* :ghissue:`3412`: Non Responsive Kernel: Running a Django development server from an IPython Notebook -* :ghissue:`3408`: Save cell toolbar and slide type metadata in notebooks -* :ghissue:`3246`: %paste regression with blank lines -* :ghissue:`3404`: Weird error with $variable and grep in command line magic (!command) -* :ghissue:`3405`: Key auto-completion in dictionaries? -* :ghissue:`3259`: Codemirror linenumber css broken -* :ghissue:`3397`: Vertical text misalignment in Markdown cells -* :ghissue:`3391`: Revert #3358 once fix integrated into CM -* :ghissue:`3360`: Error 500 while saving IPython notebook -* :ghissue:`3375`: Frequent Safari/Webkit crashes -* :ghissue:`3365`: zmq frontend -* :ghissue:`2654`: User_expression issues -* :ghissue:`3389`: Store history as plain text -* :ghissue:`3388`: Ipython parallel: open TCP connection created for each result returned from engine -* :ghissue:`3385`: setup.py failure on Python 3 -* :ghissue:`3376`: Setting `__module__` to None breaks pretty printing -* :ghissue:`3374`: ipython qtconsole does not display the prompt on OSX -* :ghissue:`3380`: simple call to kernel -* :ghissue:`3379`: TaskRecord key 'started' not set -* :ghissue:`3241`: notebook connection time out -* :ghissue:`3334`: magic interpreter interprets non magic commands? -* :ghissue:`3326`: python3.3: Type error when launching SGE cluster in IPython notebook -* :ghissue:`3349`: pip3 doesn't run 2to3? -* :ghissue:`3347`: Longlist support in ipdb -* :ghissue:`3343`: Make pip install / easy_install faster -* :ghissue:`3337`: git submodules broke nightly PPA builds -* :ghissue:`3206`: Copy/Paste Regression in QtConsole -* :ghissue:`3329`: Buggy linewrap in Mac OSX Terminal (Mountain Lion) -* :ghissue:`3327`: Qt version check broken -* :ghissue:`3303`: parallel tasks never finish under heavy load -* :ghissue:`1381`: '\\' for equation continuations require an extra '\' in markdown cells -* :ghissue:`3314`: Error launching IPython -* :ghissue:`3306`: Test failure when running on a Vagrant VM -* :ghissue:`3280`: IPython.utils.process.getoutput returns stderr -* :ghissue:`3299`: variables named _ or __ exhibit incorrect behavior -* :ghissue:`3196`: add an "x" or similar to htmlnotebook pager -* :ghissue:`3293`: Several 404 errors for js files Firefox -* :ghissue:`3292`: syntax highlighting in chrome on OSX 10.8.3 -* :ghissue:`3288`: Latest dev version hangs on page load -* :ghissue:`3283`: ipython dev retains directory information after directory change -* :ghissue:`3279`: custom.css is not overridden in the dev IPython (1.0) -* :ghissue:`2727`: %run -m doesn't support relative imports -* :ghissue:`3268`: GFM triple backquote and unknown language -* :ghissue:`3273`: Suppressing all plot related outputs -* :ghissue:`3272`: Backspace while completing load previous page -* :ghissue:`3260`: Js error in savewidget -* :ghissue:`3247`: scrollbar in notebook when not needed? -* :ghissue:`3243`: notebook: option to view json source from browser -* :ghissue:`3265`: 404 errors when running IPython 1.0dev -* :ghissue:`3257`: setup.py not finding submodules -* :ghissue:`3253`: Incorrect Qt and PySide version comparison -* :ghissue:`3248`: Cell magics broken in Qt console -* :ghissue:`3012`: Problems with the less based style.min.css -* :ghissue:`2390`: Image width/height don't work in embedded images -* :ghissue:`3236`: cannot set TerminalIPythonApp.log_format -* :ghissue:`3214`: notebook kernel dies if started with invalid parameter -* :ghissue:`2980`: Remove HTMLCell ? -* :ghissue:`3128`: qtconsole hangs on importing pylab (using X forwarding) -* :ghissue:`3198`: Hitting recursive depth causing all notebook pages to hang -* :ghissue:`3218`: race conditions in profile directory creation -* :ghissue:`3177`: OverflowError execption in handlers.py -* :ghissue:`2563`: core.profiledir.check_startup_dir() doesn't work inside py2exe'd installation -* :ghissue:`3207`: [Feature] folders for ipython notebook dashboard -* :ghissue:`3178`: cell magics do not work with empty lines after #2447 -* :ghissue:`3204`: Default plot() colors unsuitable for red-green colorblind users -* :ghissue:`1789`: ``:\n/*foo`` turns into ``:\n*(foo)`` in triple-quoted strings. -* :ghissue:`3202`: File cell magic fails with blank lines -* :ghissue:`3199`: %%cython -a stopped working? -* :ghissue:`2688`: obsolete imports in import autocompletion -* :ghissue:`3192`: Python2, Unhandled exception, __builtin__.True = False -* :ghissue:`3179`: script magic error message loop -* :ghissue:`3009`: use XDG_CACHE_HOME for cython objects -* :ghissue:`3059`: Bugs in 00_notebook_tour example. -* :ghissue:`3104`: Integrate a javascript file manager into the notebook front end -* :ghissue:`3176`: Particular equation not rendering (notebook) -* :ghissue:`1133`: [notebook] readonly and upload files/UI -* :ghissue:`2975`: [notebook] python file and cell toolbar -* :ghissue:`3017`: SciPy.weave broken in IPython notebook/ qtconsole -* :ghissue:`3161`: paste macro not reading spaces correctly -* :ghissue:`2835`: %paste not working on WinXpSP3/ipython-0.13.1.py2-win32-PROPER.exe/python27 -* :ghissue:`2628`: Make transformers work for lines following decorators -* :ghissue:`2612`: Multiline String containing ":\n?foo\n" confuses interpreter to replace ?foo with get_ipython().magic(u'pinfo foo') -* :ghissue:`2539`: Request: Enable cell magics inside of .ipy scripts -* :ghissue:`2507`: Multiline string does not work (includes `...`) with doctest type input in IPython notebook -* :ghissue:`2164`: Request: Line breaks in line magic command -* :ghissue:`3106`: poor parallel performance with many jobs -* :ghissue:`2438`: print inside multiprocessing crashes Ipython kernel -* :ghissue:`3155`: Bad md5 hash for package 0.13.2 -* :ghissue:`3045`: [Notebook] Ipython Kernel does not start if disconnected from internet(/network?) -* :ghissue:`3146`: Using celery in python 3.3 -* :ghissue:`3145`: The notebook viewer is down -* :ghissue:`2385`: grep --color not working well with notebook -* :ghissue:`3131`: Quickly install from source in a clean virtualenv? -* :ghissue:`3139`: Rolling log for ipython -* :ghissue:`3127`: notebook with pylab=inline appears to call figure.draw twice -* :ghissue:`3129`: Walking up and down the call stack -* :ghissue:`3123`: Notebook crashed if unplugged ethernet cable -* :ghissue:`3121`: NB should use normalize.css? was #3049 -* :ghissue:`3087`: Disable spellchecking in notebook -* :ghissue:`3084`: ipython pyqt 4.10 incompatibilty, QTextBlockUserData -* :ghissue:`3113`: Fails to install under Jython 2.7 beta -* :ghissue:`3110`: Render of h4 headers is not correct in notebook (error in renderedhtml.css) -* :ghissue:`3109`: BUG: read_csv: dtype={'id' : np.str}: Datatype not understood -* :ghissue:`3107`: Autocompletion of object attributes in arrays -* :ghissue:`3103`: Reset locale setting in qtconsole -* :ghissue:`3090`: python3.3 Entry Point not found -* :ghissue:`3081`: UnicodeDecodeError when using Image(data="some.jpeg") -* :ghissue:`2834`: url regexp only finds one link -* :ghissue:`3091`: qtconsole breaks doctest.testmod() in Python 3.3 -* :ghissue:`3074`: SIGUSR1 not available on Windows -* :ghissue:`2996`: registration::purging stalled registration high occurrence in small clusters -* :ghissue:`3065`: diff-ability of notebooks -* :ghissue:`3067`: Crash with pygit2 -* :ghissue:`3061`: Bug handling Ellipsis -* :ghissue:`3049`: NB css inconsistent behavior between ff and webkit -* :ghissue:`3039`: unicode errors when opening a new notebook -* :ghissue:`3048`: Installning ipython qtConsole should be easyer att Windows -* :ghissue:`3042`: Profile creation fails on 0.13.2 branch -* :ghissue:`3035`: docstring typo/inconsistency: mention of an xml notebook format? -* :ghissue:`3031`: HDF5 library segfault (possibly due to mismatching headers?) -* :ghissue:`2991`: In notebook importing sympy closes ipython kernel -* :ghissue:`3027`: f.__globals__ causes an error in Python 3.3 -* :ghissue:`3020`: Failing test test_interactiveshell.TestAstTransform on Windows -* :ghissue:`3023`: alt text for "click to expand output" has typo in alt text -* :ghissue:`2963`: %history to print all input history of a previous session when line range is omitted -* :ghissue:`3018`: IPython installed within virtualenv. WARNING "Please install IPython inside the virtualtenv" -* :ghissue:`2484`: Completion in Emacs *Python* buffer causes prompt to be increased. -* :ghissue:`3014`: Ctrl-C finishes notebook immediately -* :ghissue:`3007`: cython_pyximport reload broken in python3 -* :ghissue:`2955`: Incompatible Qt imports when running inprocess_qtconsole -* :ghissue:`3006`: [IPython 0.13.1] The check of PyQt version is wrong -* :ghissue:`3005`: Renaming a notebook to an existing notebook name overwrites the other file -* :ghissue:`2940`: Abort trap in IPython Notebook after installing matplotlib -* :ghissue:`3000`: issue #3000 -* :ghissue:`2995`: ipython_directive.py fails on multiline when prompt number < 100 -* :ghissue:`2993`: File magic (%%file) does not work with paths beginning with tilde (e.g., ~/anaconda/stuff.txt) -* :ghissue:`2992`: Cell-based input for console and qt frontends? -* :ghissue:`2425`: Liaise with Spyder devs to integrate newer IPython -* :ghissue:`2986`: requesting help in a loop can damage a notebook -* :ghissue:`2978`: v1.0-dev build errors on Arch with Python 3. -* :ghissue:`2557`: [refactor] Insert_cell_at_index() -* :ghissue:`2969`: ipython command does not work in terminal -* :ghissue:`2762`: OSX wxPython (osx_cocoa, 64bit) command "%gui wx" blocks the interpreter -* :ghissue:`2956`: Silent importing of submodules differs from standard Python3.2 interpreter's behavior -* :ghissue:`2943`: Up arrow key history search gets stuck in QTConsole -* :ghissue:`2953`: using 'nonlocal' declaration in global scope causes ipython3 crash -* :ghissue:`2952`: qtconsole ignores exec_lines -* :ghissue:`2949`: ipython crashes due to atexit() -* :ghissue:`2947`: From rmagic to an R console -* :ghissue:`2938`: docstring pane not showing in notebook -* :ghissue:`2936`: Tornado assumes invalid signature for parse_qs on Python 3.1 -* :ghissue:`2935`: unable to find python after easy_install / pip install -* :ghissue:`2920`: Add undo-cell deletion menu -* :ghissue:`2914`: BUG:saving a modified .py file after loading a module kills the kernel -* :ghissue:`2925`: BUG: kernel dies if user sets sys.stderr or sys.stdout to a file object -* :ghissue:`2909`: LaTeX sometimes fails to render in markdown cells with some curly bracket + underscore combinations -* :ghissue:`2898`: Skip ipc tests on Windows -* :ghissue:`2902`: ActiveState attempt to build ipython 0.12.1 for python 3.2.2 for Mac OS failed -* :ghissue:`2899`: Test failure in IPython.core.tests.test_magic.test_time -* :ghissue:`2890`: Test failure when fabric not installed -* :ghissue:`2892`: IPython tab completion bug for paths -* :ghissue:`1340`: Allow input cells to be collapsed -* :ghissue:`2881`: ? command in notebook does not show help in Safari -* :ghissue:`2751`: %%timeit should use minutes to format running time in long running cells -* :ghissue:`2879`: When importing a module with a wrong name, ipython crashes -* :ghissue:`2862`: %%timeit should warn of empty contents -* :ghissue:`2485`: History navigation breaks in qtconsole -* :ghissue:`2785`: gevent input hook -* :ghissue:`2843`: Sliently running code in clipboard (with paste, cpaste and variants) -* :ghissue:`2784`: %run -t -N error -* :ghissue:`2732`: Test failure with FileLinks class on Windows -* :ghissue:`2860`: ipython help notebook -> KeyError: 'KernelManager' -* :ghissue:`2858`: Where is the installed `ipython` script? -* :ghissue:`2856`: Edit code entered from ipython in external editor -* :ghissue:`2722`: IPC transport option not taking effect ? -* :ghissue:`2473`: Better error messages in ipengine/ipcontroller -* :ghissue:`2836`: Cannot send builtin module definitions to IP engines -* :ghissue:`2833`: Any reason not to use super() ? -* :ghissue:`2781`: Cannot interrupt infinite loops in the notebook -* :ghissue:`2150`: clippath_demo.py in matplotlib example does not work with inline backend -* :ghissue:`2634`: Numbered list in notebook markdown cell renders with Roman numerals instead of numbers -* :ghissue:`2230`: IPython crashing during startup with "AttributeError: 'NoneType' object has no attribute 'rstrip'" -* :ghissue:`2483`: nbviewer bug? with multi-file gists -* :ghissue:`2466`: mistyping `ed -p` breaks `ed -p` -* :ghissue:`2477`: Glob expansion tests fail on Windows -* :ghissue:`2622`: doc issue: notebooks that ship with Ipython .13 are written for python 2.x -* :ghissue:`2626`: Add "Cell -> Run All Keep Going" for notebooks -* :ghissue:`1223`: Show last modification date of each notebook -* :ghissue:`2621`: user request: put link to example notebooks in Dashboard -* :ghissue:`2564`: grid blanks plots in ipython pylab inline mode (interactive) -* :ghissue:`2532`: Django shell (IPython) gives NameError on dict comprehensions -* :ghissue:`2188`: ipython crashes on ctrl-c -* :ghissue:`2391`: Request: nbformat API to load/save without changing version -* :ghissue:`2355`: Restart kernel message even though kernel is perfectly alive -* :ghissue:`2306`: Garbled input text after reverse search on Mac OS X -* :ghissue:`2297`: ipdb with separate kernel/client pushing stdout to kernel process only -* :ghissue:`2180`: Have [kernel busy] overridden only by [kernel idle] -* :ghissue:`1188`: Pylab with OSX backend keyboard focus issue and hang -* :ghissue:`2107`: test_octavemagic.py[everything] fails -* :ghissue:`1212`: Better understand/document browser compatibility -* :ghissue:`1585`: Refactor notebook templates to use Jinja2 and make each page a separate directory -* :ghissue:`1443`: xticks scaling factor partially obscured with qtconsole and inline plotting -* :ghissue:`1209`: can't make %result work as in doc. -* :ghissue:`1200`: IPython 0.12 Windows install fails on Vista -* :ghissue:`1127`: Interactive test scripts for Qt/nb issues -* :ghissue:`959`: Matplotlib figures hide -* :ghissue:`2071`: win32 installer issue on Windows XP -* :ghissue:`2610`: ZMQInteractiveShell.colors being ignored -* :ghissue:`2505`: Markdown Cell incorrectly highlighting after "<" -* :ghissue:`165`: Installer fails to create Start Menu entries on Windows -* :ghissue:`2356`: failing traceback in terminal ipython for first exception -* :ghissue:`2145`: Have dashboad show when server disconect -* :ghissue:`2098`: Do not crash on kernel shutdow if json file is missing -* :ghissue:`2813`: Offline MathJax is broken on 0.14dev -* :ghissue:`2807`: Test failure: IPython.parallel.tests.test_client.TestClient.test_purge_everything -* :ghissue:`2486`: Readline's history search in ipython console does not clear properly after cancellation with Ctrl+C -* :ghissue:`2709`: Cython -la doesn't work -* :ghissue:`2767`: What is IPython.utils.upgradedir ? -* :ghissue:`2210`: Placing matplotlib legend outside axis bounds causes inline display to clip it -* :ghissue:`2553`: IPython Notebooks not robust against client failures -* :ghissue:`2536`: ImageDraw in Ipython notebook not drawing lines -* :ghissue:`2264`: Feature request: Versioning messaging protocol -* :ghissue:`2589`: Creation of ~300+ MPI-spawned engines causes instability in ipcluster -* :ghissue:`2672`: notebook: inline option without pylab -* :ghissue:`2673`: Indefinite Articles & Traitlets -* :ghissue:`2705`: Notebook crashes Safari with select and drag -* :ghissue:`2721`: dreload kills ipython when it hits zmq -* :ghissue:`2806`: ipython.parallel doesn't discover globals under Python 3.3 -* :ghissue:`2794`: _exit_code behaves differently in terminal vs ZMQ frontends -* :ghissue:`2793`: IPython.parallel issue with pushing pandas TimeSeries -* :ghissue:`1085`: In process kernel for Qt frontend -* :ghissue:`2760`: IndexError: list index out of range with Python 3.2 -* :ghissue:`2780`: Save and load notebooks from github -* :ghissue:`2772`: AttributeError: 'Client' object has no attribute 'kill' -* :ghissue:`2754`: Fail to send class definitions from interactive session to engines namespaces -* :ghissue:`2764`: TypeError while using 'cd' -* :ghissue:`2765`: name '__file__' is not defined -* :ghissue:`2540`: Wrap tooltip if line exceeds threshold? -* :ghissue:`2394`: Startup error on ipython qtconsole (version 0.13 and 0.14-dev -* :ghissue:`2440`: IPEP 4: Python 3 Compatibility -* :ghissue:`1814`: __file__ is not defined when file end with .ipy -* :ghissue:`2759`: R magic extension interferes with tab completion -* :ghissue:`2615`: Small change needed to rmagic extension. -* :ghissue:`2748`: collapse parts of a html notebook -* :ghissue:`1661`: %paste still bugs about IndentationError and says to use %paste -* :ghissue:`2742`: Octavemagic fails to deliver inline images in IPython (on Windows) -* :ghissue:`2739`: wiki.ipython.org contaminated with prescription drug spam -* :ghissue:`2588`: Link error while executing code from cython example notebook -* :ghissue:`2550`: Rpush magic doesn't find local variables and doesn't support comma separated lists of variables -* :ghissue:`2675`: Markdown/html blockquote need css. -* :ghissue:`2419`: TerminalInteractiveShell.__init__() ignores value of ipython_dir argument -* :ghissue:`1523`: Better LaTeX printing in the qtconsole with the sympy profile -* :ghissue:`2719`: ipython fails with `pkg_resources.DistributionNotFound: ipython==0.13` -* :ghissue:`2715`: url crashes nbviewer.ipython.org -* :ghissue:`2555`: "import" module completion on MacOSX -* :ghissue:`2707`: Problem installing the new version of IPython in Windows -* :ghissue:`2696`: SymPy magic bug in IPython Notebook -* :ghissue:`2684`: pretty print broken for types created with PyType_FromSpec -* :ghissue:`2533`: rmagic breaks on Windows -* :ghissue:`2661`: Qtconsole tooltip is too wide when the function has many arguments -* :ghissue:`2679`: ipython3 qtconsole via Homebrew on Mac OS X 10.8 - pyqt/pyside import error -* :ghissue:`2646`: pylab_not_importable -* :ghissue:`2587`: cython magic pops 2 CLI windows upon execution on Windows -* :ghissue:`2660`: Certain arguments (-h, --help, --version) never passed to scripts run with ipython -* :ghissue:`2665`: Missing docs for rmagic and some other extensions -* :ghissue:`2611`: Travis wants to drop 3.1 support -* :ghissue:`2658`: Incorrect parsing of raw multiline strings -* :ghissue:`2655`: Test fails if `from __future__ import print_function` in .pythonrc.py -* :ghissue:`2651`: nonlocal with no existing variable produces too many errors -* :ghissue:`2645`: python3 is a pain (minor unicode bug) -* :ghissue:`2637`: %paste in Python 3 on Mac doesn't work -* :ghissue:`2624`: Error on launching IPython on Win 7 and Python 2.7.3 -* :ghissue:`2608`: disk IO activity on cursor press -* :ghissue:`1275`: Markdown parses LaTeX math symbols as its formatting syntax in notebook -* :ghissue:`2613`: display(Math(...)) doesn't render \tau correctly -* :ghissue:`925`: Tab-completion in Qt console needn't use pager -* :ghissue:`2607`: %load_ext sympy.interactive.ipythonprinting dammaging output -* :ghissue:`2593`: Toolbar button to open qtconsole from notebook -* :ghissue:`2602`: IPython html documentation for downloading -* :ghissue:`2598`: ipython notebook --pylab=inline replaces built-in any() -* :ghissue:`2244`: small issue: wrong printout -* :ghissue:`2590`: add easier way to execute scripts in the current directory -* :ghissue:`2581`: %hist does not work when InteractiveShell.cache_size = 0 -* :ghissue:`2584`: No file COPYING -* :ghissue:`2578`: AttributeError: 'module' object has no attribute 'TestCase' -* :ghissue:`2576`: One of my notebooks won't load any more -- is there a maximum notebook size? -* :ghissue:`2560`: Notebook output is invisible when printing strings with \r\r\n line endings -* :ghissue:`2566`: if pyside partially present ipython qtconsole fails to load even if pyqt4 present -* :ghissue:`1308`: ipython qtconsole --ssh=server --existing ... hangs -* :ghissue:`1679`: List command doesn't work in ipdb debugger the first time -* :ghissue:`2545`: pypi win32 installer creates 64bit executibles -* :ghissue:`2080`: Event loop issues with IPython 0.12 and PyQt4 (``QDialog.exec_`` and more) -* :ghissue:`2541`: Allow `python -m IPython` -* :ghissue:`2508`: subplots_adjust() does not work correctly in ipython notebook -* :ghissue:`2289`: Incorrect mathjax rendering of certain arrays of equations -* :ghissue:`2487`: Selecting and indenting -* :ghissue:`2521`: more fine-grained 'run' controls, such as 'run from here' and 'run until here' -* :ghissue:`2535`: Funny bounding box when plot with text -* :ghissue:`2523`: History not working -* :ghissue:`2514`: Issue with zooming in qtconsole -* :ghissue:`2220`: No sys.stdout.encoding in kernel based IPython -* :ghissue:`2512`: ERROR: Internal Python error in the inspect module. -* :ghissue:`2496`: Function passwd does not work in QtConsole -* :ghissue:`1453`: make engines reconnect/die when controller was restarted -* :ghissue:`2481`: ipython notebook -- clicking in a code cell's output moves the screen to the top of the code cell -* :ghissue:`2488`: Undesired plot outputs in Notebook inline mode -* :ghissue:`2482`: ipython notebook -- download may not get the latest notebook -* :ghissue:`2471`: _subprocess module removed in Python 3.3 -* :ghissue:`2374`: Issues with man pages -* :ghissue:`2316`: parallel.Client.__init__ should take cluster_id kwarg -* :ghissue:`2457`: Can a R library wrapper be created with Rmagic? -* :ghissue:`1575`: Fallback frontend for console when connecting pylab=inlnie -enabled kernel? -* :ghissue:`2097`: Do not crash if history db is corrupted -* :ghissue:`2435`: ipengines fail if clean_logs enabled -* :ghissue:`2429`: Using warnings.warn() results in TypeError -* :ghissue:`2422`: Multiprocessing in ipython notebook kernel crash -* :ghissue:`2426`: ipython crashes with the following message. I do not what went wrong. Can you help me identify the problem? -* :ghissue:`2423`: Docs typo? -* :ghissue:`2257`: pip install -e fails -* :ghissue:`2418`: rmagic can't run R's read.csv on data files with NA data -* :ghissue:`2417`: HTML notebook: Backspace sometimes deletes multiple characters -* :ghissue:`2275`: notebook: "Down_Arrow" on last line of cell should move to end of line -* :ghissue:`2414`: 0.13.1 does not work with current EPD 7.3-2 -* :ghissue:`2409`: there is a redundant None -* :ghissue:`2410`: Use /usr/bin/python3 instead of /usr/bin/python -* :ghissue:`2366`: Notebook Dashboard --notebook-dir and fullpath -* :ghissue:`2406`: Inability to get docstring in debugger -* :ghissue:`2398`: Show line number for IndentationErrors -* :ghissue:`2314`: HTML lists seem to interfere with the QtConsole display -* :ghissue:`1688`: unicode exception when using %run with failing script -* :ghissue:`1884`: IPython.embed changes color on error -* :ghissue:`2381`: %time doesn't work for multiline statements -* :ghissue:`1435`: Add size keywords in Image class -* :ghissue:`2372`: interactiveshell.py misses urllib and io_open imports -* :ghissue:`2371`: IPython not working -* :ghissue:`2367`: Tab expansion moves to next cell in notebook -* :ghissue:`2359`: nbviever alters the order of print and display() output -* :ghissue:`2227`: print name for IPython Notebooks has become uninformative -* :ghissue:`2361`: client doesn't use connection file's 'location' in disambiguating 'interface' -* :ghissue:`2357`: failing traceback in terminal ipython for first exception -* :ghissue:`2343`: Installing in a python 3.3b2 or python 3.3rc1 virtual environment. -* :ghissue:`2315`: Failure in test: "Test we're not loading modules on startup that we shouldn't." -* :ghissue:`2351`: Multiple Notebook Apps: cookies not port specific, clash with each other -* :ghissue:`2350`: running unittest from qtconsole prints output to terminal -* :ghissue:`2303`: remote tracebacks broken since 952d0d6 (PR #2223) -* :ghissue:`2330`: qtconsole does not highlight tab-completion suggestion with custom stylesheet -* :ghissue:`2325`: Parsing Tex formula fails in Notebook -* :ghissue:`2324`: Parsing Tex formula fails -* :ghissue:`1474`: Add argument to `run -n` for custom namespace -* :ghissue:`2318`: C-m n/p don't work in Markdown cells in the notebook -* :ghissue:`2309`: time.time() in ipython notebook producing impossible results -* :ghissue:`2307`: schedule tasks on newly arrived engines -* :ghissue:`2313`: Allow Notebook HTML/JS to send messages to Python code -* :ghissue:`2304`: ipengine throws KeyError: url -* :ghissue:`1878`: shell access using ! will not fill class or function scope vars -* :ghissue:`2253`: %paste does not retrieve clipboard contents under screen/tmux on OS X -* :ghissue:`1510`: Add-on (or Monkey-patch) infrastructure for HTML notebook -* :ghissue:`2273`: triple quote and %s at beginning of line with %paste -* :ghissue:`2243`: Regression in .embed() -* :ghissue:`2266`: SSH passwordless check with OpenSSH checks for the wrong thing -* :ghissue:`2217`: Change NewNotebook handler to use 30x redirect -* :ghissue:`2276`: config option for disabling history store -* :ghissue:`2239`: can't use parallel.Reference in view.map -* :ghissue:`2272`: Sympy piecewise messed up rendering -* :ghissue:`2252`: %paste throws an exception with empty clipboard -* :ghissue:`2259`: git-mpr is currently broken -* :ghissue:`2247`: Variable expansion in shell commands should work in substrings -* :ghissue:`2026`: Run 'fast' tests only -* :ghissue:`2241`: read a list of notebooks on server and bring into browser only notebook -* :ghissue:`2237`: please put python and text editor in the web only ipython -* :ghissue:`2053`: Improvements to the IPython.display.Image object -* :ghissue:`1456`: ERROR: Internal Python error in the inspect module. -* :ghissue:`2221`: Avoid importing from IPython.parallel in core -* :ghissue:`2213`: Can't trigger startup code in Engines -* :ghissue:`1464`: Strange behavior for backspace with lines ending with more than 4 spaces in notebook -* :ghissue:`2187`: NaN in object_info_reply JSON causes parse error -* :ghissue:`214`: system command requiring administrative privileges -* :ghissue:`2195`: Unknown option `no-edit` in git-mpr -* :ghissue:`2201`: Add documentation build to tools/test_pr.py -* :ghissue:`2205`: Command-line option for default Notebook output collapsing behavior -* :ghissue:`1927`: toggle between inline and floating figures -* :ghissue:`2171`: Can't start StarCluster after upgrading to IPython 0.13 -* :ghissue:`2173`: oct2py v >= 0.3.1 doesn't need h5py anymore -* :ghissue:`2099`: storemagic needs to use self.shell -* :ghissue:`2166`: DirectView map_sync() with Lambdas Using Generators -* :ghissue:`2091`: Unable to use print_stats after %prun -r in notebook -* :ghissue:`2132`: Add fail-over for pastebin -* :ghissue:`2156`: Make it possible to install ipython without nasty gui dependencies -* :ghissue:`2154`: Scrolled long output should be off in print view by default -* :ghissue:`2162`: Tab completion does not work with IPython.embed_kernel() -* :ghissue:`2157`: IPython 0.13 / github-master cannot create logfile from scratch -* :ghissue:`2151`: missing newline when a magic is called from the qtconsole menu -* :ghissue:`2139`: 00_notebook_tour Image example broken on master -* :ghissue:`2143`: Add a %%cython_annotate magic -* :ghissue:`2135`: Running IPython from terminal -* :ghissue:`2093`: Makefile for building Sphinx documentation on Windows -* :ghissue:`2122`: Bug in pretty printing -* :ghissue:`2120`: Notebook "Make a Copy..." keeps opening duplicates in the same tab -* :ghissue:`1997`: password cannot be used with url prefix -* :ghissue:`2129`: help/doc displayed multiple times if requested in loop -* :ghissue:`2121`: ipdb does not support input history in qtconsole -* :ghissue:`2114`: %logstart doesn't log -* :ghissue:`2085`: %ed magic fails in qtconsole -* :ghissue:`2119`: IPython fails to run on MacOS Lion -* :ghissue:`2052`: %pylab inline magic does not work on windows -* :ghissue:`2111`: Ipython won't start on W7 -* :ghissue:`2112`: Strange internal traceback -* :ghissue:`2108`: Backslash (\) at the end of the line behavior different from default Python -* :ghissue:`1425`: Ampersands can't be typed sometimes in notebook cells -* :ghissue:`1513`: Add expand/collapse support for long output elements like stdout and tracebacks -* :ghissue:`2087`: error when starting ipython -* :ghissue:`2103`: Ability to run notebook file from commandline -* :ghissue:`2082`: Qt Console output spacing -* :ghissue:`2083`: Test failures with Python 3.2 and PYTHONWARNINGS="d" -* :ghissue:`2094`: about inline -* :ghissue:`2077`: Starting IPython3 on the terminal -* :ghissue:`1760`: easy_install ipython fails on py3.2-win32 -* :ghissue:`2075`: Local Mathjax install causes iptest3 error under python3 -* :ghissue:`2057`: setup fails for python3 with LANG=C -* :ghissue:`2070`: shebang on Windows -* :ghissue:`2054`: sys_info missing git hash in sdists -* :ghissue:`2059`: duplicate and modified files in documentation -* :ghissue:`2056`: except-shadows-builtin osm.py:687 -* :ghissue:`2058`: hyphen-used-as-minus-sign in manpages diff --git a/docs/source/whatsnew/github-stats-2.0.rst b/docs/source/whatsnew/github-stats-2.0.rst deleted file mode 100644 index 5033994e1e1..00000000000 --- a/docs/source/whatsnew/github-stats-2.0.rst +++ /dev/null @@ -1,1607 +0,0 @@ -.. _issues_list_200: - -Issues closed in the 2.x development cycle -========================================== - -Issues closed in 2.4.1 ----------------------- - -GitHub stats for 2014/11/01 - 2015/01/30 - -.. note:: - - IPython 2.4.0 was released without a few of the backports listed below. - 2.4.1 has the correct patches intended for 2.4.0. - -These lists are automatically generated, and may be incomplete or contain duplicates. - -The following 7 authors contributed 35 commits. - -* Benjamin Ragan-Kelley -* Carlos Cordoba -* Damon Allen -* Jessica B. Hamrick -* Mateusz Paprocki -* Peter Würtz -* Thomas Kluyver - -We closed 10 issues and merged 6 pull requests; -this is the full list (generated with the script -:file:`tools/github_stats.py`): - -Pull Requests (10): - -* :ghpull:`7106`: Changed the display order of rich output in the live notebook. -* :ghpull:`6878`: Update pygments monkeypatch for compatibility with Pygments 2.0 -* :ghpull:`6778`: backport nbformat v4 to 2.x -* :ghpull:`6761`: object_info_reply field is oname, not name -* :ghpull:`6653`: Fix IPython.utils.ansispan() to ignore stray [0m -* :ghpull:`6706`: Correctly display prompt numbers that are ``None`` -* :ghpull:`6634`: don't use contains in SelectWidget item_query -* :ghpull:`6593`: note how to start the qtconsole -* :ghpull:`6281`: more minor fixes to release scripts -* :ghpull:`5458`: Add support for PyQt5. - -Issues (6): - -* :ghissue:`7272`: qtconsole problems with pygments -* :ghissue:`7049`: Cause TypeError: 'NoneType' object is not callable in qtconsole -* :ghissue:`6877`: Qt console doesn't work with pygments 2.0rc1 -* :ghissue:`6689`: Problem with string containing two or more question marks -* :ghissue:`6702`: Cell numbering after ``ClearOutput`` preprocessor -* :ghissue:`6633`: selectwidget doesn't display 1 as a selection choice when passed in as a member of values list - - -Issues closed in 2.3.1 ----------------------- - -Just one bugfix: fixed bad CRCRLF line-endings in notebooks on Windows - -Pull Requests (1): - -* :ghpull:`6911`: don't use text mode in mkstemp - -Issues (1): - -* :ghissue:`6599`: Notebook.ipynb CR+LF turned into CR+CR+LF - - -Issues closed in 2.3.0 ----------------------- - -GitHub stats for 2014/08/06 - 2014/10/01 - -These lists are automatically generated, and may be incomplete or contain duplicates. - -The following 6 authors contributed 31 commits. - -* Benjamin Ragan-Kelley -* David Hirschfeld -* Eric Firing -* Jessica B. Hamrick -* Matthias Bussonnier -* Thomas Kluyver - -We closed 16 issues and merged 9 pull requests; -this is the full list (generated with the script -:file:`tools/github_stats.py`): - -Pull Requests (16): - -* :ghpull:`6587`: support ``%matplotlib qt5`` and ``%matplotlib nbagg`` -* :ghpull:`6583`: Windows symlink test fixes -* :ghpull:`6585`: fixes :ghissue:`6473` -* :ghpull:`6581`: Properly mock winreg functions for test -* :ghpull:`6556`: Use some more informative asserts in inprocess kernel tests -* :ghpull:`6514`: Fix for copying metadata flags -* :ghpull:`6453`: Copy file metadata in atomic save -* :ghpull:`6480`: only compare host:port in Websocket.check_origin -* :ghpull:`6483`: Trim anchor link in heading cells, fixes :ghissue:`6324` -* :ghpull:`6410`: Fix relative import in appnope -* :ghpull:`6395`: update mathjax CDN url in nbconvert template -* :ghpull:`6269`: Implement atomic save -* :ghpull:`6374`: Rename ``abort_queues`` --> ``_abort_queues`` -* :ghpull:`6321`: Use appnope in qt and wx gui support from the terminal; closes :ghissue:`6189` -* :ghpull:`6318`: use write_error instead of get_error_html -* :ghpull:`6303`: Fix error message when failing to load a notebook - -Issues (9): - -* :ghissue:`6057`: ``%matplotlib`` + qt5 -* :ghissue:`6518`: Test failure in atomic save on Windows -* :ghissue:`6473`: Switching between "Raw Cell Format" and "Edit Metadata" does not work -* :ghissue:`6405`: Creating a notebook should respect directory permissions; saving should respect prior permissions -* :ghissue:`6324`: Anchors in Heading don't work. -* :ghissue:`6409`: No module named '_dummy' -* :ghissue:`6392`: Mathjax library link broken -* :ghissue:`6329`: IPython Notebook Server URL now requires "tree" at the end of the URL? (version 2.2) -* :ghissue:`6189`: ipython console freezes for increasing no of seconds in %pylab mode - -Issues closed in 2.2.0 ----------------------- - -GitHub stats for 2014/05/21 - 2014/08/06 (tag: rel-2.1.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -The following 13 authors contributed 36 commits. - -* Adam Hodgen -* Benjamin Ragan-Kelley -* Björn Grüning -* Dara Adib -* Eric Galloway -* Jonathan Frederic -* Kyle Kelley -* Matthias Bussonnier -* Paul Ivanov -* Shayne Hodge -* Steven Anton -* Thomas Kluyver -* Zahari - -We closed 23 issues and merged 11 pull requests; -this is the full list (generated with the script -:file:`tools/github_stats.py`): - -Pull Requests (23): - -* :ghpull:`6279`: minor updates to release scripts -* :ghpull:`6273`: Upgrade default mathjax version. -* :ghpull:`6249`: always use HTTPS getting mathjax from CDN -* :ghpull:`6114`: update hmac signature comparison -* :ghpull:`6195`: Close handle on new temporary files before returning filename -* :ghpull:`6143`: pin tornado to < 4 on travis js tests -* :ghpull:`6134`: remove rackcdn https workaround for mathjax cdn -* :ghpull:`6120`: Only allow iframe embedding on same origin. -* :ghpull:`6117`: Remove / from route of TreeRedirectHandler. -* :ghpull:`6105`: only set allow_origin_pat if defined -* :ghpull:`6102`: Add newline if missing to end of script magic cell -* :ghpull:`6077`: allow unicode keys in dicts in json_clean -* :ghpull:`6061`: make CORS configurable -* :ghpull:`6081`: don’t modify dict keys while iterating through them -* :ghpull:`5803`: unify visual line handling -* :ghpull:`6005`: Changed right arrow key movement function to mirror left arrow key -* :ghpull:`6029`: add pickleutil.PICKLE_PROTOCOL -* :ghpull:`6003`: Set kernel_id before checking websocket -* :ghpull:`5994`: Fix ssh tunnel for Python3 -* :ghpull:`5973`: Do not create checkpoint_dir relative to current dir -* :ghpull:`5933`: fix qt_loader import hook signature -* :ghpull:`5944`: Markdown rendering bug fix. -* :ghpull:`5917`: use shutil.move instead of os.rename - -Issues (11): - -* :ghissue:`6246`: Include MathJax by default or access the CDN over a secure connection -* :ghissue:`5525`: Websocket origin check fails when used with Apache WS proxy -* :ghissue:`5901`: 2 test failures in Python 3.4 in parallel group -* :ghissue:`5926`: QT console: text selection cannot be made from left to right with keyboard -* :ghissue:`5998`: use_dill does not work in Python 3.4 -* :ghissue:`5964`: Traceback on Qt console exit -* :ghissue:`5787`: Error in Notebook-Generated latex (nbconvert) -* :ghissue:`5950`: qtconsole truncates help -* :ghissue:`5943`: 2.x: notebook fails to load when using HTML comments -* :ghissue:`5932`: Qt ImportDenier Does Not Adhere to PEP302 -* :ghissue:`5898`: OSError when moving configuration file - -Issues closed in 2.1.0 ----------------------- - -GitHub stats for 2014/04/02 - 2014/05/21 (since 2.0.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -The following 35 authors contributed 145 commits. - -* Adrian Price-Whelan -* Aron Ahmadia -* Benjamin Ragan-Kelley -* Benjamin Schultz -* Björn Linse -* Blake Griffith -* chebee7i -* Damián Avila -* Dav Clark -* dexterdev -* Erik Tollerud -* Grzegorz Rożniecki -* Jakob Gager -* jdavidheiser -* Jessica B. Hamrick -* Jim Garrison -* Jonathan Frederic -* Matthias Bussonnier -* Maximilian Albert -* Mohan Raj Rajamanickam -* ncornette -* Nikolay Koldunov -* Nile Geisinger -* Pankaj Pandey -* Paul Ivanov -* Pierre Haessig -* Raffaele De Feo -* Renaud Richardet -* Spencer Nelson -* Steve Chan -* sunny -* Susan Tan -* Thomas Kluyver -* Yaroslav Halchenko -* zah - -We closed a total of 129 issues, 92 pull requests and 37 regular issues; -this is the full list (generated with the script -:file:`tools/github_stats.py --milestone 2.1`): - -Pull Requests (92): - -* :ghpull:`5871`: specify encoding in msgpack.unpackb -* :ghpull:`5869`: Catch more errors from clipboard access on Windows -* :ghpull:`5866`: Make test robust against differences in line endings -* :ghpull:`5605`: Two cell toolbar fixes. -* :ghpull:`5843`: remove Firefox-specific CSS workaround -* :ghpull:`5845`: Pass Windows interrupt event to kernels as an environment variable -* :ghpull:`5835`: fix typo in v2 convert -* :ghpull:`5841`: Fix writing history with output to a file in Python 2 -* :ghpull:`5842`: fix typo in nbconvert help -* :ghpull:`5846`: Fix typos in Cython example -* :ghpull:`5839`: Close graphics dev in finally clause -* :ghpull:`5837`: pass on install docs -* :ghpull:`5832`: Fixed example to work with python3 -* :ghpull:`5826`: allow notebook tour instantiation to fail -* :ghpull:`5560`: Minor expansion of Cython example -* :ghpull:`5818`: interpret any exception in getcallargs as not callable -* :ghpull:`5816`: Add output to IPython directive when in verbatim mode. -* :ghpull:`5822`: Don't overwrite widget description in interact -* :ghpull:`5782`: Silence exception thrown by completer when dir() does not return a list -* :ghpull:`5807`: Drop log level to info for Qt console shutdown -* :ghpull:`5814`: Remove -i options from mv, rm and cp aliases -* :ghpull:`5812`: Fix application name when printing subcommand help. -* :ghpull:`5804`: remove an inappropriate ``!`` -* :ghpull:`5805`: fix engine startup files -* :ghpull:`5806`: Don't auto-move .config/ipython if symbolic link -* :ghpull:`5716`: Add booktabs package to latex base.tplx -* :ghpull:`5669`: allows threadsafe sys.stdout.flush from background threads -* :ghpull:`5668`: allow async output on the most recent request -* :ghpull:`5768`: fix cursor keys in long lines wrapped in markdown -* :ghpull:`5788`: run cells with ``silent=True`` in ``%run nb.ipynb`` -* :ghpull:`5715`: log all failed ajax API requests -* :ghpull:`5769`: Don't urlescape the text that goes into a title tag -* :ghpull:`5762`: Fix check for pickling closures -* :ghpull:`5766`: View.map with empty sequence should return empty list -* :ghpull:`5758`: Applied bug fix: using fc and ec did not properly set the figure canvas ... -* :ghpull:`5754`: Format command name into subcommand_description at run time, not import -* :ghpull:`5744`: Describe using PyPI/pip to distribute & install extensions -* :ghpull:`5712`: monkeypatch inspect.findsource only when we use it -* :ghpull:`5708`: create checkpoints dir in notebook subdirectories -* :ghpull:`5714`: log error message when API requests fail -* :ghpull:`5732`: Quick typo fix in nbformat/convert.py -* :ghpull:`5713`: Fix a NameError in IPython.parallel -* :ghpull:`5704`: Update nbconvertapp.py -* :ghpull:`5534`: cleanup some ``pre`` css inheritance -* :ghpull:`5699`: don't use common names in require decorators -* :ghpull:`5692`: Update notebook.rst fixing broken reference to notebook examples readme -* :ghpull:`5693`: Update parallel_intro.rst to fix a broken link to examples -* :ghpull:`5486`: disambiguate to location when no IPs can be determined -* :ghpull:`5574`: Remove the outdated keyboard shortcuts from notebook docs -* :ghpull:`5568`: Use ``__qualname__`` in pretty reprs for Python 3 -* :ghpull:`5678`: Fix copy & paste error in docstring of ImageWidget class -* :ghpull:`5677`: Fix %bookmark -l for Python 3 -* :ghpull:`5670`: nbconvert: Fix CWD imports -* :ghpull:`5647`: Mention git hooks in install documentation -* :ghpull:`5671`: Fix blank slides issue in Reveal slideshow pdf export -* :ghpull:`5657`: use 'localhost' as default for the notebook server -* :ghpull:`5584`: more semantic icons -* :ghpull:`5594`: update components with marked-0.3.2 -* :ghpull:`5500`: check for Python 3.2 -* :ghpull:`5582`: reset readline after running PYTHONSTARTUP -* :ghpull:`5630`: Fixed Issue :ghissue:`4012` Added Help menubar link to Github markdown doc -* :ghpull:`5613`: Fixing bug :ghissue:`5607` -* :ghpull:`5633`: Provide more help if lessc is not found. -* :ghpull:`5620`: fixed a typo in IPython.core.formatters -* :ghpull:`5619`: Fix typo in storemagic module docstring -* :ghpull:`5592`: add missing ``browser`` to notebook_aliases list -* :ghpull:`5506`: Fix ipconfig regex pattern -* :ghpull:`5581`: Fix rmagic for cells ending in comment. -* :ghpull:`5576`: only process cr if it's found -* :ghpull:`5478`: Add git-hooks install script. Update README.md -* :ghpull:`5546`: do not shutdown notebook if 'n' is part of answer -* :ghpull:`5527`: Don't remove upload items from nav tree unless explicitly requested. -* :ghpull:`5501`: remove inappropriate wheel tag override -* :ghpull:`5548`: FileNotebookManager: Use shutil.move() instead of os.rename() -* :ghpull:`5524`: never use ``for (var i in array)`` -* :ghpull:`5459`: Fix interact animation page jump FF -* :ghpull:`5559`: Minor typo fix in "Cython Magics.ipynb" -* :ghpull:`5507`: Fix typo in interactive widgets examples index notebook -* :ghpull:`5554`: Make HasTraits pickleable -* :ghpull:`5535`: fix n^2 performance issue in coalesce_streams preprocessor -* :ghpull:`5522`: fix iteration over Client -* :ghpull:`5488`: Added missing require and jquery from cdn. -* :ghpull:`5516`: ENH: list generated config files in generated, and rm them upon clean -* :ghpull:`5493`: made a minor fix to one of the widget examples -* :ghpull:`5512`: Update tooltips to refer to shift-tab -* :ghpull:`5505`: Make backport_pr work on Python 3 -* :ghpull:`5503`: check explicitly for 'dev' before adding the note to docs -* :ghpull:`5498`: use milestones to indicate backport -* :ghpull:`5492`: Polish whatsnew docs -* :ghpull:`5495`: Fix various broken things in docs -* :ghpull:`5496`: Exclude whatsnew/pr directory from docs builds -* :ghpull:`5489`: Fix required Python versions - -Issues (37): - -* :ghissue:`5364`: Horizontal scrollbar hides cell's last line on Firefox -* :ghissue:`5192`: horisontal scrollbar overlaps output or touches next cell -* :ghissue:`5840`: Third-party Windows kernels don't get interrupt signal -* :ghissue:`2412`: print history to file using qtconsole and notebook -* :ghissue:`5703`: Notebook doesn't render with "ask me every time" cookie setting in Firefox -* :ghissue:`5817`: calling mock object in IPython 2.0.0 under Python 3.4.0 raises AttributeError -* :ghissue:`5499`: Error running widgets nbconvert example -* :ghissue:`5654`: Broken links from ipython documentation -* :ghissue:`5019`: print in QT event callback doesn't show up in ipython notebook. -* :ghissue:`5800`: Only last In prompt number set ? -* :ghissue:`5801`: startup_command specified in ipengine_config.py is not executed -* :ghissue:`5690`: ipython 2.0.0 and pandoc 1.12.2.1 problem -* :ghissue:`5408`: Add checking/flushing of background output from kernel in mainloop -* :ghissue:`5407`: clearing message handlers on status=idle loses async output -* :ghissue:`5467`: Incorrect behavior of up/down keyboard arrows in code cells on wrapped lines -* :ghissue:`3085`: nicer notebook error message when lacking permissions -* :ghissue:`5765`: map_sync over empty list raises IndexError -* :ghissue:`5553`: Notebook matplotlib inline backend: can't set figure facecolor -* :ghissue:`5710`: inspect.findsource monkeypatch raises wrong exception for C extensions -* :ghissue:`5706`: Multi-Directory notebooks overwrite each other's checkpoints -* :ghissue:`5698`: can't require a function named ``f`` -* :ghissue:`5569`: Keyboard shortcuts in documentation are out of date -* :ghissue:`5566`: Function name printing should use ``__qualname__`` instead of ``__name__`` (Python 3) -* :ghissue:`5676`: "bookmark -l" not working in ipython 2.0 -* :ghissue:`5555`: Differentiate more clearly between Notebooks and Folders in new UI -* :ghissue:`5590`: Marked double escape -* :ghissue:`5514`: import tab-complete fail with ipython 2.0 shell -* :ghissue:`4012`: Notebook: link to markdown formatting reference -* :ghissue:`5611`: Typo in 'storemagic' documentation -* :ghissue:`5589`: Kernel start fails when using --browser argument -* :ghissue:`5491`: Bug in Windows ipconfig ip address regular expression -* :ghissue:`5579`: rmagic extension throws 'Error while parsing the string.' when last line is comment -* :ghissue:`5518`: Ipython2 will not open ipynb in example directory -* :ghissue:`5561`: New widget documentation has missing notebook link -* :ghissue:`5128`: Page jumping when output from widget interaction replaced -* :ghissue:`5519`: IPython.parallel.Client behavior as iterator -* :ghissue:`5510`: Tab-completion for function argument list - - -Issues closed in 2.0.0 ----------------------- - - -GitHub stats for 2013/08/09 - 2014/04/01 (since 1.0.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -The following 94 authors contributed 3949 commits. - -* Aaron Meurer -* Abhinav Upadhyay -* Adam Riggall -* Alex Rudy -* Andrew Mark -* Angus Griffith -* Antony Lee -* Aron Ahmadia -* Arun Persaud -* Benjamin Ragan-Kelley -* Bing Xia -* Blake Griffith -* Bouke van der Bijl -* Bradley M. Froehle -* Brian E. Granger -* Carlos Cordoba -* chapmanb -* chebee7i -* Christoph Gohlke -* Christophe Pradal -* Cyrille Rossant -* Damián Avila -* Daniel B. Vasquez -* Dav Clark -* David Hirschfeld -* David P. Sanders -* David Wyde -* David Österberg -* Doug Blank -* Dražen Lučanin -* epifanio -* Fernando Perez -* Gabriel Becker -* Geert Barentsen -* Hans Meine -* Ingolf Becker -* Jake Vanderplas -* Jakob Gager -* James Porter -* Jason Grout -* Jeffrey Tratner -* Jonah Graham -* Jonathan Frederic -* Joris Van den Bossche -* Juergen Hasch -* Julian Taylor -* Katie Silverio -* Kevin Burke -* Kieran O'Mahony -* Konrad Hinsen -* Kyle Kelley -* Lawrence Fu -* Marc Molla -* Martín Gaitán -* Matt Henderson -* Matthew Brett -* Matthias Bussonnier -* Michael Droettboom -* Mike McKerns -* Nathan Goldbaum -* Pablo de Oliveira -* Pankaj Pandey -* Pascal Schetelat -* Paul Ivanov -* Paul Moore -* Pere Vilas -* Peter Davis -* Philippe Mallet-Ladeira -* Preston Holmes -* Puneeth Chaganti -* Richard Everson -* Roberto Bonvallet -* Samuel Ainsworth -* Sean Vig -* Shashi Gowda -* Skipper Seabold -* Stephan Rave -* Steve Fox -* Steven Silvester -* stonebig -* Susan Tan -* Sylvain Corlay -* Takeshi Kanmae -* Ted Drain -* Thomas A Caswell -* Thomas Kluyver -* Théophile Studer -* Volker Braun -* Wieland Hoffmann -* Yaroslav Halchenko -* Yoval P. -* Yung Siang Liau -* Zachary Sailer -* zah - - -We closed a total of 1121 issues, 687 pull requests and 434 regular issues; -this is the full list (generated with the script -:file:`tools/github_stats.py`): - -Pull Requests (687): - -* :ghpull:`5487`: remove weird unicode space in the new copyright header -* :ghpull:`5476`: For 2.0: Fix links in Notebook Help Menu -* :ghpull:`5337`: Examples reorganization -* :ghpull:`5436`: CodeMirror shortcuts in QuickHelp -* :ghpull:`5444`: Fix numeric verification for Int and Float text widgets. -* :ghpull:`5449`: Stretch keyboard shortcut dialog -* :ghpull:`5473`: Minor corrections of git-hooks setup instructions -* :ghpull:`5471`: Add coding magic comment to nbconvert Python template -* :ghpull:`5452`: print_figure returns unicode for svg -* :ghpull:`5450`: proposal: remove codename -* :ghpull:`5462`: DOC : fixed minor error in using topological sort -* :ghpull:`5463`: make spin_thread tests more forgiving of slow VMs -* :ghpull:`5464`: Fix starting notebook server with file/directory at command line. -* :ghpull:`5453`: remove gitwash -* :ghpull:`5454`: Improve history API docs -* :ghpull:`5431`: update github_stats and gh_api for 2.0 -* :ghpull:`5290`: Add dual mode JS tests -* :ghpull:`5451`: check that a handler is actually registered in ShortcutManager.handles -* :ghpull:`5447`: Add %%python2 cell magic -* :ghpull:`5439`: Point to the stable SymPy docs, not the dev docs -* :ghpull:`5437`: Install jquery-ui images -* :ghpull:`5434`: fix check for empty cells in rst template -* :ghpull:`5432`: update links in notebook help menu -* :ghpull:`5435`: Update whatsnew (notebook tour) -* :ghpull:`5433`: Document extraction of octave and R magics -* :ghpull:`5428`: Update COPYING.txt -* :ghpull:`5426`: Separate get_session_info between HistoryAccessor and HistoryManager -* :ghpull:`5419`: move prompts from margin to main column on small screens -* :ghpull:`5430`: Make sure `element` is correct in the context of displayed JS -* :ghpull:`5396`: prevent saving of partially loaded notebooks -* :ghpull:`5429`: Fix tooltip pager feature -* :ghpull:`5330`: Updates to shell reference doc -* :ghpull:`5404`: Fix broken accordion widget -* :ghpull:`5339`: Don't use fork to start the notebook in js tests -* :ghpull:`5320`: Fix for Tooltip & completer click focus bug. -* :ghpull:`5421`: Move configuration of Python test controllers into setup() -* :ghpull:`5418`: fix typo in ssh launcher send_file -* :ghpull:`5403`: remove alt-- shortcut -* :ghpull:`5389`: better log message in deprecated files/ redirect -* :ghpull:`5333`: Fix filenbmanager.list_dirs fails for Windows user profile directory -* :ghpull:`5390`: finish PR #5333 -* :ghpull:`5326`: Some gardening on iptest result reporting -* :ghpull:`5375`: remove unnecessary onload hack from mathjax macro -* :ghpull:`5368`: Flexbox classes specificity fixes -* :ghpull:`5331`: fix raw_input CSS -* :ghpull:`5395`: urlencode images for rst files -* :ghpull:`5049`: update quickhelp on adding and removing shortcuts -* :ghpull:`5391`: Fix Gecko (Netscape) keyboard handling -* :ghpull:`5387`: Respect '\r' characters in nbconvert. -* :ghpull:`5399`: Revert PR #5388 -* :ghpull:`5388`: Suppress output even when a comment follows ;. Fixes #4525. -* :ghpull:`5394`: nbconvert doc update -* :ghpull:`5359`: do not install less sources -* :ghpull:`5346`: give hint on where to find custom.js -* :ghpull:`5357`: catch exception in copystat -* :ghpull:`5380`: Remove DefineShortVerb... line from latex base template -* :ghpull:`5376`: elide long containers in pretty -* :ghpull:`5310`: remove raw cell placeholder on focus, closes #5238 -* :ghpull:`5332`: semantic names for indicator icons -* :ghpull:`5386`: Fix import of socketserver on Python 3 -* :ghpull:`5360`: remove some redundant font-family: monospace -* :ghpull:`5379`: don't instantiate Application just for default logger -* :ghpull:`5372`: Don't autoclose strings -* :ghpull:`5296`: unify keyboard shortcut and codemirror interaction -* :ghpull:`5349`: Make Hub.registration_timeout configurable -* :ghpull:`5340`: install bootstrap-tour css -* :ghpull:`5335`: Update docstring for deepreload module -* :ghpull:`5321`: Improve assignment regex to match more tuple unpacking syntax -* :ghpull:`5325`: add NotebookNotary to NotebookApp's class list -* :ghpull:`5313`: avoid loading preprocessors twice -* :ghpull:`5308`: fix HTML capitalization in Highlight2HTML -* :ghpull:`5295`: OutputArea.append_type functions are not prototype methods -* :ghpull:`5318`: Fix local import of select_figure_formats -* :ghpull:`5300`: Fix NameError: name '_rl' is not defined -* :ghpull:`5292`: focus next cell on shift+enter -* :ghpull:`5291`: debug occasional error in test_queue_status -* :ghpull:`5289`: Finishing up #5274 (widget paths fixes) -* :ghpull:`5232`: Make nbconvert html full output like notebook's html. -* :ghpull:`5288`: Correct initial state of kernel status indicator -* :ghpull:`5253`: display any output from this session in terminal console -* :ghpull:`4802`: Tour of the notebook UI (was UI elements inline with highlighting) -* :ghpull:`5285`: Update signature presentation in pinfo classes -* :ghpull:`5268`: Refactoring Notebook.command_mode -* :ghpull:`5226`: Don't run PYTHONSTARTUP file if a file or code is passed -* :ghpull:`5283`: Remove Widget.closed attribute -* :ghpull:`5279`: nbconvert: Make sure node is atleast version 0.9.12 -* :ghpull:`5281`: fix a typo introduced by a rebased PR -* :ghpull:`5280`: append Firefox overflow-x fix -* :ghpull:`5277`: check that PIL can save JPEG to BytesIO -* :ghpull:`5044`: Store timestamps for modules to autoreload -* :ghpull:`5278`: Update whatsnew doc from pr files -* :ghpull:`5276`: Fix kernel restart in case connection file is deleted. -* :ghpull:`5272`: allow highlighting language to be set from notebook metadata -* :ghpull:`5158`: log refusal to serve hidden directories -* :ghpull:`5188`: New events system -* :ghpull:`5265`: Missing class def for TimeoutError -* :ghpull:`5267`: normalize unicode in notebook API tests -* :ghpull:`5076`: Refactor keyboard handling -* :ghpull:`5241`: Add some tests for utils -* :ghpull:`5261`: Don't allow edit mode up arrow to continue past index == 0 -* :ghpull:`5223`: use on-load event to trigger resizable images -* :ghpull:`5252`: make one strptime call at import of jsonutil -* :ghpull:`5153`: Dashboard sorting -* :ghpull:`5169`: Allow custom header -* :ghpull:`5242`: clear _reply_content cache before using it -* :ghpull:`5194`: require latex titles to be ascii -* :ghpull:`5244`: try to avoid EADDRINUSE errors on travis -* :ghpull:`5245`: support extracted output in HTML template -* :ghpull:`5209`: make input_area css generic to cells -* :ghpull:`5246`: less %pylab, more cowbell! -* :ghpull:`4895`: Improvements to %run completions -* :ghpull:`5243`: Add Javscript to base display priority list. -* :ghpull:`5175`: Audit .html() calls take #2 -* :ghpull:`5146`: Dual mode bug fixes. -* :ghpull:`5207`: Children fire event -* :ghpull:`5215`: Dashboard "Running" Tab -* :ghpull:`5240`: Remove unused IPython.nbconvert.utils.console module -* :ghpull:`5239`: Fix exclusion of tests directories from coverage reports -* :ghpull:`5203`: capture some logging/warning output in some tests -* :ghpull:`5216`: fixup positional arg handling in notebook app -* :ghpull:`5229`: get _ipython_display_ method safely -* :ghpull:`5234`: DOC : modified docs is HasTraits.traits and HasTraits.class_traits -* :ghpull:`5221`: Change widget children List to Tuple. -* :ghpull:`5231`: don't forget base_url when updating address bar in rename -* :ghpull:`5173`: Moved widget files into static/widgets/* -* :ghpull:`5222`: Unset PYTHONWARNINGS envvar before running subprocess tests. -* :ghpull:`5172`: Prevent page breaks when printing notebooks via print-view. -* :ghpull:`4985`: Add automatic Closebrackets function to Codemirror. -* :ghpull:`5220`: Make traitlets notify check more robust against classes redefining equality and bool -* :ghpull:`5197`: If there is an error comparing traitlet values when setting a trait, default to go ahead and notify of the new value. -* :ghpull:`5210`: fix pyreadline import in rlineimpl -* :ghpull:`5212`: Wrap nbconvert Markdown/Heading cells in live divs -* :ghpull:`5200`: Allow to pass option to jinja env -* :ghpull:`5202`: handle nodejs executable on debian -* :ghpull:`5112`: band-aid for completion -* :ghpull:`5187`: handle missing output metadata in nbconvert -* :ghpull:`5181`: use gnureadline on OS X -* :ghpull:`5136`: set default value from signature defaults in interact -* :ghpull:`5132`: remove application/pdf->pdf transform in javascript -* :ghpull:`5116`: reorganize who knows what about paths -* :ghpull:`5165`: Don't introspect __call__ for simple callables -* :ghpull:`5170`: Added msg_throttle sync=True widget traitlet -* :ghpull:`5191`: Translate markdown link to rst -* :ghpull:`5037`: FF Fix: alignment and scale of text widget -* :ghpull:`5179`: remove websocket url -* :ghpull:`5110`: add InlineBackend.print_figure_kwargs -* :ghpull:`5147`: Some template URL changes -* :ghpull:`5100`: remove base_kernel_url -* :ghpull:`5163`: Simplify implementation of TemporaryWorkingDirectory. -* :ghpull:`5166`: remove mktemp usage -* :ghpull:`5133`: don't use combine option on ucs package -* :ghpull:`5089`: Remove legacy azure nbmanager -* :ghpull:`5159`: remove append_json reference -* :ghpull:`5095`: handle image size metadata in nbconvert html -* :ghpull:`5156`: fix IPython typo, closes #5155 -* :ghpull:`5150`: fix a link that was broken -* :ghpull:`5114`: use non-breaking space for button with no description -* :ghpull:`4778`: add APIs for installing notebook extensions -* :ghpull:`5125`: Fix the display of functions with keyword-only arguments on Python 3. -* :ghpull:`5097`: minor notebook logging changes -* :ghpull:`5047`: only validate package_data when it might be used -* :ghpull:`5121`: fix remove event in KeyboardManager.register_events -* :ghpull:`5119`: Removed 'list' view from Variable Inspector example -* :ghpull:`4925`: Notebook manager api fixes -* :ghpull:`4996`: require print_method to be a bound method -* :ghpull:`5108`: require specifying the version for gh-pages -* :ghpull:`5111`: Minor typo in docstring of IPython.parallel DirectView -* :ghpull:`5098`: mostly debugging changes for IPython.parallel -* :ghpull:`5087`: trust cells with no output -* :ghpull:`5059`: Fix incorrect `Patch` logic in widget code -* :ghpull:`5075`: More flexible box model fixes -* :ghpull:`5091`: Provide logging messages in ipcluster log when engine or controllers fail to start -* :ghpull:`5090`: Print a warning when iptest is run from the IPython source directory -* :ghpull:`5077`: flush replies when entering an eventloop -* :ghpull:`5055`: Minimal changes to import IPython from IronPython -* :ghpull:`5078`: Updating JS tests README.md -* :ghpull:`5083`: don't create js test directories unless they are being used -* :ghpull:`5062`: adjust some events in nb_roundtrip -* :ghpull:`5043`: various unicode / url fixes -* :ghpull:`5066`: remove (almost) all mentions of pylab from our examples -* :ghpull:`4977`: ensure scp destination directories exist (with mkdir -p) -* :ghpull:`5053`: Move&rename JS tests -* :ghpull:`5067`: show traceback in widget handlers -* :ghpull:`4920`: Adding PDFFormatter and kernel side handling of PDF display data -* :ghpull:`5048`: Add edit/command mode indicator -* :ghpull:`5061`: make execute button in menu bar match shift-enter -* :ghpull:`5052`: Add q to toggle the pager. -* :ghpull:`5070`: fix flex: auto -* :ghpull:`5065`: Add example of using annotations in interact -* :ghpull:`5063`: another pass on Interact example notebooks -* :ghpull:`5051`: FF Fix: code cell missing hscroll (2) -* :ghpull:`4960`: Interact/Interactive for widget -* :ghpull:`5045`: Clear timeout in multi-press keyboard shortcuts. -* :ghpull:`5060`: Change 'bind' to 'link' -* :ghpull:`5039`: Expose kernel_info method on inprocess kernel client -* :ghpull:`5058`: Fix iopubwatcher.py example script. -* :ghpull:`5035`: FF Fix: code cell missing hscroll -* :ghpull:`5040`: Polishing some docs -* :ghpull:`5001`: Add directory navigation to dashboard -* :ghpull:`5042`: Remove duplicated Channel ABC classes. -* :ghpull:`5036`: FF Fix: ext link icon same line as link text in help menu -* :ghpull:`4975`: setup.py changes for 2.0 -* :ghpull:`4774`: emit event on appended element on dom -* :ghpull:`5023`: Widgets- add ability to pack and unpack arrays on JS side. -* :ghpull:`5003`: Fix pretty reprs of super() objects -* :ghpull:`4974`: make paste focus the pasted cell -* :ghpull:`5012`: Make `SelectionWidget.values` a dict -* :ghpull:`5018`: Prevent 'iptest IPython' from trying to run. -* :ghpull:`5025`: citation2latex filter (using HTMLParser) -* :ghpull:`5027`: pin lessc to 1.4 -* :ghpull:`4952`: Widget test inconsistencies -* :ghpull:`5014`: Fix command mode & popup view bug -* :ghpull:`4842`: more subtle kernel indicator -* :ghpull:`5017`: Add notebook examples link to help menu. -* :ghpull:`5015`: don't write cell.trusted to disk -* :ghpull:`5007`: Update whatsnew doc from PR files -* :ghpull:`5010`: Fixes for widget alignment in FF -* :ghpull:`4901`: Add a convenience class to sync traitlet attributes -* :ghpull:`5008`: updated explanation of 'pyin' messages -* :ghpull:`5004`: Fix widget vslider spacing -* :ghpull:`4933`: Small Widget inconsistency fixes -* :ghpull:`4979`: add versioning notes to small message spec changes -* :ghpull:`4893`: add font-awesome 3.2.1 -* :ghpull:`4982`: Live readout for slider widgets -* :ghpull:`4813`: make help menu a template -* :ghpull:`4939`: Embed qtconsole docs (continued) -* :ghpull:`4964`: remove shift-= merge keyboard shortcut -* :ghpull:`4504`: Allow input transformers to raise SyntaxError -* :ghpull:`4929`: Fixing various modal/focus related bugs -* :ghpull:`4971`: Fixing issues with js tests -* :ghpull:`4972`: Work around problem in doctest discovery in Python 3.4 with PyQt -* :ghpull:`4937`: pickle arrays with dtype=object -* :ghpull:`4934`: `ipython profile create` respects `--ipython-dir` -* :ghpull:`4954`: generate unicode filename -* :ghpull:`4845`: Add Origin Checking. -* :ghpull:`4916`: Fine tuning the behavior of the modal UI -* :ghpull:`4966`: Ignore sys.argv for NotebookNotary in tests -* :ghpull:`4967`: Fix typo in warning about web socket being closed -* :ghpull:`4965`: Remove mention of iplogger from setup.py -* :ghpull:`4962`: Fixed typos in quick-help text -* :ghpull:`4953`: add utils.wait_for_idle in js tests -* :ghpull:`4870`: ipython_directive, report except/warn in block and add :okexcept: :okwarning: options to suppress -* :ghpull:`4662`: Menu cleanup -* :ghpull:`4824`: sign notebooks -* :ghpull:`4943`: Docs shotgun 4 -* :ghpull:`4848`: avoid import of nearby temporary with %edit -* :ghpull:`4950`: Two fixes for file upload related bugs -* :ghpull:`4927`: there shouldn't be a 'files/' prefix in FileLink[s] -* :ghpull:`4928`: use importlib.machinery when available -* :ghpull:`4949`: Remove the docscrape modules, which are part of numpydoc -* :ghpull:`4849`: Various unicode fixes (mostly on Windows) -* :ghpull:`4932`: always point py3compat.input to builtin_mod.input -* :ghpull:`4807`: Correct handling of ansi colour codes when nbconverting to latex -* :ghpull:`4922`: Python nbconvert output shouldn't have output -* :ghpull:`4912`: Skip some Windows io failures -* :ghpull:`4919`: flush output before showing tracebacks -* :ghpull:`4915`: ZMQCompleter inherits from IPCompleter -* :ghpull:`4890`: better cleanup channel FDs -* :ghpull:`4880`: set profile name from profile_dir -* :ghpull:`4853`: fix setting image height/width from metadata -* :ghpull:`4786`: Reduce spacing of heading cells -* :ghpull:`4680`: Minimal pandoc version warning -* :ghpull:`4908`: detect builtin docstrings in oinspect -* :ghpull:`4911`: Don't use `python -m package` on Windows Python 2 -* :ghpull:`4909`: sort dictionary keys before comparison, ordering is not guaranteed -* :ghpull:`4374`: IPEP 23: Backbone.js Widgets -* :ghpull:`4903`: use https for all embeds -* :ghpull:`4894`: Shortcut changes -* :ghpull:`4897`: More detailed documentation about kernel_cmd -* :ghpull:`4891`: Squash a few Sphinx warnings from nbconvert.utils.lexers docstrings -* :ghpull:`4679`: JPG compression for inline pylab -* :ghpull:`4708`: Fix indent and center -* :ghpull:`4789`: fix IPython.embed -* :ghpull:`4655`: prefer marked to pandoc for markdown2html -* :ghpull:`4876`: don't show tooltip if object is not found -* :ghpull:`4873`: use 'combine' option to ucs package -* :ghpull:`4732`: Accents in notebook names and in command-line (nbconvert) -* :ghpull:`4867`: Update URL for Lawrence Hall of Science webcam image -* :ghpull:`4868`: Static path fixes -* :ghpull:`4858`: fix tb_offset when running a file -* :ghpull:`4826`: some $.html( -> $.text( -* :ghpull:`4847`: add js kernel_info request -* :ghpull:`4832`: allow NotImplementedError in formatters -* :ghpull:`4803`: BUG: fix cython magic support in ipython_directive -* :ghpull:`4865`: `build` listed twice in .gitignore. Removing one. -* :ghpull:`4851`: fix tooltip token regex for single-character names -* :ghpull:`4846`: Remove some leftover traces of irunner -* :ghpull:`4820`: fix regex for cleaning old logs with ipcluster -* :ghpull:`4844`: adjustments to notebook app logging -* :ghpull:`4840`: Error in Session.send_raw() -* :ghpull:`4819`: update CodeMirror to 3.21 -* :ghpull:`4823`: Minor fixes for typos/inconsistencies in parallel docs -* :ghpull:`4811`: document code mirror tab and shift-tab -* :ghpull:`4795`: merge reveal templates -* :ghpull:`4796`: update components -* :ghpull:`4806`: Correct order of packages for unicode in nbconvert to LaTeX -* :ghpull:`4800`: Qt frontend: Handle 'aborted' prompt replies. -* :ghpull:`4794`: Compatibility fix for Python3 (Issue #4783 ) -* :ghpull:`4799`: minor js test fix -* :ghpull:`4788`: warn when notebook is started in pylab mode -* :ghpull:`4772`: Notebook server info files -* :ghpull:`4797`: be conservative about kernel_info implementation -* :ghpull:`4787`: non-python kernels run python code with qtconsole -* :ghpull:`4565`: various display type validations -* :ghpull:`4703`: Math macro in jinja templates. -* :ghpull:`4781`: Fix "Source" text for the "Other Syntax" section of the "Typesetting Math" notebook -* :ghpull:`4776`: Manually document py3compat module. -* :ghpull:`4533`: propagate display metadata to all mimetypes -* :ghpull:`4785`: Replacing a for-in loop by an index loop on an array -* :ghpull:`4780`: Updating CSS for UI example. -* :ghpull:`3605`: Modal UI -* :ghpull:`4758`: Python 3.4 fixes -* :ghpull:`4735`: add some HTML error pages -* :ghpull:`4775`: Update whatsnew doc from PR files -* :ghpull:`4760`: Make examples and docs more Python 3 aware -* :ghpull:`4773`: Don't wait forever for notebook server to launch/die for tests -* :ghpull:`4768`: Qt console: Fix _prompt_pos accounting on timer flush output. -* :ghpull:`4727`: Remove Nbconvert template loading magic -* :ghpull:`4763`: Set numpydoc options to produce fewer Sphinx warnings. -* :ghpull:`4770`: always define aliases, even if empty -* :ghpull:`4766`: add `python -m` entry points for everything -* :ghpull:`4767`: remove manpages for irunner, iplogger -* :ghpull:`4751`: Added --post-serve explanation into the nbconvert docs. -* :ghpull:`4762`: whitelist alphanumeric characters for cookie_name -* :ghpull:`4625`: Deprecate %profile magic -* :ghpull:`4745`: warn on failed formatter calls -* :ghpull:`4746`: remove redundant cls alias on Windows -* :ghpull:`4749`: Fix bug in determination of public ips. -* :ghpull:`4715`: restore use of tornado static_url in templates -* :ghpull:`4748`: fix race condition in profiledir creation. -* :ghpull:`4720`: never use ssh multiplexer in tunnels -* :ghpull:`4658`: Bug fix for #4643: Regex object needs to be reset between calls in toolt... -* :ghpull:`4561`: Add Formatter.pop(type) -* :ghpull:`4712`: Docs shotgun 3 -* :ghpull:`4713`: Fix saving kernel history in Python 2 -* :ghpull:`4744`: don't use lazily-evaluated rc.ids in wait_for_idle -* :ghpull:`4740`: %env can't set variables -* :ghpull:`4737`: check every link when detecting virutalenv -* :ghpull:`4738`: don't inject help into user_ns -* :ghpull:`4739`: skip html nbconvert tests when their dependencies are missing -* :ghpull:`4730`: Fix stripping continuation prompts when copying from Qt console -* :ghpull:`4725`: Doc fixes -* :ghpull:`4656`: Nbconvert HTTP service -* :ghpull:`4710`: make @interactive decorator friendlier with dill -* :ghpull:`4722`: allow purging local results as long as they are not outstanding -* :ghpull:`4549`: Updated IPython console lexers. -* :ghpull:`4570`: Update IPython directive -* :ghpull:`4719`: Fix comment typo in prefilter.py -* :ghpull:`4575`: make sure to encode URL components for API requests -* :ghpull:`4718`: Fixed typo in displaypub -* :ghpull:`4716`: Remove input_prefilter hook -* :ghpull:`4691`: survive failure to bind to localhost in zmq.iostream -* :ghpull:`4696`: don't do anything if add_anchor fails -* :ghpull:`4711`: some typos in the docs -* :ghpull:`4700`: use if main block in entry points -* :ghpull:`4692`: setup.py symlink improvements -* :ghpull:`4265`: JSON configuration file -* :ghpull:`4505`: Nbconvert latex markdown images2 -* :ghpull:`4608`: transparent background match ... all colors -* :ghpull:`4678`: allow ipython console to handle text/plain display -* :ghpull:`4706`: remove irunner, iplogger -* :ghpull:`4701`: Delete an old dictionary available for selecting the aligment of text. -* :ghpull:`4702`: Making reveal font-size a relative unit. -* :ghpull:`4649`: added a quiet option to %cpaste to suppress output -* :ghpull:`4690`: Option to spew subprocess streams during tests -* :ghpull:`4688`: Fixed various typos in docstrings. -* :ghpull:`4645`: CasperJs utility functions. -* :ghpull:`4670`: Stop bundling the numpydoc Sphinx extension -* :ghpull:`4675`: common IPython prefix for ModIndex -* :ghpull:`4672`: Remove unused 'attic' module -* :ghpull:`4671`: Fix docstrings in utils.text -* :ghpull:`4669`: add missing help strings to HistoryManager configurables -* :ghpull:`4668`: Make non-ASCII docstring unicode -* :ghpull:`4650`: added a note about sharing of nbconvert tempates -* :ghpull:`4646`: Fixing various output related things: -* :ghpull:`4665`: check for libedit in readline on OS X -* :ghpull:`4606`: Make running PYTHONSTARTUP optional -* :ghpull:`4654`: Fixing left padding of text cells to match that of code cells. -* :ghpull:`4306`: add raw_mimetype metadata to raw cells -* :ghpull:`4576`: Tighten up the vertical spacing on cells and make the padding of cells more consistent -* :ghpull:`4353`: Don't reset the readline completer after each prompt -* :ghpull:`4567`: Adding prompt area to non-CodeCells to indent content. -* :ghpull:`4446`: Use SVG plots in OctaveMagic by default due to lack of Ghostscript on Windows Octave -* :ghpull:`4613`: remove configurable.created -* :ghpull:`4631`: Use argument lists for command help tests -* :ghpull:`4633`: Modifies test_get_long_path_name_winr32() to allow for long path names in temp dir -* :ghpull:`4642`: Allow docs to build without PyQt installed. -* :ghpull:`4641`: Don't check for wx in the test suite. -* :ghpull:`4622`: make QtConsole Lexer configurable -* :ghpull:`4594`: Fixed #2923 Move Save Away from Cut in toolbar -* :ghpull:`4593`: don't interfere with set_next_input contents in qtconsole -* :ghpull:`4640`: Support matplotlib's Gtk3 backend in --pylab mode -* :ghpull:`4639`: Minor import fix to get qtconsole with --pylab=qt working -* :ghpull:`4637`: Fixed typo in links.txt. -* :ghpull:`4634`: Fix nbrun in notebooks with non-code cells. -* :ghpull:`4632`: Restore the ability to run tests from a function. -* :ghpull:`4624`: Fix crash when $EDITOR is non-ASCII -* :ghpull:`4453`: Play nice with App Nap -* :ghpull:`4541`: relax ipconfig matching on Windows -* :ghpull:`4552`: add pickleutil.use_dill -* :ghpull:`4590`: Font awesome for IPython slides -* :ghpull:`4589`: Inherit the width of pre code inside the input code cells. -* :ghpull:`4588`: Update reveal.js CDN to 2.5.0. -* :ghpull:`4569`: store cell toolbar preset in notebook metadata -* :ghpull:`4609`: Fix bytes regex for Python 3. -* :ghpull:`4581`: Writing unicode to stdout -* :ghpull:`4591`: Documenting codemirror shorcuts. -* :ghpull:`4607`: Tutorial doc should link to user config intro -* :ghpull:`4601`: test that rename fails with 409 if it would clobber -* :ghpull:`4599`: re-cast int/float subclasses to int/float in json_clean -* :ghpull:`4542`: new `ipython history clear` subcommand -* :ghpull:`4568`: don't use lazily-evaluated rc.ids in wait_for_idle -* :ghpull:`4572`: DOC: %profile docstring should reference %prun -* :ghpull:`4571`: no longer need 3 suffix on travis, tox -* :ghpull:`4566`: Fixing cell_type in CodeCell constructor. -* :ghpull:`4563`: Specify encoding for reading notebook file. -* :ghpull:`4452`: support notebooks in %run -* :ghpull:`4546`: fix warning condition on notebook startup -* :ghpull:`4540`: Apidocs3 -* :ghpull:`4553`: Fix Python 3 handling of urllib -* :ghpull:`4543`: make hiding of initial namespace optional -* :ghpull:`4517`: send shutdown_request on exit of `ipython console` -* :ghpull:`4528`: improvements to bash completion -* :ghpull:`4532`: Hide dynamically defined metaclass base from Sphinx. -* :ghpull:`4515`: Spring Cleaning, and Load speedup -* :ghpull:`4529`: note routing identities needed for input requests -* :ghpull:`4514`: allow restart in `%run -d` -* :ghpull:`4527`: add redirect for 1.0-style 'files/' prefix links -* :ghpull:`4526`: Allow unicode arguments to passwd_check on Python 2 -* :ghpull:`4403`: Global highlight language selection. -* :ghpull:`4250`: outputarea.js: Wrap inline SVGs inside an iframe -* :ghpull:`4521`: Read wav files in binary mode -* :ghpull:`4444`: Css cleaning -* :ghpull:`4523`: Use username and password for MongoDB on ShiningPanda -* :ghpull:`4510`: Update whatsnew from PR files -* :ghpull:`4441`: add `setup.py jsversion` -* :ghpull:`4518`: Fix for race condition in url file decoding. -* :ghpull:`4497`: don't automatically unpack datetime objects in the message spec -* :ghpull:`4506`: wait for empty queues as well as load-balanced tasks -* :ghpull:`4492`: Configuration docs refresh -* :ghpull:`4508`: Fix some uses of map() in Qt console completion code. -* :ghpull:`4498`: Daemon StreamCapturer -* :ghpull:`4499`: Skip clipboard test on unix systems if headless. -* :ghpull:`4460`: Better clipboard handling, esp. with pywin32 -* :ghpull:`4496`: Pass nbformat object to write call to save .py script -* :ghpull:`4466`: various pandoc latex fixes -* :ghpull:`4473`: Setup for Python 2/3 -* :ghpull:`4459`: protect against broken repr in lib.pretty -* :ghpull:`4457`: Use ~/.ipython as default config directory -* :ghpull:`4489`: check realpath of env in init_virtualenv -* :ghpull:`4490`: fix possible race condition in test_await_data -* :ghpull:`4476`: Fix: Remove space added by display(JavaScript) on page reload -* :ghpull:`4398`: [Notebook] Deactivate tooltip on tab by default. -* :ghpull:`4480`: Docs shotgun 2 -* :ghpull:`4488`: fix typo in message spec doc -* :ghpull:`4479`: yet another JS race condition fix -* :ghpull:`4477`: Allow incremental builds of the html_noapi docs target -* :ghpull:`4470`: Various Config object cleanups -* :ghpull:`4410`: make close-and-halt work on new tabs in Chrome -* :ghpull:`4469`: Python 3 & getcwdu -* :ghpull:`4451`: fix: allow JS test to run after shutdown test -* :ghpull:`4456`: Simplify StreamCapturer for subprocess testing -* :ghpull:`4464`: Correct description for Bytes traitlet type -* :ghpull:`4465`: Clean up MANIFEST.in -* :ghpull:`4461`: Correct TypeError message in svg2pdf -* :ghpull:`4458`: use signalstatus if exit status is undefined -* :ghpull:`4438`: Single codebase Python 3 support (again) -* :ghpull:`4198`: Version conversion, support for X to Y even if Y < X (nbformat) -* :ghpull:`4415`: More tooltips in the Notebook menu -* :ghpull:`4450`: remove monkey patch for older versions of tornado -* :ghpull:`4423`: Fix progress bar and scrolling bug. -* :ghpull:`4435`: raise 404 on not found static file -* :ghpull:`4442`: fix and add shim for change introduce by #4195 -* :ghpull:`4436`: allow `require("nbextensions/extname")` to load from IPYTHONDIR/nbextensions -* :ghpull:`4437`: don't compute etags in static file handlers -* :ghpull:`4427`: notebooks should always have one checkpoint -* :ghpull:`4425`: fix js pythonisme -* :ghpull:`4195`: IPEP 21: widget messages -* :ghpull:`4434`: Fix broken link for Dive Into Python. -* :ghpull:`4428`: bump minimum tornado version to 3.1.0 -* :ghpull:`4302`: Add an Audio display class -* :ghpull:`4285`: Notebook javascript test suite using CasperJS -* :ghpull:`4420`: Allow checking for backports via milestone -* :ghpull:`4426`: set kernel cwd to notebook's directory -* :ghpull:`4389`: By default, Magics inherit from Configurable -* :ghpull:`4393`: Capture output from subprocs during test, and display on failure -* :ghpull:`4419`: define InlineBackend configurable in its own file -* :ghpull:`4303`: Multidirectory support for the Notebook -* :ghpull:`4371`: Restored ipython profile locate dir and fixed typo. (Fixes #3708). -* :ghpull:`4414`: Specify unicode type properly in rmagic -* :ghpull:`4413`: don't instantiate IPython shell as class attr -* :ghpull:`4400`: Remove 5s wait on inactivity on GUI inputhook loops -* :ghpull:`4412`: Fix traitlet _notify_trait by-ref issue -* :ghpull:`4378`: split adds new cell above, rather than below -* :ghpull:`4405`: Bring display of builtin types and functions in line with Py 2 -* :ghpull:`4367`: clean up of documentation files -* :ghpull:`4401`: Provide a name of the HistorySavingThread -* :ghpull:`4384`: fix menubar height measurement -* :ghpull:`4377`: fix tooltip cancel -* :ghpull:`4293`: Factorise code in tooltip for julia monkeypatching -* :ghpull:`4292`: improve js-completer logic. -* :ghpull:`4363`: set_next_input: keep only last input when repeatedly called in a single cell -* :ghpull:`4382`: Use safe_hasattr in dir2 -* :ghpull:`4379`: fix (CTRL-M -) shortcut for splitting cell in FF -* :ghpull:`4380`: Test and fixes for localinterfaces -* :ghpull:`4372`: Don't assume that SyntaxTB is always called with a SyntaxError -* :ghpull:`4342`: Return value directly from the try block and avoid a variable -* :ghpull:`4154`: Center LaTeX and figures in markdown -* :ghpull:`4311`: %load -s to load specific functions or classes -* :ghpull:`4350`: WinHPC launcher fixes -* :ghpull:`4345`: Make irunner compatible with upcoming pexpect 3.0 interface -* :ghpull:`4276`: Support container methods in config -* :ghpull:`4359`: test_pylabtools also needs to modify matplotlib.rcParamsOrig -* :ghpull:`4355`: remove hardcoded box-orient -* :ghpull:`4333`: Add Edit Notebook Metadata to Edit menu -* :ghpull:`4349`: Script to update What's New file -* :ghpull:`4348`: Call PDF viewer after latex compiling (nbconvert) -* :ghpull:`4346`: getpass() on Windows & Python 2 needs bytes prompt -* :ghpull:`4304`: use netifaces for faster IPython.utils.localinterfaces -* :ghpull:`4305`: Add even more ways to populate localinterfaces -* :ghpull:`4313`: remove strip_math_space -* :ghpull:`4325`: Some changes to improve readability. -* :ghpull:`4281`: Adjust tab completion widget if too close to bottom of page. -* :ghpull:`4347`: Remove pycolor script -* :ghpull:`4322`: Scroll to the top after change of slides in the IPython slides -* :ghpull:`4289`: Fix scrolling output (not working post clear_output changes) -* :ghpull:`4343`: Make parameters for kernel start method more general -* :ghpull:`4237`: Keywords should shadow magic functions -* :ghpull:`4338`: adjust default value of level in sync_imports -* :ghpull:`4328`: Remove unused loop variable. -* :ghpull:`4340`: fix mathjax download url to new GitHub format -* :ghpull:`4336`: use simple replacement rather than string formatting in format_kernel_cmd -* :ghpull:`4264`: catch unicode error listing profiles -* :ghpull:`4314`: catch EACCES when binding notebook app -* :ghpull:`4324`: Remove commented addthis toolbar -* :ghpull:`4327`: Use the with statement to open a file. -* :ghpull:`4318`: fix initial sys.path -* :ghpull:`4315`: Explicitly state what version of Pandoc is supported in docs/install -* :ghpull:`4316`: underscore missing on notebook_p4 -* :ghpull:`4295`: Implement boundary option for load magic (#1093) -* :ghpull:`4300`: traits defauts are strings not object -* :ghpull:`4297`: Remove an unreachable return statement. -* :ghpull:`4260`: Use subprocess for system_raw -* :ghpull:`4277`: add nbextensions -* :ghpull:`4294`: don't require tornado 3 in `--post serve` -* :ghpull:`4270`: adjust Scheduler timeout logic -* :ghpull:`4278`: add `-a` to easy_install command in libedit warning -* :ghpull:`4282`: Enable automatic line breaks in MathJax. -* :ghpull:`4279`: Fixing line-height of list items in tree view. -* :ghpull:`4253`: fixes #4039. -* :ghpull:`4131`: Add module's name argument in %%cython magic -* :ghpull:`4269`: Add mathletters option and longtable package to latex_base.tplx -* :ghpull:`4230`: Switch correctly to the user's default matplotlib backend after inline. -* :ghpull:`4271`: Hopefully fix ordering of output on ShiningPanda -* :ghpull:`4239`: more informative error message for bad serialization -* :ghpull:`4263`: Fix excludes for IPython.testing -* :ghpull:`4112`: nbconvert: Latex template refactor -* :ghpull:`4261`: Fixing a formatting error in the custom display example notebook. -* :ghpull:`4259`: Fix Windows test exclusions -* :ghpull:`4229`: Clear_output: Animation & widget related changes. -* :ghpull:`4151`: Refactor alias machinery -* :ghpull:`4153`: make timeit return an object that contains values -* :ghpull:`4258`: to-backport label is now 1.2 -* :ghpull:`4242`: Allow passing extra arguments to iptest through for nose -* :ghpull:`4257`: fix unicode argv parsing -* :ghpull:`4166`: avoid executing code in utils.localinterfaces at import time -* :ghpull:`4214`: engine ID metadata should be unicode, not bytes -* :ghpull:`4232`: no highlight if no language specified -* :ghpull:`4218`: Fix display of SyntaxError when .py file is modified -* :ghpull:`4207`: add `setup.py css` command -* :ghpull:`4224`: clear previous callbacks on execute -* :ghpull:`4180`: Iptest refactoring -* :ghpull:`4105`: JS output area misaligned -* :ghpull:`4220`: Various improvements to docs formatting -* :ghpull:`4187`: Select adequate highlighter for cell magic languages -* :ghpull:`4228`: update -dev docs to reflect latest stable version -* :ghpull:`4219`: Drop bundled argparse -* :ghpull:`3851`: Adds an explicit newline for pretty-printing. -* :ghpull:`3622`: Drop fakemodule -* :ghpull:`4080`: change default behavior of database task storage -* :ghpull:`4197`: enable cython highlight in notebook -* :ghpull:`4225`: Updated docstring for core.display.Image -* :ghpull:`4175`: nbconvert: Jinjaless exporter base -* :ghpull:`4208`: Added a lightweight "htmlcore" Makefile entry -* :ghpull:`4209`: Magic doc fixes -* :ghpull:`4217`: avoid importing numpy at the module level -* :ghpull:`4213`: fixed dead link in examples/notebooks readme to Part 3 -* :ghpull:`4183`: ESC should be handled by CM if tooltip is not on -* :ghpull:`4193`: Update for #3549: Append Firefox overflow-x fix -* :ghpull:`4205`: use TextIOWrapper when communicating with pandoc subprocess -* :ghpull:`4204`: remove some extraneous print statements from IPython.parallel -* :ghpull:`4201`: HeadingCells cannot be split or merged -* :ghpull:`4048`: finish up speaker-notes PR -* :ghpull:`4079`: trigger `Kernel.status_started` after websockets open -* :ghpull:`4186`: moved DummyMod to proper namespace to enable dill pickling -* :ghpull:`4190`: update version-check message in setup.py and IPython.__init__ -* :ghpull:`4188`: Allow user_ns trait to be None -* :ghpull:`4189`: always fire LOCAL_IPS.extend(PUBLIC_IPS) -* :ghpull:`4174`: various issues in markdown and rst templates -* :ghpull:`4178`: add missing data_javascript -* :ghpull:`4168`: Py3 failing tests -* :ghpull:`4181`: nbconvert: Fix, sphinx template not removing new lines from headers -* :ghpull:`4043`: don't 'restore_bytes' in from_JSON -* :ghpull:`4149`: reuse more kernels in kernel tests -* :ghpull:`4163`: Fix for incorrect default encoding on Windows. -* :ghpull:`4136`: catch javascript errors in any output -* :ghpull:`4171`: add nbconvert config file when creating profiles -* :ghpull:`4172`: add ability to check what PRs should be backported in backport_pr -* :ghpull:`4167`: --fast flag for test suite! -* :ghpull:`4125`: Basic exercise of `ipython [subcommand] -h` and help-all -* :ghpull:`4085`: nbconvert: Fix sphinx preprocessor date format string for Windows -* :ghpull:`4159`: don't split `.cell` and `div.cell` CSS -* :ghpull:`4165`: Remove use of parametric tests -* :ghpull:`4158`: generate choices for `--gui` configurable from real mapping -* :ghpull:`4083`: Implement a better check for hidden values for %who etc. -* :ghpull:`4147`: Reference notebook examples, fixes #4146. -* :ghpull:`4065`: do not include specific css in embedable one -* :ghpull:`4092`: nbconvert: Fix for unicode html headers, Windows + Python 2.x -* :ghpull:`4074`: close Client sockets if connection fails -* :ghpull:`4064`: Store default codemirror mode in only 1 place -* :ghpull:`4104`: Add way to install MathJax to a particular profile -* :ghpull:`4161`: Select name when renaming a notebook -* :ghpull:`4160`: Add quotes around ".[notebook]" in readme -* :ghpull:`4144`: help_end transformer shouldn't pick up ? in multiline string -* :ghpull:`4090`: Add LaTeX citation handling to nbconvert -* :ghpull:`4143`: update example custom.js -* :ghpull:`4142`: DOC: unwrap openssl line in public_server doc -* :ghpull:`4126`: update tox.ini -* :ghpull:`4141`: add files with a separate `add` call in backport_pr -* :ghpull:`4137`: Restore autorestore option for storemagic -* :ghpull:`4098`: pass profile-dir instead of profile name to Kernel -* :ghpull:`4120`: support `input` in Python 2 kernels -* :ghpull:`4088`: nbconvert: Fix coalescestreams line with incorrect nesting causing strange behavior -* :ghpull:`4060`: only strip continuation prompts if regular prompts seen first -* :ghpull:`4132`: Fixed name error bug in function safe_unicode in module py3compat. -* :ghpull:`4121`: move test_kernel from IPython.zmq to IPython.kernel -* :ghpull:`4118`: ZMQ heartbeat channel: catch EINTR exceptions and continue. -* :ghpull:`4070`: New changes should go into pr/ folder -* :ghpull:`4054`: use unicode for HTML export -* :ghpull:`4106`: fix a couple of default block values -* :ghpull:`4107`: update parallel magic tests with capture_output API -* :ghpull:`4102`: Fix clashes between debugger tests and coverage.py -* :ghpull:`4115`: Update docs on declaring a magic function -* :ghpull:`4101`: restore accidentally removed EngineError -* :ghpull:`4096`: minor docs changes -* :ghpull:`4094`: Update target branch before backporting PR -* :ghpull:`4069`: Drop monkeypatch for pre-1.0 nose -* :ghpull:`4056`: respect `pylab_import_all` when `--pylab` specified at the command-line -* :ghpull:`4091`: Make Qt console banner configurable -* :ghpull:`4086`: fix missing errno import -* :ghpull:`4084`: Use msvcrt.getwch() for Windows pager. -* :ghpull:`4073`: rename ``post_processors`` submodule to ``postprocessors`` -* :ghpull:`4075`: Update supported Python versions in tools/test_pr -* :ghpull:`4068`: minor bug fix, define 'cell' in dialog.js. -* :ghpull:`4044`: rename call methods to transform and postprocess -* :ghpull:`3744`: capture rich output as well as stdout/err in capture_output -* :ghpull:`3969`: "use strict" in most (if not all) our javascript -* :ghpull:`4030`: exclude `.git` in MANIFEST.in -* :ghpull:`4047`: Use istype() when checking if canned object is a dict -* :ghpull:`4031`: don't close_fds on Windows -* :ghpull:`4029`: bson.Binary moved -* :ghpull:`3883`: skip test on unix when x11 not available -* :ghpull:`3863`: Added working speaker notes for slides. -* :ghpull:`4035`: Fixed custom jinja2 templates being ignored when setting template_path -* :ghpull:`4002`: Drop Python 2.6 and 3.2 -* :ghpull:`4026`: small doc fix in nbconvert -* :ghpull:`4016`: Fix IPython.start_* functions -* :ghpull:`4021`: Fix parallel.client.View map() on numpy arrays -* :ghpull:`4022`: DOC: fix links to matplotlib, notebook docs -* :ghpull:`4018`: Fix warning when running IPython.kernel tests -* :ghpull:`4017`: Add REPL-like printing of final/return value to %%R cell magic -* :ghpull:`4019`: Test skipping without unicode paths -* :ghpull:`4008`: Transform code before %prun/%%prun runs -* :ghpull:`4014`: Fix typo in ipapp -* :ghpull:`3997`: DOC: typos + rewording in examples/notebooks/Cell Magics.ipynb -* :ghpull:`3914`: nbconvert: Transformer tests -* :ghpull:`3987`: get files list in backport_pr -* :ghpull:`3923`: nbconvert: Writer tests -* :ghpull:`3974`: nbconvert: Fix app tests on Window7 w/ Python 3.3 -* :ghpull:`3937`: make tab visible in codemirror and light red background -* :ghpull:`3933`: nbconvert: Post-processor tests -* :ghpull:`3978`: fix `--existing` with non-localhost IP -* :ghpull:`3939`: minor checkpoint cleanup -* :ghpull:`3955`: complete on % for magic in notebook -* :ghpull:`3981`: BF: fix nbconert rst input prompt spacing -* :ghpull:`3960`: Don't make sphinx a dependency for importing nbconvert -* :ghpull:`3973`: logging.Formatter is not new-style in 2.6 - -Issues (434): - -* :ghissue:`5476`: For 2.0: Fix links in Notebook Help Menu -* :ghissue:`5337`: Examples reorganization -* :ghissue:`5436`: CodeMirror shortcuts in QuickHelp -* :ghissue:`5444`: Fix numeric verification for Int and Float text widgets. -* :ghissue:`5443`: Int and Float Widgets don't allow negative signs -* :ghissue:`5449`: Stretch keyboard shortcut dialog -* :ghissue:`5471`: Add coding magic comment to nbconvert Python template -* :ghissue:`5470`: UTF-8 Issue When Converting Notebook to a Script. -* :ghissue:`5369`: FormatterWarning for SVG matplotlib output in notebook -* :ghissue:`5460`: Can't start the notebook server specifying a notebook -* :ghissue:`2918`: CodeMirror related issues. -* :ghissue:`5431`: update github_stats and gh_api for 2.0 -* :ghissue:`4887`: Add tests for modal UI -* :ghissue:`5290`: Add dual mode JS tests -* :ghissue:`5448`: Cmd+/ shortcut doesn't work in IPython master -* :ghissue:`5447`: Add %%python2 cell magic -* :ghissue:`5442`: Make a "python2" alias or rename the "python"cell magic. -* :ghissue:`2495`: non-ascii characters in the path -* :ghissue:`4554`: dictDB: Exception due to str to datetime comparission -* :ghissue:`5006`: Comm code is not run in the same context as notebook code -* :ghissue:`5118`: Weird interact behavior -* :ghissue:`5401`: Empty code cells in nbconvert rst output cause problems -* :ghissue:`5434`: fix check for empty cells in rst template -* :ghissue:`4944`: Trouble finding ipynb path in Windows 8 -* :ghissue:`4605`: Change the url of Editor Shorcuts in the notebook menu. -* :ghissue:`5425`: Update COPYING.txt -* :ghissue:`5348`: BUG: HistoryAccessor.get_session_info(0) - exception -* :ghissue:`5293`: Javascript("element.append()") looks broken. -* :ghissue:`5363`: Disable saving if notebook has stopped loading -* :ghissue:`5189`: Tooltip pager mode is broken -* :ghissue:`5330`: Updates to shell reference doc -* :ghissue:`5397`: Accordion widget broken -* :ghissue:`5106`: Flexbox CSS specificity bugs -* :ghissue:`5297`: tooltip triggers focus bug -* :ghissue:`5417`: scp checking for existence of directories: directory names are incorrect -* :ghissue:`5302`: Parallel engine registration fails for slow engines -* :ghissue:`5334`: notebook's split-cell shortcut dangerous / incompatible with Neo layout (for instance) -* :ghissue:`5324`: Style of `raw_input` UI is off in notebook -* :ghissue:`5350`: Converting notebooks with spaces in their names to RST gives broken images -* :ghissue:`5049`: update quickhelp on adding and removing shortcuts -* :ghissue:`4941`: Eliminating display of intermediate stages in progress bars -* :ghissue:`5345`: nbconvert to markdown does not use backticks -* :ghissue:`5357`: catch exception in copystat -* :ghissue:`5351`: Notebook saving fails on smb share -* :ghissue:`4946`: TeX produced cannot be converted to PDF -* :ghissue:`5347`: pretty print list too slow -* :ghissue:`5238`: Raw cell placeholder is not removed when you edit the cell -* :ghissue:`5382`: Qtconsole doesn't run in Python 3 -* :ghissue:`5378`: Unexpected and new conflict between PyFileConfigLoader and IPythonQtConsoleApp -* :ghissue:`4945`: Heading/cells positioning problem and cell output wrapping -* :ghissue:`5084`: Consistent approach for HTML/JS output on nbviewer -* :ghissue:`4902`: print preview does not work, custom.css not found -* :ghissue:`5336`: TypeError in bootstrap-tour.min.js -* :ghissue:`5303`: Changed Hub.registration_timeout to be a config input. -* :ghissue:`995`: Paste-able mode in terminal -* :ghissue:`5305`: Tuple unpacking for shell escape -* :ghissue:`5232`: Make nbconvert html full output like notebook's html. -* :ghissue:`5224`: Audit nbconvert HTML output -* :ghissue:`5253`: display any output from this session in terminal console -* :ghissue:`5251`: ipython console ignoring some stream messages? -* :ghissue:`4802`: Tour of the notebook UI (was UI elements inline with highlighting) -* :ghissue:`5103`: Moving Constructor definition to the top like a Function definition -* :ghissue:`5264`: Test failures on master with Anaconda -* :ghissue:`4833`: Serve /usr/share/javascript at /_sysassets/javascript/ in notebook -* :ghissue:`5071`: Prevent %pylab from clobbering interactive -* :ghissue:`5282`: Exception in widget __del__ methods in Python 3.4. -* :ghissue:`5280`: append Firefox overflow-x fix -* :ghissue:`5120`: append Firefox overflow-x fix, again -* :ghissue:`4127`: autoreload shouldn't rely on .pyc modification times -* :ghissue:`5272`: allow highlighting language to be set from notebook metadata -* :ghissue:`5050`: Notebook cells truncated with Firefox -* :ghissue:`4839`: Error in Session.send_raw() -* :ghissue:`5188`: New events system -* :ghissue:`5076`: Refactor keyboard handling -* :ghissue:`4886`: Refactor and consolidate different keyboard logic in JavaScript code -* :ghissue:`5002`: the green cell border moving forever in Chrome, when there are many code cells. -* :ghissue:`5259`: Codemirror still active in command mode -* :ghissue:`5219`: Output images appear as small thumbnails (Notebook) -* :ghissue:`4829`: Not able to connect qtconsole in Windows 8 -* :ghissue:`5152`: Hide __pycache__ in dashboard directory list -* :ghissue:`5151`: Case-insesitive sort for dashboard list -* :ghissue:`4603`: Warn when overwriting a notebook with upload -* :ghissue:`4895`: Improvements to %run completions -* :ghissue:`3459`: Filename completion when run script with %run -* :ghissue:`5225`: Add JavaScript to nbconvert HTML display priority -* :ghissue:`5034`: Audit the places where we call `.html(something)` -* :ghissue:`5094`: Dancing cells in notebook -* :ghissue:`4999`: Notebook focus effects -* :ghissue:`5149`: Clicking on a TextBoxWidget in FF completely breaks dual mode. -* :ghissue:`5207`: Children fire event -* :ghissue:`5227`: display_method of objects with custom __getattr__ -* :ghissue:`5236`: Cursor keys do not work to leave Markdown cell while it's being edited -* :ghissue:`5205`: Use CTuple traitlet for Widget children -* :ghissue:`5230`: notebook rename does not respect url prefix -* :ghissue:`5218`: Test failures with Python 3 and enabled warnings -* :ghissue:`5115`: Page Breaks for Print Preview Broken by display: flex - Simple CSS Fix -* :ghissue:`5024`: Make nbconvert HTML output smart about page breaking -* :ghissue:`4985`: Add automatic Closebrackets function to Codemirror. -* :ghissue:`5184`: print '\xa' crashes the interactive shell -* :ghissue:`5214`: Downloading notebook as Python (.py) fails -* :ghissue:`5211`: AttributeError: 'module' object has no attribute '_outputfile' -* :ghissue:`5206`: [CSS?] Inconsistencies in nbconvert divs and IPython Notebook divs? -* :ghissue:`5201`: node != nodejs within Debian packages -* :ghissue:`5112`: band-aid for completion -* :ghissue:`4860`: Completer As-You-Type Broken -* :ghissue:`5116`: reorganize who knows what about paths -* :ghissue:`4973`: Adding security.js with 1st attempt at is_safe -* :ghissue:`5164`: test_oinspect.test_calltip_builtin failure with python3.4 -* :ghissue:`5127`: Widgets: skip intermediate callbacks during throttling -* :ghissue:`5013`: Widget alignment differs between FF and Chrome -* :ghissue:`5141`: tornado error static file -* :ghissue:`5160`: TemporaryWorkingDirectory incompatible with python3.4 -* :ghissue:`5140`: WIP: %kernels magic -* :ghissue:`4987`: Widget lifecycle problems -* :ghissue:`5129`: UCS package break latex export on non-ascii -* :ghissue:`4986`: Cell horizontal scrollbar is missing in FF but not in Chrome -* :ghissue:`4685`: nbconvert ignores image size metadata -* :ghissue:`5155`: Notebook logout button does not work (source typo) -* :ghissue:`2678`: Ctrl-m keyboard shortcut clash on Chrome OS -* :ghissue:`5113`: ButtonWidget without caption wrong height. -* :ghissue:`4778`: add APIs for installing notebook extensions -* :ghissue:`5046`: python setup.py failed vs git submodule update worked -* :ghissue:`4925`: Notebook manager api fixes -* :ghissue:`5073`: Cannot align widgets horizontally in the notebook -* :ghissue:`4996`: require print_method to be a bound method -* :ghissue:`4990`: _repr_html_ exception reporting corner case when using type(foo) -* :ghissue:`5099`: Notebook: Changing base_project_url results in failed WebSockets call -* :ghissue:`5096`: Client.map is not fault tolerant -* :ghissue:`4997`: Inconsistent %matplotlib qt behavior -* :ghissue:`5041`: Remove more .html(...) calls. -* :ghissue:`5078`: Updating JS tests README.md -* :ghissue:`4977`: ensure scp destination directories exist (with mkdir -p) -* :ghissue:`3411`: ipython parallel: scp failure. -* :ghissue:`5064`: Errors during interact display at the terminal, not anywhere in the notebook -* :ghissue:`4921`: Add PDF formatter and handling -* :ghissue:`4920`: Adding PDFFormatter and kernel side handling of PDF display data -* :ghissue:`5048`: Add edit/command mode indicator -* :ghissue:`4889`: Add UI element for indicating command/edit modes -* :ghissue:`5052`: Add q to toggle the pager. -* :ghissue:`5000`: Closing pager with keyboard in modal UI -* :ghissue:`5069`: Box model changes broke the Keyboard Shortcuts help modal -* :ghissue:`4960`: Interact/Interactive for widget -* :ghissue:`4883`: Implement interact/interactive for widgets -* :ghissue:`5038`: Fix multiple press keyboard events -* :ghissue:`5054`: UnicodeDecodeError: 'ascii' codec can't decode byte 0xc6 in position 1: ordinal not in range(128) -* :ghissue:`5031`: Bug during integration of IPython console in Qt application -* :ghissue:`5057`: iopubwatcher.py example is broken. -* :ghissue:`4747`: Add event for output_area adding an output -* :ghissue:`5001`: Add directory navigation to dashboard -* :ghissue:`5016`: Help menu external-link icons break layout in FF -* :ghissue:`4885`: Modal UI behavior changes -* :ghissue:`5009`: notebook signatures don't work -* :ghissue:`4975`: setup.py changes for 2.0 -* :ghissue:`4774`: emit event on appended element on dom -* :ghissue:`5020`: Python Lists translated to javascript objects in widgets -* :ghissue:`5003`: Fix pretty reprs of super() objects -* :ghissue:`5012`: Make `SelectionWidget.values` a dict -* :ghissue:`4961`: Bug when constructing a selection widget with both values and labels -* :ghissue:`4283`: A `<` in a markdown cell strips cell content when converting to latex -* :ghissue:`4006`: iptest IPython broken -* :ghissue:`4251`: & escaped to & in tex ? -* :ghissue:`5027`: pin lessc to 1.4 -* :ghissue:`4323`: Take 2: citation2latex filter (using HTMLParser) -* :ghissue:`4196`: Printing notebook from browser gives 1-page truncated output -* :ghissue:`4842`: more subtle kernel indicator -* :ghissue:`4057`: No path to notebook examples from Help menu -* :ghissue:`5015`: don't write cell.trusted to disk -* :ghissue:`4617`: Changed url link in Help dropdown menu. -* :ghissue:`4976`: Container widget layout broken on Firefox -* :ghissue:`4981`: Vertical slider layout broken -* :ghissue:`4793`: Message spec changes related to `clear_output` -* :ghissue:`4982`: Live readout for slider widgets -* :ghissue:`4813`: make help menu a template -* :ghissue:`4989`: Filename tab completion completely broken -* :ghissue:`1380`: Tab should insert 4 spaces in # comment lines -* :ghissue:`2888`: spaces vs tabs -* :ghissue:`1193`: Allow resizing figures in notebook -* :ghissue:`4504`: Allow input transformers to raise SyntaxError -* :ghissue:`4697`: Problems with height after toggling header and toolbar... -* :ghissue:`4951`: TextWidget to code cell command mode bug. -* :ghissue:`4809`: Arbitrary scrolling (jumping) in clicks in modal UI for notebook -* :ghissue:`4971`: Fixing issues with js tests -* :ghissue:`4972`: Work around problem in doctest discovery in Python 3.4 with PyQt -* :ghissue:`4892`: IPython.qt test failure with python3.4 -* :ghissue:`4863`: BUG: cannot create an OBJECT array from memory buffer -* :ghissue:`4704`: Subcommand `profile` ignores --ipython-dir -* :ghissue:`4845`: Add Origin Checking. -* :ghissue:`4870`: ipython_directive, report except/warn in block and add :okexcept: :okwarning: options to suppress -* :ghissue:`4956`: Shift-Enter does not move to next cell -* :ghissue:`4662`: Menu cleanup -* :ghissue:`4824`: sign notebooks -* :ghissue:`4848`: avoid import of nearby temporary with %edit -* :ghissue:`4731`: %edit files mistakenly import modules in /tmp -* :ghissue:`4950`: Two fixes for file upload related bugs -* :ghissue:`4871`: Notebook upload fails after Delete -* :ghissue:`4825`: File Upload URL set incorrectly -* :ghissue:`3867`: display.FileLinks should work in the exported html verion of a notebook -* :ghissue:`4948`: reveal: ipython css overrides reveal themes -* :ghissue:`4947`: reveal: slides that are too big? -* :ghissue:`4051`: Test failures with Python 3 and enabled warnings -* :ghissue:`3633`: outstanding issues over in ipython/nbconvert repo -* :ghissue:`4087`: Sympy printing in the example notebook -* :ghissue:`4627`: Document various QtConsole embedding approaches. -* :ghissue:`4849`: Various unicode fixes (mostly on Windows) -* :ghissue:`3653`: autocompletion in "from package import " -* :ghissue:`4583`: overwrite? prompt gets EOFError in 2 process -* :ghissue:`4807`: Correct handling of ansi colour codes when nbconverting to latex -* :ghissue:`4611`: Document how to compile .less files in dev docs. -* :ghissue:`4618`: "Editor Shortcuts" link is broken in help menu dropdown notebook -* :ghissue:`4522`: DeprecationWarning: the sets module is deprecated -* :ghissue:`4368`: No symlink from ipython to ipython3 when inside a python3 virtualenv -* :ghissue:`4234`: Math without $$ doesn't show up when converted to slides -* :ghissue:`4194`: config.TerminalIPythonApp.nosep does not work -* :ghissue:`1491`: prefilter not called for multi-line notebook cells -* :ghissue:`4001`: Windows IPython executable /scripts/ipython not working -* :ghissue:`3959`: think more carefully about text wrapping in nbconvert -* :ghissue:`4907`: Test for traceback depth fails on Windows -* :ghissue:`4906`: Test for IPython.embed() fails on Windows -* :ghissue:`4912`: Skip some Windows io failures -* :ghissue:`3700`: stdout/stderr should be flushed printing exception output... -* :ghissue:`1181`: greedy completer bug in terminal console -* :ghissue:`2032`: check for a few places we should be using DEFAULT_ENCODING -* :ghissue:`4882`: Too many files open when starting and stopping kernel repeatedly -* :ghissue:`4880`: set profile name from profile_dir -* :ghissue:`4238`: parallel.Client() not using profile that notebook was run with? -* :ghissue:`4853`: fix setting image height/width from metadata -* :ghissue:`4786`: Reduce spacing of heading cells -* :ghissue:`4680`: Minimal pandoc version warning -* :ghissue:`3707`: nbconvert: Remove IPython magic commands from --format="python" output -* :ghissue:`4130`: PDF figures as links from png or svg figures -* :ghissue:`3919`: Allow --profile to be passed a dir. -* :ghissue:`2136`: Handle hard newlines in pretty printer -* :ghissue:`4790`: Notebook modal UI: "merge cell below" key binding, `shift+=`, does not work with some keyboard layouts -* :ghissue:`4884`: Keyboard shortcut changes -* :ghissue:`1184`: slow handling of keyboard input -* :ghissue:`4913`: Mathjax, Markdown, tex, env* and italic -* :ghissue:`3972`: nbconvert: Template output testing -* :ghissue:`4903`: use https for all embeds -* :ghissue:`4874`: --debug does not work if you set .kernel_cmd -* :ghissue:`4679`: JPG compression for inline pylab -* :ghissue:`4708`: Fix indent and center -* :ghissue:`4789`: fix IPython.embed -* :ghissue:`4759`: Application._load_config_files log parameter default fails -* :ghissue:`3153`: docs / file menu: explain how to exit the notebook -* :ghissue:`4791`: Did updates to ipython_directive bork support for cython magic snippets? -* :ghissue:`4385`: "Part 4 - Markdown Cells.ipynb" nbviewer example seems not well referenced in current online documentation page https://ipython.org/ipython-doc/stable/interactive/notebook.htm -* :ghissue:`4655`: prefer marked to pandoc for markdown2html -* :ghissue:`3441`: Fix focus related problems in the notebook -* :ghissue:`3402`: Feature Request: Save As (latex, html,..etc) as a menu option in Notebook rather than explicit need to invoke nbconvert -* :ghissue:`3224`: Revisit layout of notebook area -* :ghissue:`2746`: rerunning a cell with long output (exception) scrolls to much (html notebook) -* :ghissue:`2667`: can't save opened notebook if accidentally delete the notebook in the dashboard -* :ghissue:`3026`: Reporting errors from _repr__ methods -* :ghissue:`1844`: Notebook does not exist and permalinks -* :ghissue:`2450`: [closed PR] Prevent jumping of window to input when output is clicked. -* :ghissue:`3166`: IPEP 16: Notebook multi directory dashboard and URL mapping -* :ghissue:`3691`: Slight misalignment of Notebook menu bar with focus box -* :ghissue:`4875`: Empty tooltip with `object_found = false` still being shown -* :ghissue:`4432`: The SSL cert for the MathJax CDN is invalid and URL is not protocol agnostic -* :ghissue:`2633`: Help text should leave current cell active -* :ghissue:`3976`: DOC: Pandas link on the notebook help menu? -* :ghissue:`4082`: /new handler redirect cached by browser -* :ghissue:`4298`: Slow ipython --pylab and ipython notebook startup -* :ghissue:`4545`: %store magic not working -* :ghissue:`4610`: toolbar UI enhancements -* :ghissue:`4782`: New modal UI -* :ghissue:`4732`: Accents in notebook names and in command-line (nbconvert) -* :ghissue:`4752`: link broken in docs/examples -* :ghissue:`4835`: running ipython on python files adds an extra traceback frame -* :ghissue:`4792`: repr_html exception warning on qtconsole with pandas #4745 -* :ghissue:`4834`: function tooltip issues -* :ghissue:`4808`: Docstrings in Notebook not displayed properly and introspection -* :ghissue:`4846`: Remove some leftover traces of irunner -* :ghissue:`4810`: ipcluster bug in clean_logs flag -* :ghissue:`4812`: update CodeMirror for the notebook -* :ghissue:`671`: add migration guide for old IPython config -* :ghissue:`4783`: ipython 2dev under windows / (win)python 3.3 experiment -* :ghissue:`4772`: Notebook server info files -* :ghissue:`4765`: missing build script for highlight.js -* :ghissue:`4787`: non-python kernels run python code with qtconsole -* :ghissue:`4703`: Math macro in jinja templates. -* :ghissue:`4595`: ipython notebook XSS vulnerable -* :ghissue:`4776`: Manually document py3compat module. -* :ghissue:`4686`: For-in loop on an array in cell.js -* :ghissue:`3605`: Modal UI -* :ghissue:`4769`: Ipython 2.0 will not startup on py27 on windows -* :ghissue:`4482`: reveal.js converter not including CDN by default? -* :ghissue:`4761`: ipv6 address triggers cookie exception -* :ghissue:`4580`: rename or remove %profile magic -* :ghissue:`4643`: Docstring does not open properly -* :ghissue:`4714`: Static URLs are not auto-versioned -* :ghissue:`2573`: document code mirror keyboard shortcuts -* :ghissue:`4717`: hang in parallel.Client when using SSHAgent -* :ghissue:`4544`: Clarify the requirement for pyreadline on Windows -* :ghissue:`3451`: revisit REST /new handler to avoid systematic crawling. -* :ghissue:`2922`: File => Save as '.py' saves magic as code -* :ghissue:`4728`: Copy/Paste stripping broken in version > 0.13.x in QTConsole -* :ghissue:`4539`: Nbconvert: Latex to PDF conversion fails on notebooks with accented letters -* :ghissue:`4721`: purge_results with jobid crashing - looking for insight -* :ghissue:`4620`: Notebook with ? in title defies autosave, renaming and deletion. -* :ghissue:`4574`: Hash character in notebook name breaks a lot of things -* :ghissue:`4709`: input_prefilter hook not called -* :ghissue:`1680`: qtconsole should support --no-banner and custom banner -* :ghissue:`4689`: IOStream IP address configurable -* :ghissue:`4698`: Missing "if __name__ == '__main__':" check in /usr/bin/ipython -* :ghissue:`4191`: NBConvert: markdown inline and locally referenced files have incorrect file location for latex -* :ghissue:`2865`: %%!? does not display the shell execute docstring -* :ghissue:`1551`: Notebook should be saved before printing -* :ghissue:`4612`: remove `Configurable.created` ? -* :ghissue:`4629`: Lots of tests fail due to space in sys.executable -* :ghissue:`4644`: Fixed URLs for notebooks -* :ghissue:`4621`: IPython 1.1.0 Qtconsole syntax highlighting highlights python 2 only built-ins when using python 3 -* :ghissue:`2923`: Move Delete Button Away from Save Button in the HTML notebook toolbar -* :ghissue:`4615`: UnicodeDecodeError -* :ghissue:`4431`: ipython slow in os x mavericks? -* :ghissue:`4538`: DOC: document how to change ipcontroller-engine.json in case controller was started with --ip="*" -* :ghissue:`4551`: Serialize methods and closures -* :ghissue:`4081`: [Nbconvert][reveal] link to font awesome ? -* :ghissue:`4602`: "ipcluster stop" fails after "ipcluster start --daemonize" using python3.3 -* :ghissue:`4578`: NBconvert fails with unicode errors when `--stdout` and file redirection is specified and HTML entities are present -* :ghissue:`4600`: Renaming new notebook to an exist name silently deletes the old one -* :ghissue:`4598`: Qtconsole docstring pop-up fails on method containing defaulted enum argument -* :ghissue:`951`: Remove Tornado monkeypatch -* :ghissue:`4564`: Notebook save failure -* :ghissue:`4562`: nbconvert: Default encoding problem on OS X -* :ghissue:`1675`: add file_to_run=file.ipynb capability to the notebook -* :ghissue:`4516`: `ipython console` doesn't send a `shutdown_request` -* :ghissue:`3043`: can't restart pdb session in ipython -* :ghissue:`4524`: Fix bug with non ascii passwords in notebook login -* :ghissue:`1866`: problems rendering an SVG? -* :ghissue:`4520`: unicode error when trying Audio('data/Bach Cello Suite #3.wav') -* :ghissue:`4493`: Qtconsole cannot print an ISO8601 date at nanosecond precision -* :ghissue:`4502`: intermittent parallel test failure test_purge_everything -* :ghissue:`4495`: firefox 25.0: notebooks report "Notebook save failed", .py script save fails, but .ipynb save succeeds -* :ghissue:`4245`: nbconvert latex: code highlighting causes error -* :ghissue:`4486`: Test for whether inside virtualenv does not work if directory is symlinked -* :ghissue:`4485`: Incorrect info in "Messaging in IPython" documentation. -* :ghissue:`4447`: Ipcontroller broken in current HEAD on windows -* :ghissue:`4241`: Audio display object -* :ghissue:`4463`: Error on empty c.Session.key -* :ghissue:`4454`: UnicodeDecodeError when starting Ipython notebook on a directory containing a file with a non-ascii character -* :ghissue:`3801`: Autocompletion: Fix issue #3723 -- ordering of completions for magic commands and variables with same name -* :ghissue:`3723`: Code completion: 'matplotlib' and '%matplotlib' -* :ghissue:`4396`: Always checkpoint al least once ? -* :ghissue:`2524`: [Notebook] Clear kernel queue -* :ghissue:`2292`: Client side tests for the notebook -* :ghissue:`4424`: Dealing with images in multidirectory environment -* :ghissue:`4388`: Make writing configurable magics easier -* :ghissue:`852`: Notebook should be saved before downloading -* :ghissue:`3708`: ipython profile locate should also work -* :ghissue:`1349`: `?` may generate hundreds of cell -* :ghissue:`4381`: Using hasattr for trait_names instead of just looking for it directly/using __dir__? -* :ghissue:`4361`: Crash Ultratraceback/ session history -* :ghissue:`3044`: IPython notebook autocomplete for filename string converts multiple spaces to a single space -* :ghissue:`3346`: Up arrow history search shows duplicates in Qtconsole -* :ghissue:`3496`: Fix import errors when running tests from the source directory -* :ghissue:`4114`: If default profile doesn't exist, can't install mathjax to any location -* :ghissue:`4335`: TestPylabSwitch.test_qt fails -* :ghissue:`4291`: serve like option for nbconvert --to latex -* :ghissue:`1824`: Exception before prompting for password during ssh connection -* :ghissue:`4309`: Error in nbconvert - closing tag is not inserted in HTML under some circumstances -* :ghissue:`4351`: /parallel/apps/launcher.py error -* :ghissue:`3603`: Upcoming issues with nbconvert -* :ghissue:`4296`: sync_imports() fails in python 3.3 -* :ghissue:`4339`: local mathjax install doesn't work -* :ghissue:`4334`: NotebookApp.webapp_settings static_url_prefix causes crash -* :ghissue:`4308`: Error when use "ipython notebook" in win7 64 with python2.7.3 64. -* :ghissue:`4317`: Relative imports broken in the notebook (Windows) -* :ghissue:`3658`: Saving Notebook clears "Kernel Busy" status from the page and titlebar -* :ghissue:`4312`: Link broken on ipython-doc stable -* :ghissue:`1093`: Add boundary options to %load -* :ghissue:`3619`: Multi-dir webservice design -* :ghissue:`4299`: Nbconvert, default_preprocessors to list of dotted name not list of obj -* :ghissue:`3210`: IPython.parallel tests seem to hang on ShiningPanda -* :ghissue:`4280`: MathJax Automatic Line Breaking -* :ghissue:`4039`: Celltoolbar example issue -* :ghissue:`4247`: nbconvert --to latex: error when converting greek letter -* :ghissue:`4273`: %%capture not capturing rich objects like plots (IPython 1.1.0) -* :ghissue:`3866`: Vertical offsets in LaTeX output for nbconvert -* :ghissue:`3631`: xkcd mode for the IPython notebook -* :ghissue:`4243`: Test exclusions not working on Windows -* :ghissue:`4256`: IPython no longer handles unicode file names -* :ghissue:`3656`: Audio displayobject -* :ghissue:`4223`: Double output on Ctrl-enter-enter -* :ghissue:`4184`: nbconvert: use r pygmentize backend when highlighting "%%R" cells -* :ghissue:`3851`: Adds an explicit newline for pretty-printing. -* :ghissue:`3622`: Drop fakemodule -* :ghissue:`4122`: Nbconvert [windows]: Inconsistent line endings in markdown cells exported to latex -* :ghissue:`3819`: nbconvert add extra blank line to code block on Windows. -* :ghissue:`4203`: remove spurious print statement from parallel annoted functions -* :ghissue:`4200`: Notebook: merging a heading cell and markdown cell cannot be undone -* :ghissue:`3747`: ipynb -> ipynb transformer -* :ghissue:`4024`: nbconvert markdown issues -* :ghissue:`3903`: on Windows, 'ipython3 nbconvert "C:/blabla/first_try.ipynb" --to slides' gives an unexpected result, and '--post serve' fails -* :ghissue:`4095`: Catch js error in append html in stream/pyerr -* :ghissue:`1880`: Add parallelism to test_pr -* :ghissue:`4085`: nbconvert: Fix sphinx preprocessor date format string for Windows -* :ghissue:`4156`: Specifying --gui=tk at the command line -* :ghissue:`4146`: Having to prepend 'files/' to markdown image paths is confusing -* :ghissue:`3818`: nbconvert can't handle Heading with Chinese characters on Japanese Windows OS. -* :ghissue:`4134`: multi-line parser fails on ''' in comment, qtconsole and notebook. -* :ghissue:`3998`: sample custom.js needs to be updated -* :ghissue:`4078`: StoreMagic.autorestore not working in 1.0.0 -* :ghissue:`3990`: Buitlin `input` doesn't work over zmq -* :ghissue:`4015`: nbconvert fails to convert all the content of a notebook -* :ghissue:`4059`: Issues with Ellipsis literal in Python 3 -* :ghissue:`2310`: "ZMQError: Interrupted system call" from RichIPythonWidget -* :ghissue:`3807`: qtconsole ipython 0.13.2 - html/xhtml export fails -* :ghissue:`4103`: Wrong default argument of DirectView.clear -* :ghissue:`4100`: parallel.client.client references undefined error.EngineError -* :ghissue:`484`: Drop nosepatch -* :ghissue:`3350`: Added longlist support in ipdb. -* :ghissue:`1591`: Keying 'q' doesn't quit the interactive help in Wins7 -* :ghissue:`40`: The tests in test_process fail under Windows -* :ghissue:`3744`: capture rich output as well as stdout/err in capture_output -* :ghissue:`3742`: %%capture to grab rich display outputs -* :ghissue:`3863`: Added working speaker notes for slides. -* :ghissue:`4013`: Iptest fails in dual python installation -* :ghissue:`4005`: IPython.start_kernel doesn't work. -* :ghissue:`4020`: IPython parallel map fails on numpy arrays -* :ghissue:`3914`: nbconvert: Transformer tests -* :ghissue:`3923`: nbconvert: Writer tests -* :ghissue:`3945`: nbconvert: commandline tests fail Win7x64 Py3.3 -* :ghissue:`3937`: make tab visible in codemirror and light red background -* :ghissue:`3935`: No feedback for mixed tabs and spaces -* :ghissue:`3933`: nbconvert: Post-processor tests -* :ghissue:`3977`: unable to complete remote connections for two-process -* :ghissue:`3939`: minor checkpoint cleanup -* :ghissue:`3955`: complete on % for magic in notebook -* :ghissue:`3954`: all magics should be listed when completing on % -* :ghissue:`3980`: nbconvert rst output lacks needed blank lines -* :ghissue:`3968`: TypeError: super() argument 1 must be type, not classobj (Python 2.6.6) -* :ghissue:`3880`: nbconvert: R&D remaining tests -* :ghissue:`2440`: IPEP 4: Python 3 Compatibility diff --git a/docs/source/whatsnew/github-stats-3.rst b/docs/source/whatsnew/github-stats-3.rst deleted file mode 100644 index c4fb3dff9e4..00000000000 --- a/docs/source/whatsnew/github-stats-3.rst +++ /dev/null @@ -1,269 +0,0 @@ -.. _issues_list_3: - -Issues closed in the 3.x development cycle -========================================== - - -Issues closed in 3.2.1 ----------------------- - -GitHub stats for 2015/06/22 - 2015/07/12 (since 3.2) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 1 issue and merged 3 pull requests. -The full list can be seen `on GitHub `__ - -The following 5 authors contributed 9 commits. - -* Benjamin Ragan-Kelley -* Matthias Bussonnier -* Nitin Dahyabhai -* Sebastiaan Mathot -* Thomas Kluyver - - -Issues closed in 3.2 --------------------- - -GitHub stats for 2015/04/03 - 2015/06/21 (since 3.1) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 7 issues and merged 30 pull requests. -The full list can be seen `on GitHub `__ - -The following 15 authors contributed 74 commits. - -* Benjamin Ragan-Kelley -* Brian Gough -* Damián Avila -* Ian Barfield -* Jason Grout -* Jeff Hussmann -* Jessica B. Hamrick -* Kyle Kelley -* Matthias Bussonnier -* Nicholas Bollweg -* Randy Lai -* Scott Sanderson -* Sylvain Corlay -* Thomas A Caswell -* Thomas Kluyver - - -Issues closed in 3.1 --------------------- - -GitHub stats for 2015/02/27 - 2015/04/03 (since 3.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 46 issues and merged 133 pull requests. -The full list can be seen `on GitHub `__. - -The following 33 authors contributed 344 commits: - -* Abe Guerra -* Adal Chiriliuc -* Benjamin Ragan-Kelley -* Brian Drawert -* Fernando Perez -* Gareth Elston -* Gert-Ludwig Ingold -* Giuseppe Venturini -* Jakob Gager -* Jan Schulz -* Jason Grout -* Jessica B. Hamrick -* Jonathan Frederic -* Justin Tyberg -* Lorena Pantano -* mashenjun -* Mathieu -* Matthias Bussonnier -* Morten Enemark Lund -* Naveen Nathan -* Nicholas Bollweg -* onesandzeroes -* Patrick Snape -* Peter Parente -* RickWinter -* Robert Smith -* Ryan Nelson -* Scott Sanderson -* Sylvain Corlay -* Thomas Kluyver -* tmtabor -* Wieland Hoffmann -* Yuval Langer - - -Issues closed in 3.0 --------------------- - -GitHub stats for 2014/04/02 - 2015/02/13 (since 2.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 469 issues and merged 925 pull requests. -The full list can be seen `on GitHub `__. - -The following 155 authors contributed 5975 commits. - -* A.J. Holyoake -* abalkin -* Adam Hodgen -* Adrian Price-Whelan -* Amin Bandali -* Andreas Amann -* Andrew Dawes -* Andrew Jesaitis -* Andrew Payne -* AnneTheAgile -* Aron Ahmadia -* Ben Duffield -* Benjamin ABEL -* Benjamin Ragan-Kelley -* Benjamin Schultz -* Björn Grüning -* Björn Linse -* Blake Griffith -* Boris Egorov -* Brian E. Granger -* bsvh -* Carlos Cordoba -* Cedric GESTES -* cel -* chebee7i -* Christoph Gohlke -* CJ Carey -* Cyrille Rossant -* Dale Jung -* Damián Avila -* Damon Allen -* Daniel B. Vasquez -* Daniel Rocco -* Daniel Wehner -* Dav Clark -* David Hirschfeld -* David Neto -* dexterdev -* Dimitry Kloper -* dongweiming -* Doug Blank -* drevicko -* Dustin Rodriguez -* Eric Firing -* Eric Galloway -* Erik M. Bray -* Erik Tollerud -* Ezequiel (Zac) Panepucci -* Fernando Perez -* foogunlana -* Francisco de la Peña -* George Titsworth -* Gordon Ball -* gporras -* Grzegorz Rożniecki -* Helen ST -* immerrr -* Ingolf Becker -* Jakob Gager -* James Goppert -* James Porter -* Jan Schulz -* Jason Goad -* Jason Gors -* Jason Grout -* Jason Newton -* jdavidheiser -* Jean-Christophe Jaskula -* Jeff Hemmelgarn -* Jeffrey Bush -* Jeroen Demeyer -* Jessica B. Hamrick -* Jessica Frazelle -* jhemmelg -* Jim Garrison -* Joel Nothman -* Johannes Feist -* John Stowers -* John Zwinck -* jonasc -* Jonathan Frederic -* Juergen Hasch -* Julia Evans -* Justyna Ilczuk -* Jörg Dietrich -* K.-Michael Aye -* Kalibri -* Kester Tong -* Kyle Kelley -* Kyle Rawlins -* Lev Abalkin -* Manuel Riel -* Martin Bergtholdt -* Martin Spacek -* Mateusz Paprocki -* Mathieu -* Matthias Bussonnier -* Maximilian Albert -* mbyt -* MechCoder -* Mohan Raj Rajamanickam -* mvr -* Narahari -* Nathan Goldbaum -* Nathan Heijermans -* Nathaniel J. Smith -* ncornette -* Nicholas Bollweg -* Nick White -* Nikolay Koldunov -* Nile Geisinger -* Olga Botvinnik -* Osada Paranaliyanage -* Pankaj Pandey -* Pascal Bugnion -* patricktokeeffe -* Paul Ivanov -* Peter Odding -* Peter Parente -* Peter Würtz -* Phil Elson -* Phillip Nordwall -* Pierre Gerold -* Pierre Haessig -* Raffaele De Feo -* Ramiro Gómez -* Reggie Pierce -* Remi Rampin -* Renaud Richardet -* Richard Everson -* Scott Sanderson -* Silvia Vinyes -* Simon Guillot -* Spencer Nelson -* Stefan Zimmermann -* Steve Chan -* Steven Anton -* Steven Silvester -* sunny -* Susan Tan -* Sylvain Corlay -* Tarun Gaba -* Thomas Ballinger -* Thomas Kluyver -* Thomas Robitaille -* Thomas Spura -* Tobias Oberstein -* Torsten Bittner -* unknown -* v923z -* vaibhavsagar -* W. Trevor King -* weichm -* Xiuming Chen -* Yaroslav Halchenko -* zah diff --git a/docs/source/whatsnew/github-stats-4.rst b/docs/source/whatsnew/github-stats-4.rst deleted file mode 100644 index b7410fafe02..00000000000 --- a/docs/source/whatsnew/github-stats-4.rst +++ /dev/null @@ -1,178 +0,0 @@ -.. _issues_list_4: - -Issues closed in the 4.x development cycle -========================================== - - -Issues closed in 4.2 --------------------- - -GitHub stats for 2015/02/02 - 2016/04/20 (since 4.1) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 10 issues and merged 22 pull requests. -The full list can be seen `on GitHub `__ - -The following 10 authors contributed 27 commits. - -* Benjamin Ragan-Kelley -* Carlos Cordoba -* Gökhan Karabulut -* Jonas Rauber -* Matthias Bussonnier -* Paul Ivanov -* Sebastian Bank -* Thomas A Caswell -* Thomas Kluyver -* Vincent Woo - - -Issues closed in 4.1 --------------------- - -GitHub stats for 2015/08/12 - 2016/02/02 (since 4.0.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 60 issues and merged 148 pull requests. -The full list can be seen `on GitHub `__ - -The following 52 authors contributed 468 commits. - -* Aaron Meurer -* Alexandre Avanian -* Anthony Sottile -* Antony Lee -* Arthur Loder -* Ben Kasel -* Ben Rousch -* Benjamin Ragan-Kelley -* bollwyvl -* Carol Willing -* Christopher Roach -* Douglas La Rocca -* Fairly -* Fernando Perez -* Frank Sachsenheim -* Guillaume DOUMENC -* Gábor Luk -* Hoyt Koepke -* Ivan Timokhin -* Jacob Niehus -* JamshedVesuna -* Jan Schulz -* Jan-Philip Gehrcke -* jc -* Jessica B. Hamrick -* jferrara -* John Bohannon -* John Kirkham -* Jonathan Frederic -* Kyle Kelley -* Lev Givon -* Lilian Besson -* lingxz -* Matthias Bussonnier -* memeplex -* Michael Droettboom -* naught101 -* Peter Waller -* Pierre Gerold -* Rémy Léone -* Scott Sanderson -* Shanzhuo Zhang -* Sylvain Corlay -* Tayfun Sen -* Thomas A Caswell -* Thomas Ballinger -* Thomas Kluyver -* Vincent Legoll -* Wouter Bolsterlee -* xconverge -* Yuri Numerov -* Zachary Pincus - - -Issues closed in 4.0 --------------------- - - -GitHub stats for 2015/02/27 - 2015/08/11 (since 3.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 35 issues and merged 125 pull requests. -The full list can be seen `on GitHub `__ - -The following 69 authors contributed 1186 commits. - -* Abe Guerra -* Adal Chiriliuc -* Alexander Belopolsky -* Andrew Murray -* Antonio Russo -* Benjamin Ragan-Kelley -* Björn Linse -* Brian Drawert -* chebee7i -* Daniel Rocco -* Donny Winston -* Drekin -* Erik Hvatum -* Fernando Perez -* Francisco de la Peña -* Frazer McLean -* Gareth Elston -* Gert-Ludwig Ingold -* Giuseppe Venturini -* Ian Barfield -* Ivan Pozdeev -* Jakob Gager -* Jan Schulz -* Jason Grout -* Jeff Hussmann -* Jessica B. Hamrick -* Joe Borg -* Joel Nothman -* Johan Forsberg -* Jonathan Frederic -* Justin Tyberg -* Koen van Besien -* Kyle Kelley -* Lorena Pantano -* Lucretiel -* Marin Gilles -* mashenjun -* Mathieu -* Matthias Bussonnier -* Merlijn van Deen -* Mikhail Korobov -* Naveen Nathan -* Nicholas Bollweg -* nottaanibot -* Omer Katz -* onesandzeroes -* Patrick Snape -* patter001 -* Peter Parente -* Pietro Battiston -* RickWinter -* Robert Smith -* Ryan Nelson -* Scott Sanderson -* Sebastiaan Mathot -* Sylvain Corlay -* thethomask -* Thomas A Caswell -* Thomas Adriaan Hellinger -* Thomas Kluyver -* Tianhui Michael Li -* tmtabor -* unknown -* Victor Ramirez -* Volker Braun -* Wieland Hoffmann -* Yuval Langer -* Zoltán Vörös -* Élie Michel diff --git a/docs/source/whatsnew/github-stats-5.rst b/docs/source/whatsnew/github-stats-5.rst deleted file mode 100644 index 97893cce1c6..00000000000 --- a/docs/source/whatsnew/github-stats-5.rst +++ /dev/null @@ -1,201 +0,0 @@ -.. _issues_list_5: - -Issues closed in the 5.x development cycle -========================================== - -Issues closed in 5.6 --------------------- - -GitHub stats for 2017/09/15 - 2018/04/02 (tag: 5.5.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 2 issues and merged 28 pull requests. -The full list can be seen `on GitHub `__ - -The following 10 authors contributed 47 commits. - -* Benjamin Ragan-Kelley -* Henry Fredrick Schreiner -* Joris Van den Bossche -* Matthias Bussonnier -* Mradul Dubey -* Roshan Rao -* Samuel Lelièvre -* Teddy Rendahl -* Thomas A Caswell -* Thomas Kluyver - -Issues closed in 5.4 --------------------- - -GitHub stats for 2017/02/24 - 2017/05/30 (tag: 5.3.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 8 issues and merged 43 pull requests. -The full list can be seen `on GitHub `__ - -The following 11 authors contributed 64 commits. - -* Benjamin Ragan-Kelley -* Carol Willing -* Kyle Kelley -* Leo Singer -* Luke Pfister -* Lumir Balhar -* Matthias Bussonnier -* meeseeksdev[bot] -* memeplex -* Thomas Kluyver -* Ximin Luo - -Issues closed in 5.3 --------------------- - -GitHub stats for 2017/02/24 - 2017/05/30 (tag: 5.3.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 6 issues and merged 28 pull requests. -The full list can be seen `on GitHub `__ - -The following 11 authors contributed 53 commits. - -* Benjamin Ragan-Kelley -* Carol Willing -* Justin Jent -* Kyle Kelley -* Lumir Balhar -* Matthias Bussonnier -* meeseeksdev[bot] -* Segev Finer -* Steven Maude -* Thomas A Caswell -* Thomas Kluyver - - -Issues closed in 5.2 --------------------- - -GitHub stats for 2016/08/13 - 2017/01/29 (tag: 5.1.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 30 issues and merged 74 pull requests. -The full list can be seen `on GitHub `__ - -The following 40 authors contributed 434 commits. - -* Adam Eury -* anantkaushik89 -* anatoly techtonik -* Benjamin Ragan-Kelley -* Bibo Hao -* Carl Smith -* Carol Willing -* Chilaka Ramakrishna -* Christopher Welborn -* Denis S. Tereshchenko -* Diego Garcia -* fatData -* Fermi paradox -* Fernando Perez -* fuho -* Hassan Kibirige -* Jamshed Vesuna -* Jens Hedegaard Nielsen -* Jeroen Demeyer -* kaushikanant -* Kenneth Hoste -* Keshav Ramaswamy -* Kyle Kelley -* Matteo -* Matthias Bussonnier -* mbyt -* memeplex -* Moez Bouhlel -* Pablo Galindo -* Paul Ivanov -* pietvo -* Piotr Przetacznik -* Rounak Banik -* sachet-mittal -* Srinivas Reddy Thatiparthy -* Tamir Bahar -* Thomas A Caswell -* Thomas Kluyver -* tillahoffmann -* Yuri Numerov - - -Issues closed in 5.1 --------------------- - -GitHub stats for 2016/07/08 - 2016/08/13 (tag: 5.0.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 33 issues and merged 43 pull requests. -The full list can be seen `on GitHub `__ - -The following 17 authors contributed 129 commits. - -* Antony Lee -* Benjamin Ragan-Kelley -* Carol Willing -* Danilo J. S. Bellini -* 小明 (`dongweiming `__) -* Fernando Perez -* Gavin Cooper -* Gil Forsyth -* Jacob Niehus -* Julian Kuhlmann -* Matthias Bussonnier -* Michael Pacer -* Nik Nyby -* Pavol Juhas -* Luke Deen Taylor -* Thomas Kluyver -* Tamir Bahar - - -Issues closed in 5.0 --------------------- - -GitHub stats for 2016/07/05 - 2016/07/07 (tag: 5.0.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 95 issues and merged 191 pull requests. -The full list can be seen `on GitHub `__ - -The following 27 authors contributed 229 commits. - -* Adam Greenhall -* Adrian -* Antony Lee -* Benjamin Ragan-Kelley -* Carlos Cordoba -* Carol Willing -* Chris -* Craig Citro -* Dmitry Zotikov -* Fernando Perez -* Gil Forsyth -* Jason Grout -* Jonathan Frederic -* Jonathan Slenders -* Justin Zymbaluk -* Kelly Liu -* klonuo -* Matthias Bussonnier -* nvdv -* Pavol Juhas -* Pierre Gerold -* sukisuki -* Sylvain Corlay -* Thomas A Caswell -* Thomas Kluyver -* Trevor Bekolay -* Yuri Numerov diff --git a/docs/source/whatsnew/github-stats-6.rst b/docs/source/whatsnew/github-stats-6.rst deleted file mode 100644 index f926ed7427f..00000000000 --- a/docs/source/whatsnew/github-stats-6.rst +++ /dev/null @@ -1,184 +0,0 @@ -Issues closed in the 6.x development cycle -========================================== - -Issues closed in 6.3 --------------------- - - -GitHub stats for 2017/09/15 - 2018/04/02 (tag: 6.2.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 10 issues and merged 50 pull requests. -The full list can be seen `on GitHub `__ - -The following 35 authors contributed 253 commits. - -* Anatoly Techtonik -* Antony Lee -* Benjamin Ragan-Kelley -* Corey McCandless -* Craig Citro -* Cristian Ciupitu -* David Cottrell -* David Straub -* Doug Latornell -* Fabio Niephaus -* Gergely Nagy -* Henry Fredrick Schreiner -* Hugo -* Ismael Venegas Castelló -* Ivan Gonzalez -* J Forde -* Jeremy Sikes -* Joris Van den Bossche -* Lesley Cordero -* luzpaz -* madhu94 -* Matthew R. Scott -* Matthias Bussonnier -* Matthias Geier -* Olesya Baranova -* Peter Williams -* Rastislav Barlik -* Roshan Rao -* rs2 -* Samuel Lelièvre -* Shailyn javier Ortiz jimenez -* Sjoerd de Vries -* Teddy Rendahl -* Thomas A Caswell -* Thomas Kluyver - -Issues closed in 6.2 --------------------- - -GitHub stats for 2017/05/31 - 2017/09/15 (tag: 6.1.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 3 issues and merged 37 pull requests. -The full list can be seen `on GitHub `__ - -The following 32 authors contributed 196 commits. - -* adityausathe -* Antony Lee -* Benjamin Ragan-Kelley -* Carl Smith -* Eren Halici -* Erich Spaker -* Grant Nestor -* Jean Cruypenynck -* Jeroen Demeyer -* jfbu -* jlstevens -* jus1tin -* Kyle Kelley -* M Pacer -* Marc Richter -* Marius van Niekerk -* Matthias Bussonnier -* mpacer -* Mradul Dubey -* ormung -* pepie34 -* Ritesh Kadmawala -* ryan thielke -* Segev Finer -* Srinath -* Srinivas Reddy Thatiparthy -* Steven Maude -* Sudarshan Raghunathan -* Sudarshan Rangarajan -* Thomas A Caswell -* Thomas Ballinger -* Thomas Kluyver - - -Issues closed in 6.1 --------------------- - -GitHub stats for 2017/04/19 - 2017/05/30 (tag: 6.0.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 10 issues and merged 43 pull requests. -The full list can be seen `on GitHub `__ - -The following 26 authors contributed 116 commits. - -* Alex Alekseyev -* Benjamin Ragan-Kelley -* Brian E. Granger -* Christopher C. Aycock -* Dave Willmer -* David Bradway -* ICanWaitAndFishAllDay -* Ignat Shining -* Jarrod Janssen -* Joshua Storck -* Luke Pfister -* Matthias Bussonnier -* Matti Remes -* meeseeksdev[bot] -* memeplex -* Ming Zhang -* Nick Weseman -* Paul Ivanov -* Piotr Zielinski -* ryan thielke -* sagnak -* Sang Min Park -* Srinivas Reddy Thatiparthy -* Steve Bartz -* Thomas Kluyver -* Tory Haavik - - -Issues closed in 6.0 --------------------- - -GitHub stats for 2017/04/10 - 2017/04/19 (milestone: 6.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 49 issues and merged 145 pull requests. -The full list can be seen `on GitHub `__ - -The following 34 authors contributed 176 commits. - -* Adam Eury -* anantkaushik89 -* Antonino Ingargiola -* Benjamin Ragan-Kelley -* Carol Willing -* Chilaka Ramakrishna -* chillaranand -* Denis S. Tereshchenko -* Diego Garcia -* fatData -* Fermi paradox -* fuho -* Grant Nestor -* Ian Rose -* Jeroen Demeyer -* kaushikanant -* Keshav Ramaswamy -* Matteo -* Matthias Bussonnier -* mbyt -* Michael Käufl -* michaelpacer -* Moez Bouhlel -* Pablo Galindo -* Paul Ivanov -* Piotr Przetacznik -* Rounak Banik -* sachet-mittal -* Srinivas Reddy Thatiparthy -* Tamir Bahar -* Thomas Hisch -* Thomas Kluyver -* Utkarsh Upadhyay -* Yuri Numerov diff --git a/docs/source/whatsnew/github-stats-7.rst b/docs/source/whatsnew/github-stats-7.rst deleted file mode 100644 index 07146effbc8..00000000000 --- a/docs/source/whatsnew/github-stats-7.rst +++ /dev/null @@ -1,64 +0,0 @@ -Issues closed in the 7.x development cycle -========================================== - -Issues closed in 7.0 --------------------- - -GitHub stats for 2018/07/29 - 2018/09/27 (since tag: 6.5.0) - -These lists are automatically generated, and may be incomplete or contain duplicates. - -We closed 20 issues and merged 76 pull requests. -The full list can be seen `on GitHub `__ - -The following 49 authors contributed 471 commits. - -* alphaCTzo7G -* Alyssa Whitwell -* Anatol Ulrich -* apunisal -* Benjamin Ragan-Kelley -* Chaz Reid -* Christoph -* Dale Jung -* Dave Hirschfeld -* dhirschf -* Doug Latornell -* Fernando Perez -* Fred Mitchell -* Gabriel Potter -* gpotter2 -* Grant Nestor -* hongshaoyang -* Hugo -* J Forde -* Jonathan Slenders -* Jörg Dietrich -* Kyle Kelley -* luz.paz -* M Pacer -* Matthew R. Scott -* Matthew Seal -* Matthias Bussonnier -* meeseeksdev[bot] -* Michael Käufl -* Olesya Baranova -* oscar6echo -* Paul Ganssle -* Paul Ivanov -* Peter Parente -* prasanth -* Shailyn javier Ortiz jimenez -* Sourav Singh -* Srinivas Reddy Thatiparthy -* Steven Silvester -* stonebig -* Subhendu Ranjan Mishra -* Takafumi Arakaki -* Thomas A Caswell -* Thomas Kluyver -* Todd -* Wei Yen -* Yarko Tymciurak -* Yutao Yuan -* Zi Chong Kao diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index d96b53879b5..57f6c7ab190 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -19,22 +19,13 @@ What's new in IPython .. only:: ipydev Development version in-progress features: - - .. toctree:: - - development - -.. - this makes a hidden toctree that keeps sphinx from complaining about - documents included nowhere when building docs for stable - -.. only:: ipystable .. toctree:: - :hidden: + :maxdepth: 1 development + This section documents the changes that have been made in various versions of IPython. Users should consult these pages to learn about new features, bug fixes and backwards incompatibilities. Developers should summarize the @@ -43,27 +34,24 @@ development work they do here in a user friendly format. .. toctree:: :maxdepth: 1 + version9 + version8 version7 - github-stats-7 version6 - github-stats-6 version5 - github-stats-5 version4 - github-stats-4 version3 - github-stats-3 version3_widget_migration version2.0 - github-stats-2.0 version1.0 - github-stats-1.0 version0.13 - github-stats-0.13 version0.12 - github-stats-0.12 version0.11 - github-stats-0.11 version0.10 version0.9 version0.8 + +.. + The development.rst is included in the ipydev toctree above. + For stable builds, it's automatically excluded by the only directive, + so no hidden toctree is needed here. diff --git a/docs/source/whatsnew/version0.10.rst b/docs/source/whatsnew/version0.10.rst index 6073216395a..d88132f0193 100644 --- a/docs/source/whatsnew/version0.10.rst +++ b/docs/source/whatsnew/version0.10.rst @@ -25,14 +25,14 @@ Highlights of this release: hence plots were blocking with GTK. Since this is the default matplotlib backend on Unix systems, this was a major annoyance for many users. Many thanks to Paul Ivanov for helping resolve this issue. - + - Fix IOError bug on Windows when used with -gthread. - Work robustly if $HOME is missing from environment. - Better POSIX support in ssh scripts (remove bash-specific idioms). - Improved support for non-ascii characters in log files. - Work correctly in environments where GTK can be imported but not started (such as a linux text console without X11). - + For this release we merged 24 commits, contributed by the following people (please let us know if we omitted your name and we'll gladly fix this in the notes for the future): @@ -77,7 +77,7 @@ Highlights of this release: in remote tasks, as well as providing better control for remote task IDs. - New IPython Sphinx directive contributed by John Hunter. You can use this - directive to mark blocks in reSructuredText documents as containing IPython + directive to mark blocks in reStructuredText documents as containing IPython syntax (including figures) and the will be executed during the build: .. sourcecode:: ipython @@ -250,7 +250,7 @@ New features * The developer guidelines in the documentation have been updated to explain our workflow using :command:`bzr` and Launchpad. - + * Fully refactored :command:`ipcluster` command line program for starting IPython clusters. This new version is a complete rewrite and 1) is fully cross platform (we now use Twisted's process management), 2) has much @@ -268,7 +268,7 @@ New features * sh profile: easy way to give 'title' to prompt: assign to variable '_prompt_title'. It looks like this:: - + [~]|1> _prompt_title = 'sudo!' sudo![~]|2> @@ -284,7 +284,7 @@ Bug fixes * Fix #368719, removed top-level debian/ directory to make the job of Debian packagers easier. - + * Fix #291143 by including man pages contributed by Stephan Peijnik from the Debian project. @@ -333,7 +333,7 @@ Bug fixes * Many other small bug fixes not listed here by number (see the bzr log for more info). - + Backwards incompatible changes ------------------------------ @@ -357,4 +357,3 @@ Backwards incompatible changes * Remove ipy_leo.py. You can use :command:`easy_install ipython-extension` to get it. (done to decouple it from ipython release cycle) - diff --git a/docs/source/whatsnew/version0.11.rst b/docs/source/whatsnew/version0.11.rst index b03293717f6..710853658b7 100644 --- a/docs/source/whatsnew/version0.11.rst +++ b/docs/source/whatsnew/version0.11.rst @@ -84,7 +84,7 @@ A quick summary of the major changes (see below for details): demonstration`__ of the features it provides. .. __: http://pirsquared.org/blog/2011/07/28/vim-ipython/ - + * **Integration into Microsoft Visual Studio**. Thanks to the work of the Microsoft `Python Tools for Visual Studio`__ team, this version of IPython has been integrated into Microsoft Visual Studio's Python tools open source @@ -114,7 +114,7 @@ A quick summary of the major changes (see below for details): * **Pasting of code with prompts**. IPython now intelligently strips out input prompts , be they plain Python ones (``>>>`` and ``...``) or IPython ones (``In [N]:`` and ``...:``). More details :ref:`here `. - + Authors and support ------------------- @@ -154,8 +154,8 @@ cycle from several institutions: .. __: http://modular.math.washington.edu/grants/compmath09 -- Microsoft's team working on `Python Tools for Visual Studio`__ developed the - integraton of IPython into the Python plugin for Visual Studio 2010. +- Microsoft's team working on `Python Tools for Visual Studio`__ developed the + integratron of IPython into the Python plugin for Visual Studio 2010. .. __: http://pytools.codeplex.com @@ -195,19 +195,18 @@ series. A huge diff and ~2200 commits make up this cycle:: 2200 Since our move to github, 511 issues were closed, 226 of which were pull -requests and 285 regular issues (:ref:`a full list with links -` is available for those interested in the details). Github's +requests and 285 regular issues. Github's pull requests are a fantastic mechanism for reviewing code and building a shared ownership of the project, and we are making enthusiastic use of it. -.. Note:: +.. note:: This undercounts the number of issues closed in this development cycle, since we only moved to github for issue tracking in May 2010, but we have no way of collecting statistics on the number of issues closed in the old Launchpad bug tracker prior to that. - + .. _qtconsole_011: Qt Console @@ -264,7 +263,7 @@ reference docs. Refactoring ----------- -As of this release, a signifiant portion of IPython has been refactored. This +As of this release, a significant portion of IPython has been refactored. This refactoring is founded on a number of new abstractions. The main new classes that implement these abstractions are: @@ -309,7 +308,7 @@ be started by calling ``ipython qtconsole``. The protocol is :ref:`documented `. The parallel computing framework has also been rewritten using ZMQ. The -protocol is described :ref:`here `, and the code is in the +protocol is described in the ipyparallel documentation, and the code is in the new :mod:`IPython.parallel` module. .. _python3_011: @@ -414,7 +413,7 @@ Additional new features * The embedded shell has been refactored into a truly standalone subclass of :class:`InteractiveShell` called :class:`InteractiveShellEmbed`. All - embedding logic has been taken out of the base class and put into the + embedding logic has been taken out of the base class and put into the embedded subclass. * Added methods of :class:`~IPython.core.interactiveshell.InteractiveShell` to @@ -476,7 +475,7 @@ Additional new features resolution is done at a single point, when the parent :class:`~traitlets.HasTraitlets` is instantiated. -* :mod:`IPython.utils.ipstruct` has been refactored to be a subclass of +* :mod:`IPython.utils.ipstruct` has been refactored to be a subclass of dict. It also now has full docstrings and doctests. * Created a Traits like implementation in :mod:`traitlets`. This @@ -563,7 +562,7 @@ Backwards incompatible changes longer needed because of the new capabilities in :mod:`IPython.lib.inputhook`. -* New top-level sub-packages have been created: :mod:`IPython.core`, +* New top-level sub-packages have been created: :mod:`IPython.core`, :mod:`IPython.lib`, :mod:`IPython.utils`, :mod:`IPython.deathrow`, :mod:`IPython.quarantine`. All existing top-level modules have been moved to appropriate sub-packages. All internal import statements diff --git a/docs/source/whatsnew/version0.12.rst b/docs/source/whatsnew/version0.12.rst index 5d27504d4bc..61af0eb18f0 100644 --- a/docs/source/whatsnew/version0.12.rst +++ b/docs/source/whatsnew/version0.12.rst @@ -8,8 +8,6 @@ Release 0.12.1 IPython 0.12.1 is a bugfix release of 0.12, pulling only bugfixes and minor cleanup from 0.13, timed for the Ubuntu 12.04 LTS release. -See the :ref:`list of fixed issues ` for specific backported issues. - Release 0.12 ============ @@ -87,11 +85,11 @@ You can see all the relevant options with:: and just like the Qt console, you can start the notebook server with pylab support by using:: - + ipython notebook --pylab for floating matplotlib windows or:: - + ipython notebook --pylab inline for plotting support with automatically inlined figures. Note that it is now @@ -117,7 +115,7 @@ have embedded an IPython kernel. This is also something that we have wanted for a long time, and which is a culmination (as a team effort) of the work started last year during the 2010 Google Summer of Code project. - + Tabbed QtConsole ~~~~~~~~~~~~~~~~ @@ -167,7 +165,7 @@ the area where PyPy still lags most behind. But for everyday interactive use at the terminal, with this release and PyPy 1.7, things seem to work quite well from our admittedly limited testing. - + Other important new features ---------------------------- @@ -208,7 +206,7 @@ Other important new features Major Bugs fixed ---------------- -In this cycle, we have :ref:`closed over 500 issues `, but a +In this cycle, we have closed over 500 issues, but a few major ones merit special mention: * Simple configuration errors should no longer crash IPython. In 0.11, errors @@ -287,7 +285,7 @@ Backwards incompatible changes The full path will still work, and is necessary for using custom launchers not in IPython's launcher module. - + Further, MPIExec launcher names are now prefixed with just MPI, to better match other batch launchers, and be generally more intuitive. The MPIExec names are deprecated, but continue to work. @@ -307,8 +305,7 @@ Development summary and credits The previous version (IPython 0.11) was released on July 31 2011, so this release cycle was roughly 4 1/2 months long, we closed a total of 515 issues, -257 pull requests and 258 regular issues (a :ref:`detailed list -` is available). +257 pull requests and 258 regular issues. Many users and developers contributed code, features, bug reports and ideas to this release. Please do not hesitate in contacting us if we've failed to @@ -361,7 +358,7 @@ had commits from the following 45 contributors, a mix of new and regular names * Timo Paulssen * Valentin Haenel * Yaroslav Halchenko - + .. note:: This list was generated with the output of diff --git a/docs/source/whatsnew/version0.13.rst b/docs/source/whatsnew/version0.13.rst index 63140125a5a..55cc9632655 100644 --- a/docs/source/whatsnew/version0.13.rst +++ b/docs/source/whatsnew/version0.13.rst @@ -17,9 +17,7 @@ bug and regression fixes. The previous version (0.12) was released on December - a diff of 114226 lines. The amount of work included in this release is so large, that we can only cover -here the main highlights; please see our :ref:`detailed release statistics -` for links to every issue and pull request closed on GitHub -as well as a full list of individual contributors. +here the main highlights. Major Notebook improvements: new user interface and more @@ -114,13 +112,13 @@ can merge it for full production use as soon as possible. JavaScript refactoring ~~~~~~~~~~~~~~~~~~~~~~ - + All the client-side JavaScript has been decoupled to ease reuse of parts of the machinery without having to build a full-blown notebook. This will make it much easier to communicate with an IPython kernel from existing web pages and to integrate single cells into other sites, without loading the full notebook document-like UI. :ghpull:`1711`. - + This refactoring also enables the possibility of writing dynamic javascript widgets that are returned from Python code and that present an interactive view to the user, with callbacks in Javascript executing calls to the Kernel. This @@ -131,7 +129,7 @@ An example of this capability has been provided as a proof of concept in parallel engines, acting as a mini-console for parallel debugging and introspection. - + Improved tooltips ~~~~~~~~~~~~~~~~~ @@ -163,16 +161,16 @@ many bug fixes and minor changes to add polish and robustness throughout: example: ``ipython notebook path/`` will automatically set ``path/`` as the notebook directory, and ``ipython notebook path/foo.ipynb`` will further start with the ``foo.ipynb`` notebook opened. :ghpull:`1686`. - + * If a notebook directory is specified with ``--notebook-dir`` (or with the corresponding configuration flag ``NotebookManager.notebook_dir``), all kernels start in this directory. * Fix codemirror clearing of cells with ``Ctrl-Z``; :ghpull:`1965`. - + * Text (markdown) cells now line wrap correctly in the notebook, making them much easier to edit :ghpull:`1330`. - + * PNG and JPEG figures returned from plots can be interactively resized in the notebook, by dragging them from their lower left corner. :ghpull:`1832`. @@ -189,12 +187,12 @@ many bug fixes and minor changes to add polish and robustness throughout: * Using ``Ctrl-S`` (or ``Cmd-S`` on a Mac) actually saves the notebook rather than providing the fairly useless browser html save dialog. :ghpull:`1334`. - + * Allow accessing local files from the notebook (in urls), by serving any local file as the url ``files/``. This makes it possible to, for example, embed local images in a notebook. :ghpull:`1211`. - + Cell magics ----------- @@ -209,19 +207,19 @@ simple functions without the need for object orientation. Please see the All builtin magics now exist in a few subclasses that group together related functionality, and the new :mod:`IPython.core.magics` package has been created to organize this into smaller files. - + This cleanup was the last major piece of deep refactoring needed from the original 2001 codebase. - + We have also introduced a new type of magic function, prefixed with `%%` instead of `%`, which operates at the whole-cell level. A cell magic receives two arguments: the line it is called on (like a line magic) and the body of the cell below it. - + Cell magics are most natural in the notebook, but they also work in the terminal and qt console, with the usual approach of using a blank line to signal cell termination. - + For example, to time the execution of several statements:: %%timeit x = 0 # setup @@ -242,32 +240,32 @@ interpreter installed: * ``%%!``: run cell body with the underlying OS shell; this is similar to prefixing every line in the cell with ``!``. - + * ``%%bash``: run cell body under bash. - + * ``%%capture``: capture the output of the code in the cell (and stderr as well). Useful to run codes that produce too much output that you don't even want scrolled. - + * ``%%file``: save cell body as a file. - + * ``%%perl``: run cell body using Perl. - + * ``%%prun``: run cell body with profiler (cell extension of ``%prun``). - + * ``%%python3``: run cell body using Python 3. - + * ``%%ruby``: run cell body using Ruby. - + * ``%%script``: run cell body with the script specified in the first line. - + * ``%%sh``: run cell body using sh. - + * ``%%sx``: run cell with system shell and capture process output (cell extension of ``%sx``). - + * ``%%system``: run cell with system shell (``%%!`` is an alias to this). - + * ``%%timeit``: time the execution of the cell (extension of ``%timeit``). This is what some of the script-related magics look like in action: @@ -277,7 +275,7 @@ This is what some of the script-related magics look like in action: :alt: Cluster management from the notebook dashboard :align: center :target: ../_images/ipy_013_notebook_script_cells.png - + In addition, we have also a number of :ref:`extensions ` that provide specialized magics. These typically require additional software to run and must be manually loaded via ``%load_ext ``, but are @@ -359,7 +357,7 @@ or select a completion with the arrow keys (:ghpull:`1851`). In the notebook, completions are now sourced both from object introspection and analysis of surrounding code, so limited completions can be offered for -variables defined in the current cell, or while the kernel is busy +variables defined in the current cell, or while the kernel is busy (:ghpull:`1711`). @@ -390,13 +388,13 @@ polish has gone into it, here are a few highlights: * Allow for restarting kernels without clearing the qtconsole, while leaving a visible indication that the kernel has restarted. :ghpull:`1681`. - + * Allow the native display of jpeg images in the qtconsole. :ghpull:`1643`. .. _spyder: https://code.google.com/p/spyderlib - + Parallel -------- @@ -433,25 +431,25 @@ modes, and in cell mode finer control has been added about how to collate output from multiple engines. :ghpull:`1768`. There have also been incremental improvements to the SSH launchers: - + * add to_send/fetch steps for moving connection files around. - + * add SSHProxyEngineSetLauncher, for invoking to `ipcluster engines` on a remote host. This can be used to start a set of engines via PBS/SGE/MPI *remotely*. - + This makes the SSHLauncher usable on machines without shared filesystems. A number of 'sugar' methods/properties were added to AsyncResult that are quite useful (:ghpull:`1548`) for everday work: - + * ``ar.wall_time`` = received - submitted * ``ar.serial_time`` = sum of serial computation time * ``ar.elapsed`` = time since submission (wall_time if done) * ``ar.progress`` = (int) number of sub-tasks that have completed * ``len(ar)`` = # of tasks * ``ar.wait_interactive()``: prints progress - + Added :meth:`.Client.spin_thread` / :meth:`~.Client.stop_spin_thread` for running spin in a background thread, to keep zmq queue clear. This can be used to ensure that timing information is as accurate as possible (at the cost of @@ -494,7 +492,7 @@ consequences: %%px %%R ... R code goes here - + * It is possible to embed not only an interactive shell with the :func:`IPython.embed` call as always, but now you can also embed a *kernel* with :func:`IPython.embed_kernel()`. Embedding an IPython kernel in an @@ -509,7 +507,7 @@ In addition, having a single core object through our entire architecture also makes the project conceptually cleaner, easier to maintain and more robust. This took a lot of work to get in place, but we are thrilled to have this major piece of architecture finally where we'd always wanted it to be. - + Official Public API ------------------- @@ -532,28 +530,28 @@ instructions to install them on Mac OSX. This is a first draft of our icons, and we encourage contributions from users with graphic talent to improve them in the future. - + New top-level `locate` command ------------------------------ Add `locate` entry points; these would be useful for quickly locating IPython directories and profiles from other (non-Python) applications. :ghpull:`1762`. - + Examples:: - + $> ipython locate /Users/me/.ipython - + $> ipython locate profile foo /Users/me/.ipython/profile_foo - + $> ipython locate profile /Users/me/.ipython/profile_default - + $> ipython locate profile dne [ProfileLocate] Profile u'dne' not found. - + Other new features and improvements ----------------------------------- @@ -617,13 +615,13 @@ Other new features and improvements * ``clear_output`` improvements, which allow things like progress bars and other simple animations to work well in the notebook (:ghpull:`1563`): - + * `clear_output()` clears the line, even in terminal IPython, the QtConsole and plain Python as well, by printing `\r` to streams. - + * `clear_output()` avoids the flicker in the notebook by adding a delay, and firing immediately upon the next actual display message. - + * `display_javascript` hides its `output_area` element, so using display to run a bunch of javascript doesn't result in ever-growing vertical space. @@ -631,11 +629,11 @@ Other new features and improvements supplant proper installation (as users should do), it helps ad-hoc calling of IPython from inside a virtualenv. :ghpull:`1388`. - + Major Bugs fixed ---------------- -In this cycle, we have :ref:`closed over 740 issues `, but a +In this cycle, we have closed over 740 issues, but a few major ones merit special mention: * The ``%pastebin`` magic has been updated to point to gist.github.com, since diff --git a/docs/source/whatsnew/version0.8.rst b/docs/source/whatsnew/version0.8.rst index 38d5ed06b29..34b3b3b4c42 100644 --- a/docs/source/whatsnew/version0.8.rst +++ b/docs/source/whatsnew/version0.8.rst @@ -12,23 +12,22 @@ across several platforms. Release 0.8.3 ============= - + * pydb is now disabled by default (due to %run -d problems). You can enable it by passing -pydb command line argument to IPython. Note that setting it in config file won't work. - + Release 0.8.2 ============= -* %pushd/%popd behave differently; now "pushd /foo" pushes CURRENT directory - and jumps to /foo. The current behaviour is closer to the documented +* %pushd/%popd behave differently; now "pushd /foo" pushes CURRENT directory + and jumps to /foo. The current behaviour is closer to the documented behaviour, and should not trip anyone. - + Older releases ============== Changes in earlier releases of IPython are described in the older file ``ChangeLog``. Please refer to this document for details. - diff --git a/docs/source/whatsnew/version0.9.rst b/docs/source/whatsnew/version0.9.rst index d0f45eba7d7..339046c46bf 100644 --- a/docs/source/whatsnew/version0.9.rst +++ b/docs/source/whatsnew/version0.9.rst @@ -33,7 +33,7 @@ New features * A new, still experimental but highly functional, WX shell by Gael Varoquaux. This work was sponsored by Enthought, and while it's still very new, it is - based on a more cleanly organized arhictecture of the various IPython + based on a more cleanly organized architecture of the various IPython components. We will continue to develop this over the next few releases as a model for GUI components that use IPython. @@ -69,7 +69,7 @@ New features method that has the same syntax as the built-in `map`. We have also defined a `mapper` factory interface that creates objects that implement :class:`IPython.kernel.mapper.IMapper` for different controllers. Both the - multiengine and task controller now have mapping capabilties. + multiengine and task controller now have mapping capabilities. * The parallel function capabilities have been reworks. The major changes are that i) there is now an `@parallel` magic that creates parallel functions, @@ -81,11 +81,11 @@ New features :mod:`IPython.kernel`, :mod:`IPython.kernel.core`, :mod:`traitlets.config`, :mod:`IPython.tools` and :mod:`IPython.testing`. -* As part of merging in the `ipython1-dev` stuff, the `setup.py` script and +* As part of merging in the `ipython1-dev` stuff, the ``setup.py`` script and friends have been completely refactored. Now we are checking for dependencies using the approach that matplotlib uses. -* The documentation has been completely reorganized to accept the +* The documentation has been completely reorganized to accept the documentation from `ipython1-dev`. * We have switched to using Foolscap for all of our network protocols in @@ -114,7 +114,7 @@ New features Last part of dir name is checked first. If no matches for that are found, look at the whole path. - + Bug fixes --------- @@ -135,7 +135,7 @@ Bug fixes * Greedy ``cd`` completion has been disabled again (it was enabled in 0.8.4) as it caused problems on certain platforms. - + Backwards incompatible changes ------------------------------ @@ -161,16 +161,16 @@ Backwards incompatible changes `'basic'` to `'b'`. * IPython has a larger set of dependencies if you want all of its capabilities. - See the `setup.py` script for details. + See the ``setup.py`` script for details. -* The constructors for :class:`IPython.kernel.client.MultiEngineClient` and +* The constructors for :class:`IPython.kernel.client.MultiEngineClient` and :class:`IPython.kernel.client.TaskClient` no longer take the (ip,port) tuple. Instead they take the filename of a file that contains the FURL for that client. If the FURL file is in your IPYTHONDIR, it will be found automatically and the constructor can be left empty. -* The asynchronous clients in :mod:`IPython.kernel.asyncclient` are now created - using the factory functions :func:`get_multiengine_client` and +* The asynchronous clients in :mod:`IPython.kernel.asyncclient` are now created + using the factory functions :func:`get_multiengine_client` and :func:`get_task_client`. These return a `Deferred` to the actual client. * The command line options to `ipcontroller` and `ipengine` have changed to @@ -197,20 +197,20 @@ New features * Initial draft of a process daemon in :mod:`ipython1.daemon`. This has not been merged into IPython and is still in `ipython1-dev`. - + * The ``TaskController`` now has methods for getting the queue status. * The ``TaskResult`` objects not have information about how long the task took to run. - -* We are attaching additional attributes to exceptions ``(_ipython_*)`` that + +* We are attaching additional attributes to exceptions ``(_ipython_*)`` that we use to carry additional info around. - + * New top-level module :mod:`asyncclient` that has asynchronous versions (that return deferreds) of the client classes. This is designed to users who want to run their own Twisted reactor. - -* All the clients in :mod:`client` are now based on Twisted. This is done by + +* All the clients in :mod:`client` are now based on Twisted. This is done by running the Twisted reactor in a separate thread and using the :func:`blockingCallFromThread` function that is in recent versions of Twisted. @@ -221,12 +221,12 @@ New features * Gather/scatter are now implemented in the client to reduce the work load of the controller and improve performance. -* Complete rewrite of the IPython docuementation. All of the documentation +* Complete rewrite of the IPython documentation. All of the documentation from the IPython website has been moved into docs/source as restructured - text documents. PDF and HTML documentation are being generated using + text documents. PDF and HTML documentation are being generated using Sphinx. -* New developer oriented documentation: development guidelines and roadmap. +* New developer oriented documentation: development guidelines and roadmap. * Traditional ``ChangeLog`` has been changed to a more useful ``changes.txt`` file that is organized by release and is meant to provide something more @@ -237,14 +237,14 @@ Bug fixes * Created a proper ``MANIFEST.in`` file to create source distributions. -* Fixed a bug in the ``MultiEngine`` interface. Previously, multi-engine - actions were being collected with a :class:`DeferredList` with - ``fireononeerrback=1``. This meant that methods were returning - before all engines had given their results. This was causing extremely odd - bugs in certain cases. To fix this problem, we have 1) set - ``fireononeerrback=0`` to make sure all results (or exceptions) are in - before returning and 2) introduced a :exc:`CompositeError` exception - that wraps all of the engine exceptions. This is a huge change as it means +* Fixed a bug in the ``MultiEngine`` interface. Previously, multi-engine + actions were being collected with a :class:`DeferredList` with + ``fireononeerrback=1``. This meant that methods were returning + before all engines had given their results. This was causing extremely odd + bugs in certain cases. To fix this problem, we have 1) set + ``fireononeerrback=0`` to make sure all results (or exceptions) are in + before returning and 2) introduced a :exc:`CompositeError` exception + that wraps all of the engine exceptions. This is a huge change as it means that users will have to catch :exc:`CompositeError` rather than the actual exception. @@ -264,12 +264,12 @@ Backwards incompatible changes argument that defaults to ``'all'``. * The :attr:`MultiEngineClient.magicTargets` has been renamed to - :attr:`MultiEngineClient.targets`. + :attr:`MultiEngineClient.targets`. * All methods in the MultiEngine interface now accept the optional keyword argument ``block``. -* Renamed :class:`RemoteController` to :class:`MultiEngineClient` and +* Renamed :class:`RemoteController` to :class:`MultiEngineClient` and :class:`TaskController` to :class:`TaskClient`. * Renamed the top-level module from :mod:`api` to :mod:`client`. @@ -278,6 +278,5 @@ Backwards incompatible changes exception that wraps the user's exceptions, rather than just raising the raw user's exception. -* Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push`` +* Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push`` and ``pull``. - diff --git a/docs/source/whatsnew/version1.0.rst b/docs/source/whatsnew/version1.0.rst index 3e8afdb1dfc..d4aef9f2a4e 100644 --- a/docs/source/whatsnew/version1.0.rst +++ b/docs/source/whatsnew/version1.0.rst @@ -22,9 +22,7 @@ and in this development cycle we had: - ~4000 commits. The amount of work included in this release is so large that we can only cover -here the main highlights; please see our :ref:`detailed release statistics -` for links to every issue and pull request closed on GitHub -as well as a full list of individual contributors. +here the main highlights. It includes Reorganization @@ -72,7 +70,7 @@ We also have added: Which is a *library* function for getting the current IPython instance, -and will return ``None`` if no IPython instance is running. +and will return :py:data:`None` if no IPython instance is running. This is the official way to check whether your code is called from inside an IPython session. If you want to check for IPython without unnecessarily importing IPython, use this function: @@ -135,7 +133,7 @@ Backwards incompatible changes - For all kernel managers, the ``sub_channel`` attribute has been renamed to ``iopub_channel``. - Users on Python versions before 2.6.6, 2.7.1 or 3.2 will now need to call - :func:`IPython.utils.doctestreload.doctest_reload` to make doctests run + :func:`IPython.utils.doctestreload.doctest_reload` to make doctests run correctly inside IPython. Python releases since those versions are unaffected. For details, see :ghpull:`3068` and `Python issue 8048 `_. - The ``InteractiveShell.cache_main_mod()`` method has been removed, and @@ -275,7 +273,7 @@ whether this tradeoff is appropriate for their application. Parallel -------- -IPython.parallel has had some refactoring as well. +IPython.parallel has had some refactoring as well. There are many improvements and fixes, but these are the major changes: - Connections have been simplified. All ports and the serialization in use @@ -299,4 +297,3 @@ Various fixes, including improved performance with lots of text output, and better drag and drop support. The initial window size of the qtconsole is now configurable via ``IPythonWidget.width`` and ``IPythonWidget.height``. - diff --git a/docs/source/whatsnew/version2.0.rst b/docs/source/whatsnew/version2.0.rst index e379308fd53..2f1aa5884ee 100644 --- a/docs/source/whatsnew/version2.0.rst +++ b/docs/source/whatsnew/version2.0.rst @@ -16,9 +16,6 @@ January, 2014 - support for PyQt5 in the kernel (not QtConsole) - support for Pygments 2.0 -For more information on what fixes have been backported to 2.4, -see our :ref:`detailed release info `. - Release 2.3.1 ============= @@ -27,9 +24,6 @@ November, 2014 - Fix CRCRLF line-ending bug in notebooks on Windows -For more information on what fixes have been backported to 2.3.1, -see our :ref:`detailed release info `. - Release 2.3.0 ============= @@ -38,9 +32,6 @@ October, 2014 - improve qt5 support - prevent notebook data loss with atomic writes -For more information on what fixes have been backported to 2.3, -see our :ref:`detailed release info `. - Release 2.2.0 ============= @@ -48,19 +39,12 @@ August, 2014 - Add CORS configuration -For more information on what fixes have been backported to 2.2, -see our :ref:`detailed release info `. - Release 2.1.0 ============= May, 2014 IPython 2.1 is the first bugfix release for 2.0. -For more information on what fixes have been backported to 2.1, -see our :ref:`detailed release info -`. - Release 2.0.0 ============= @@ -87,9 +71,7 @@ Contribution summary since IPython 1.0 in August, 2013: - ~4000 commits The amount of work included in this release is so large that we can only cover -here the main highlights; please see our :ref:`detailed release statistics -` for links to every issue and pull request closed on GitHub -as well as a full list of individual contributors. +here the main highlights. New stuff in the IPython notebook --------------------------------- @@ -175,8 +157,6 @@ IPython previously supported Python 3 by running 2to3 during setup. We have now switched to a single codebase which runs natively on Python 2.7 and 3.3. -For notes on how to maintain this, see :doc:`/development/pycompat`. - Selecting matplotlib figure formats ----------------------------------- @@ -235,8 +215,7 @@ New IPython console lexer ------------------------- The IPython console lexer has been rewritten and now supports tracebacks -and customized input/output prompts. See the :ref:`new lexer docs ` -for details. +and customized input/output prompts. DisplayFormatter changes ------------------------ @@ -341,7 +320,7 @@ Backwards incompatible changes Python versions are now 2.7 and 3.3. * The Transformer classes have been renamed to Preprocessor in nbconvert and their ``call`` methods have been renamed to ``preprocess``. -* The ``call`` methods of nbconvert post-processsors have been renamed to +* The ``call`` methods of nbconvert post-processors have been renamed to ``postprocess``. * The module ``IPython.core.fakemodule`` has been removed. diff --git a/docs/source/whatsnew/version3.rst b/docs/source/whatsnew/version3.rst index f230943b5ae..aa4232d662f 100644 --- a/docs/source/whatsnew/version3.rst +++ b/docs/source/whatsnew/version3.rst @@ -24,8 +24,6 @@ IPython 3.2.1 is a small bugfix release, primarily for cross-site security fixes Users are **strongly** encouraged to upgrade immediately. There are also a few small unicode and nbconvert-related fixes. -See :ref:`issues_list_3` for details. - IPython 3.2 =========== @@ -41,8 +39,6 @@ Highlights: - Fix XSS vulnerability in JSON error messages - Various widget-related fixes -See :ref:`issues_list_3` for details. - IPython 3.1 =========== @@ -58,7 +54,7 @@ Highlights: - Various focus jumping and scrolling fixes in the notebook. - Various message ordering and widget fixes in the notebook. - Images in markdown and output are confined to the notebook width. - An `.unconfined` CSS class is added to disable this behavior per-image. + An ``.unconfined`` CSS class is added to disable this behavior per-image. The resize handle on output images is removed. - Improved ordering of tooltip content for Python functions, putting the signature at the top. - Fix UnicodeErrors when displaying some objects with unicode reprs on Python 2. @@ -69,9 +65,6 @@ Highlights: - Fix memory leak in the IPython.parallel Controller on Python 3. -See :ref:`issues_list_3` for details. - - Release 3.0 =========== @@ -93,7 +86,7 @@ while Python-specific projects (interactive Python shell, Python kernel, IPython will remain under IPython, and be split into a few smaller packages. To reflect this, IPython is in a bit of a transition state. The logo on the notebook is now the Jupyter logo. -When installing kernels system-wide, they go in a `jupyter` directory. +When installing kernels system-wide, they go in a ``jupyter`` directory. We are going to do our best to ease this transition for users and developers. Big changes are ahead. @@ -162,7 +155,7 @@ Other new features __ https://pypi.python.org/pypi/cloud -* Added a .ipynb exporter to nbconvert. It can be used by passing `--to notebook` +* Added a .ipynb exporter to nbconvert. It can be used by passing ``--to notebook`` as a commandline argument to nbconvert. * New nbconvert preprocessor called :class:`~.ClearOutputPreprocessor`. This @@ -202,7 +195,7 @@ Other new features doing it automatically for every change of the parameter widgets. This should be useful for long-running functions. -* The ``%cython`` magic is now part of the Cython module. Use `%load_ext Cython` with a version of Cython >= 0.21 to have access to the magic now. +* The ``%cython`` magic is now part of the Cython module. Use ``%load_ext Cython`` with a version of Cython >= 0.21 to have access to the magic now. * The Notebook application now offers integrated terminals on Unix platforms, intended for when it is used on a remote server. To enable these, install @@ -267,8 +260,8 @@ Other new features * Enhanced support for :magic:`env` magic. As before, :magic:`env` with no arguments displays all environment variables and values. Additionally, :magic:`env` can be used to get or set individual environment variables. To - display an individual value, use the `%env var` syntax. To set a value, use - `env var val` or `env var=val`. Python value expansion using `$` works as usual. + display an individual value, use the ``%env var`` syntax. To set a value, use + ``env var val`` or ``env var=val``. Python value expansion using ``$`` works as usual. Backwards incompatible changes @@ -281,7 +274,7 @@ Backwards incompatible changes * The notebook format has been updated from version 3 to version 4. Read-only support for v4 notebooks has been backported to IPython 2.4. Notable changes: - + * heading cells are removed in favor or markdown headings * notebook outputs and output messages are more consistent with each other * use :func:`IPython.nbformat.read` and :func:`~IPython.nbformat.write` @@ -289,37 +282,37 @@ Backwards incompatible changes instead of the deprecated :mod:`IPython.nbformat.current` APIs. You can downgrade a notebook to v3 via ``nbconvert``:: - + ipython nbconvert --to notebook --nbformat 3 - + which will create :file:`notebook.v3.ipynb`, a copy of the notebook in v3 format. * :func:`IPython.core.oinspect.getsource` call specification has changed: - * `oname` keyword argument has been added for property source formatting - * `is_binary` keyword argument has been dropped, passing ``True`` had - previously short-circuited the function to return ``None`` unconditionally + * ``oname`` keyword argument has been added for property source formatting + * ``is_binary`` keyword argument has been dropped, passing :py:data:`True` had + previously short-circuited the function to return :py:data:`None` unconditionally * Removed the octavemagic extension: it is now available as ``oct2py.ipython``. * Creating PDFs with LaTeX no longer uses a post processor. - Use `nbconvert --to pdf` instead of `nbconvert --to latex --post pdf`. + Use ``nbconvert --to pdf`` instead of ``nbconvert --to latex --post pdf``. * Used https://github.com/jdfreder/bootstrap2to3 to migrate the Notebook to Bootstrap 3. Additional changes: - - Set `.tab-content .row` `0px;` left and right margin (bootstrap default is `-15px;`) - - Removed `height: @btn_mini_height;` from `.list_header>div, .list_item>div` in `tree.less` - - Set `#header` div `margin-bottom: 0px;` - - Set `#menus` to `float: left;` - - Set `#maintoolbar .navbar-text` to `float: none;` + - Set ``.tab-content .row`` ``0px;`` left and right margin (bootstrap default is ``-15px;``) + - Removed ``height: @btn_mini_height;`` from ``.list_header>div, .list_item>div`` in ``tree.less`` + - Set ``#header`` div ``margin-bottom: 0px;`` + - Set ``#menus`` to ``float: left;`` + - Set ``#maintoolbar .navbar-text`` to ``float: none;`` - Added no-padding convenience class. - Set border of #maintoolbar to 0px -* Accessing the `container` DOM object when displaying javascript has been - deprecated in IPython 2.0 in favor of accessing `element`. Starting with - IPython 3.0 trying to access `container` will raise an error in browser +* Accessing the ``container`` DOM object when displaying javascript has been + deprecated in IPython 2.0 in favor of accessing ``element``. Starting with + IPython 3.0 trying to access ``container`` will raise an error in browser javascript console. * ``IPython.utils.py3compat.open`` was removed: :func:`io.open` provides all @@ -338,16 +331,16 @@ Backwards incompatible changes * :meth:`~.KernelManager.start_kernel` and :meth:`~.KernelManager.format_kernel_cmd` no longer accept a ``executable`` parameter. Use the kernelspec machinery instead. -* The widget classes have been renamed from `*Widget` to `*`. The old names are +* The widget classes have been renamed from ``*Widget`` to ``*``. The old names are still functional, but are deprecated. i.e. `IntSliderWidget` has been renamed to `IntSlider`. * The ContainerWidget was renamed to Box and no longer defaults as a flexible box in the web browser. A new FlexBox widget was added, which allows you to use the flexible box model. -* The notebook now uses a single websocket at `/kernels//channels` instead of separate - `/kernels//{shell|iopub|stdin}` channels. Messages on each channel are identified by a - `channel` key in the message dict, for both send and recv. +* The notebook now uses a single websocket at ``/kernels//channels`` instead of separate + ``/kernels//{shell|iopub|stdin}`` channels. Messages on each channel are identified by a + ``channel`` key in the message dict, for both send and recv. Content Security Policy diff --git a/docs/source/whatsnew/version3_widget_migration.rst b/docs/source/whatsnew/version3_widget_migration.rst index 1daeb7e91b5..869f6a2f56b 100644 --- a/docs/source/whatsnew/version3_widget_migration.rst +++ b/docs/source/whatsnew/version3_widget_migration.rst @@ -11,7 +11,7 @@ Upgrading Notebooks "Widget" suffix was removed from the end of the class name. i.e. ``ButtonWidget`` is now ``Button``. 3. ``ContainerWidget`` was renamed to ``Box``. -4. ``PopupWidget`` was removed from IPython, because bootstrapjs was +4. ``PopupWidget`` was removed from IPython, because bootstrapjs was problematic (creates global variables, etc.). If you use the ``PopupWidget``, try using a ``Box`` widget instead. If your notebook can't live without the popup functionality, subclass the ``Box`` diff --git a/docs/source/whatsnew/version4.rst b/docs/source/whatsnew/version4.rst index 6150a8668cd..c4fbd6176d1 100644 --- a/docs/source/whatsnew/version4.rst +++ b/docs/source/whatsnew/version4.rst @@ -81,4 +81,3 @@ IPython highlights: `reload` into the interactive namespace have been deprecated, and will be removed in future versions. You should now explicitly import `reload` from `IPython.lib.deepreload` to use it. - diff --git a/docs/source/whatsnew/version5.rst b/docs/source/whatsnew/version5.rst index 4ca5775748f..a11f5748ddd 100644 --- a/docs/source/whatsnew/version5.rst +++ b/docs/source/whatsnew/version5.rst @@ -2,6 +2,12 @@ 5.x Series ============ +.. _whatsnew580: + +IPython 5.8.0 +============= + +* Update inspecting function/methods for future-proofing. :ghpull:`11139` .. _whatsnew570: @@ -80,8 +86,10 @@ Misc You can see all the PR marked for the `5.5. milestone `_, and all the `backport versions `_. - - +IPython 5.4.1 +============= +Released a few hours after 5.4, fix a crash when +``backports.shutil-get-terminal-size`` is not installed. :ghissue:`10629` .. _whatsnew540: @@ -93,7 +101,7 @@ series which is Python 3 only. It backports most of the new exposed API additions made in IPython 6.0 and 6.1 and avoid having to write conditional logics depending of the version of IPython. -Please upgrade to pip 9 or greater before upgrading IPython. +Please upgrade to pip 9 or greater before upgrading IPython. Failing to do so on Python 2 may lead to a broken IPython install. Configurable TerminalInteractiveShell @@ -107,7 +115,6 @@ that allow to customize the class used to start the terminal frontend. This should allow user to use custom interfaces, like reviving the former readline interface which is now a separate package not maintained by the core team. - Define ``_repr_mimebundle_`` ---------------------------- @@ -153,7 +160,7 @@ Miscs * ``_mp_main_`` is not reloaded which fixes issues with multiprocessing. :ghpull:`10523` * Use user colorscheme in Pdb as well :ghpull:`10479` -* Faster shutdown. :ghpull:`10408` +* Faster shutdown. :ghpull:`10408` * Fix a crash in reverse search. :ghpull:`10371` * added ``Completer.backslash_combining_completions`` boolean option to deactivate backslash-tab completion that may conflict with windows path. @@ -217,7 +224,7 @@ Released on January 29th, 2017. Remarkable changes and fixes: :ghpull:`10050` * Fixes OS X event loop issues at startup, :ghpull:`10150` * Deprecate the ``%autoindent`` magic. :ghpull:`10176` -* Emit a :any:`DeprecationWarning` when setting the deprecated +* Emit a :py:exc:`DeprecationWarning` when setting the deprecated ``limit_to_all`` option of the completer. :ghpull:`10198` * The :cellmagic:`capture` magic can now capture the result of a cell (from an expression on the last line), as well as printed and displayed output. @@ -300,7 +307,7 @@ and tab completions that don't clutter up your history. :target: ../_images/ptshell_features.png These features are provided by the Python library `prompt_toolkit -`__, which replaces +`__, which replaces ``readline`` throughout our terminal interface. Relying on this pure-Python, cross platform module also makes it simpler to @@ -332,7 +339,7 @@ Some changes that we're aware of, with suggestions on how to handle them: IPython no longer uses readline configuration (``~/.inputrc``). We hope that the functionality you want (e.g. vi input mode) will be available by configuring -IPython directly (see :doc:`/config/options/terminal`). +IPython directly (see :doc:`/config/options/index`). If something's missing, please file an issue. The ``PromptManager`` class has been removed, and the prompt machinery simplified. @@ -436,5 +443,3 @@ Known Issues: - Color styles might not adapt to terminal emulator themes. This will need new version of Pygments to be released, and can be mitigated with custom themes. - - diff --git a/docs/source/whatsnew/version6.rst b/docs/source/whatsnew/version6.rst index 461132ecfce..153efe9db08 100644 --- a/docs/source/whatsnew/version6.rst +++ b/docs/source/whatsnew/version6.rst @@ -22,7 +22,7 @@ IPython 6.4.0 Everything new in :ref:`IPython 5.7 ` * Fix display object not emitting metadata :ghpull:`11106` -* Comments failing Jedi test :ghpull:`11110` +* Comments failing Jedi test :ghpull:`11110` .. _whatsnew631: @@ -199,7 +199,7 @@ You should be able to update by using: .. note:: If your pip version is greater than or equal to pip 9.0.1 you will automatically get - the most recent version of IPython compatible with your system: on Python 2 you + the most recent version of IPython compatible with your system: on Python 2 you will get the latest IPython 5.x bugfix, while in Python 3 you will get the latest 6.x stable version. @@ -210,8 +210,8 @@ The completer Completion API has seen an overhaul, and the new completer has plenty of improvements both from the end users of terminal IPython and for consumers of the API. -This new API is capable of pulling completions from :any:`jedi`, thus allowing -type inference on non-executed code. If :any:`jedi` is installed, completions like +This new API is capable of pulling completions from :mod:`jedi`, thus allowing +type inference on non-executed code. If :mod:`jedi` is installed, completions like the following are now possible without code evaluation: >>> data = ['Number of users', 123_456] @@ -220,7 +220,7 @@ the following are now possible without code evaluation: That is to say, IPython is now capable of inferring that `data[0]` is a string, and will suggest completions like `.capitalize`. The completion power of IPython will increase with new Jedi releases, and a number of bug-fixes and more completions -are already available on the development version of :any:`jedi` if you are curious. +are already available on the development version of :mod:`jedi` if you are curious. With the help of prompt toolkit, types of completions can be shown in the completer interface: @@ -244,7 +244,7 @@ Extra patches and updates will be needed to the :mod:`ipykernel` package for this feature to be available to other clients like Jupyter Notebook, Lab, Nteract, Hydrogen... -The use of Jedi should be barely noticeable on recent machines, but +The use of Jedi should be barely noticeable on recent machines, but can be slower on older ones. To tweak the performance, the amount of time given to Jedi to compute type inference can be adjusted with ``c.IPCompleter.jedi_compute_type_timeout``. The objects whose type were not @@ -277,10 +277,10 @@ automatically upgrade to the latest version compatible with your system. The ability to use only Python 3 on the code base of IPython brings a number of advantages. Most of the newly written code make use of `optional function type -annotation `_ leading to clearer code +annotation `_ leading to clearer code and better documentation. -The total size of the repository has also decreased by about 1500 lines (for the +The total size of the repository has also decreased by about 1500 lines (for the first time excluding the big split for 4.0). The decrease is potentially a bit more for the sour as some documents like this one are append only and are about 300 lines long. @@ -335,7 +335,7 @@ Functions Deprecated in 6.x Development cycle - ``IPython.utils.warn`` was deprecated in IPython 4.0, and has now been removed. - instead of ``IPython.utils.warn`` inbuilt :any:`warnings` module is used. + instead of ``IPython.utils.warn`` inbuilt :mod:`warnings` module is used. - The function `IPython.core.oinspect.py:call_tip` is unused, was marked as diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 1a8e1eff67c..b3920cae09e 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -2,6 +2,1573 @@ 7.x Series ============ +.. _version 7.34: + +IPython 7.34 +============ + +This version contains a single fix: fix uncaught BdbQuit exceptions on ipdb +exit :ghpull:`13668` + + +.. _version 7.33: + +IPython 7.33 +============ + + - Allow IPython hooks to receive current cell ids when frontend support it. See + :ghpull:`13600` + + - ``?`` does not trigger the insertion of a new cell anymore as most frontend + allow proper multiline edition. :ghpull:`13625` + + +.. _version 7.32: + +IPython 7.32 +============ + + + +Autoload magic lazily +--------------------- + +The ability to configure magics to be lazily loaded has been added to IPython. +See the ``ipython --help-all`` section on ``MagicsManager.lazy_magic``. +One can now use:: + + c.MagicsManager.lazy_magics = { + "my_magic": "slow.to.import", + "my_other_magic": "also.slow", + } + +And on first use of ``%my_magic``, or corresponding cell magic, or other line magic, +the corresponding ``load_ext`` will be called just before trying to invoke the magic. + +Misc +---- + + - Update sphinxify for Docrepr 0.2.0 :ghpull:`13503`. + - Set co_name for cells run line by line (to fix debugging with Python 3.10) + :ghpull:`13535` + + +Many thanks to all the contributors to this release. You can find all individual +contributions to this milestone `on github +`__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + +.. _version 7.31: + +IPython 7.31 +============ + +IPython 7.31 brings a couple of backports and fixes from the 8.0 branches, +it is likely one of the last releases of the 7.x series, as 8.0 will probably be released +between this release and what would have been 7.32. + +Please test 8.0 beta/rc releases in addition to this release. + +This Releases: + - Backport some fixes for Python 3.10 (:ghpull:`13412`) + - use full-alpha transparency on dvipng rendered LaTeX (:ghpull:`13372`) + +Many thanks to all the contributors to this release. You can find all individual +contributions to this milestone `on github +`__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + +.. _version 7.30: + +IPython 7.30 +============ + +IPython 7.30 fixes a couple of bugs introduce in previous releases (in +particular with respect to path handling), and introduce a few features and +improvements: + +Notably we will highlight :ghpull:`13267` "Document that ``%run`` can execute +notebooks and ipy scripts.", which is the first commit of Fernando Pérez since +mid 2016 (IPython 5.1). If you are new to IPython, Fernando created IPython in +2001. The other most recent contribution of Fernando to IPython itself was +May 2018, by reviewing and merging PRs. I want to note that Fernando is still +active but mostly as a mentor and leader of the whole Jupyter organisation, but +we're still happy to see him contribute code ! + +:ghpull:`13290` "Use sphinxify (if available) in object_inspect_mime path" +should allow richer Repr of docstrings when using jupyterlab inspector. + +:ghpull:`13311` make the debugger use ``ThreadPoolExecutor`` for debugger cmdloop. +This should fix some issues/infinite loop, but let us know if you come across +any regressions. In particular this fixes issues with `kmaork/madbg `_, +a remote debugger for IPython. + +Note that this is likely the ante-penultimate release of IPython 7.x as a stable +branch, as I hope to release IPython 8.0 as well as IPython 7.31 next +month/early 2022. + +IPython 8.0 will drop support for Python 3.7, removed nose as a dependency, and +7.x will only get critical bug fixes with 8.x becoming the new stable. This will +not be possible without `NumFOCUS Small Development Grants +`_ Which allowed us to +hire `Nikita Kniazev `_ who provide Python and C++ +help and contracting work. + + +Many thanks to all the contributors to this release. You can find all individual +contributions to this milestone `on github +`__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + +.. _version 7.29: + +IPython 7.29 +============ + + +IPython 7.29 brings a couple of new functionalities to IPython and a number of bugfixes. +It is one of the largest recent release, relatively speaking, with close to 15 Pull Requests. + + + - fix an issue where base64 was returned instead of bytes when showing figures :ghpull:`13162` + - fix compatibility with PyQt6, PySide 6 :ghpull:`13172`. This may be of + interest if you are running on Apple Silicon as only qt6.2+ is natively + compatible. + - fix matplotlib qtagg eventloop :ghpull:`13179` + - Multiple docs fixes, typos, ... etc. + - Debugger will now exit by default on SigInt :ghpull:`13218`, this will be + useful in notebook/lab if you forgot to exit the debugger. "Interrupt Kernel" + will now exist the debugger. + +It give Pdb the ability to skip code in decorators. If functions contain a +special value names ``__debuggerskip__ = True|False``, the function will not be +stepped into, and Pdb will step into lower frames only if the value is set to +``False``. The exact behavior is still likely to have corner cases and will be +refined in subsequent releases. Feedback welcome. See the debugger module +documentation for more info. Thanks to the `D. E. Shaw +group `__ for funding this feature. + +The main branch of IPython is receiving a number of changes as we received a +`NumFOCUS SDG `__ +($4800), to help us finish replacing ``nose`` by ``pytest``, and make IPython +future proof with an 8.0 release. + + +Many thanks to all the contributors to this release. You can find all individual +contributions to this milestone `on github +`__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + +.. _version 7.28: + +IPython 7.28 +============ + + +IPython 7.28 is again a minor release that mostly bring bugfixes, and couple of +improvement. Many thanks to MrMino, who again did all the work this month, and +made a number of documentation improvements. + +Here is a non-exhaustive list of changes, + +Fixes: + + - async with doesn't allow newlines :ghpull:`13090` + - Dynamically changing to vi mode via %config magic) :ghpull:`13091` + +Virtualenv handling fixes: + + - init_virtualenv now uses Pathlib :ghpull:`12548` + - Fix Improper path comparison of virtualenv directories :ghpull:`13140` + - Fix virtual environment user warning for lower case paths :ghpull:`13094` + - Adapt to all sorts of drive names for cygwin :ghpull:`13153` + +New Features: + + - enable autoplay in embed YouTube player :ghpull:`13133` + + Documentation: + + - Fix formatting for the core.interactiveshell documentation :ghpull:`13118` + - Fix broken ipyparallel's refs :ghpull:`13138` + - Improve formatting of %time documentation :ghpull:`13125` + - Reword the YouTubeVideo autoplay WN :ghpull:`13147` + + +Highlighted features +-------------------- + + +``YouTubeVideo`` autoplay and the ability to add extra attributes to ``IFrame`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can add any extra attributes to the `` + +Related to the above, the ``YouTubeVideo`` class now takes an +``allow_autoplay`` flag, which sets up the iframe of the embedded YouTube video +such that it allows autoplay. + +.. note:: + Whether this works depends on the autoplay policy of the browser rendering + the HTML allowing it. It also could get blocked by some browser extensions. + +Try it out! + +:: + + In [1]: from IPython.display import YouTubeVideo + + In [2]: YouTubeVideo("dQw4w9WgXcQ", allow_autoplay=True) + + + +Thanks +------ + +Many thanks to all the contributors to this release. You can find all individual +contributions to this milestone `on github +`__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + +.. _version 7.27: + +IPython 7.27 +============ + +IPython 7.27 is a minor release that fixes a couple of issues and compatibility. + +- Add support for GTK4 :ghpull:`131011` +- Add support for Qt6 :ghpull:`13085` +- Fix an issue with pip magic on windows :ghpull:`13093` + +Thanks +------ + +Many thanks to all the contributors to this release. You can find all individual +contributions to this milestone `on github +`__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + +.. _version 7.26: + +IPython 7.26 +============ + +IPython 7.26 is a minor release that fixes a couple of issues, updates in API +and Copyright/Licenses issues around various part of the codebase. + +We'll highlight `this issue ` +pointing out we were including and refereeing to code from Stack Overflow which +was CC-BY-SA, hence incompatible with the BSD license of IPython. This lead us +to a rewriting of the corresponding logic which in our case was done in a more +efficient way (in our case we were searching string prefixes instead of full +strings). + +You will notice also a number of documentation improvements and cleanup. + +Of particular interest are the following Pull-requests: + + + - The IPython directive now uses Sphinx logging for warnings. :ghpull:`13030`. + - Add expiry days option to pastebin magic and change http protocol to https. + :ghpull:`13056` + - Make Ipython.utils.timing work with jupyterlite :ghpull:`13050`. + +Pastebin magic expiry days option +--------------------------------- + +The Pastebin magic now has ``-e`` option to determine +the number of days for paste expiration. For example +the paste that created with ``%pastebin -e 20 1`` magic will +be available for next 20 days. + + + + + +Thanks +------ + +Many thanks to all the contributors to this release and in particular MrMino who +is doing most of the work those days. You can find all individual contributions +to this milestone `on github `__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + +.. _version 7.25: + +IPython 7.25 +============ + +IPython 7.25 is a minor release that contains a single bugfix, which is highly +recommended for all users of ipdb, ipython debugger %debug magic and similar. + +Issuing commands like ``where`` from within the debugger would reset the +local variables changes made by the user. It is interesting to look at the root +cause of the issue as accessing an attribute (``frame.f_locals``) would trigger +this side effects. + +Thanks in particular to the patience from the reporters at D.E. Shaw for their +initial bug report that was due to a similar coding oversight in an extension, +and who took time to debug and narrow down the problem. + +Thanks +------ + +Many thanks to all the contributors to this release you can find all individual +contributions to this milestone `on github `__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + +.. _version 7.24: + +IPython 7.24 +============ + +Third release of IPython for 2021, mostly containing bug fixes. A couple of not +typical updates: + +Misc +---- + + + - Fix an issue where ``%recall`` would both succeeded and print an error message + it failed. :ghpull:`12952` + - Drop support for NumPy 1.16 – practically has no effect beyond indicating in + package metadata that we do not support it. :ghpull:`12937` + +Debugger improvements +--------------------- + +The debugger (and ``%debug`` magic) have been improved and can skip or hide frames +originating from files that are not writable to the user, as these are less +likely to be the source of errors, or be part of system files this can be a useful +addition when debugging long errors. + +In addition to the global ``skip_hidden True|False`` command, the debugger has +gained finer grained control of predicates as to whether to a frame should be +considered hidden. So far 3 predicates are available : + + - ``tbhide``: frames containing the local variable ``__tracebackhide__`` set to + True. + - ``readonly``: frames originating from readonly files, set to False. + - ``ipython_internal``: frames that are likely to be from IPython internal + code, set to True. + +You can toggle individual predicates during a session with + +.. code-block:: + + ipdb> skip_predicates readonly True + +Read-only files will now be considered hidden frames. + + +You can call ``skip_predicates`` without arguments to see the states of current +predicates: + +.. code-block:: + + ipdb> skip_predicates + current predicates: + tbhide : True + readonly : False + ipython_internal : True + +If all predicates are set to ``False``, ``skip_hidden`` will practically have +no effect. We attempt to warn you when all predicates are False. + +Note that the ``readonly`` predicate may increase disk access as we check for +file access permission for all frames on many command invocation, but is usually +cached by operating systems. Let us know if you encounter any issues. + +As the IPython debugger does not use the traitlets infrastructure for +configuration, by editing your ``.pdbrc`` files and appending commands you would +like to be executed just before entering the interactive prompt. For example: + + +.. code:: + + # file : ~/.pdbrc + skip_predicates readonly True + skip_predicates tbhide False + +Will hide read only frames by default and show frames marked with +``__tracebackhide__``. + + + + +Thanks +------ + +Many thanks to all the contributors to this release you can find all individual +contributions to this milestone `on github `__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries, in particular above mentioned +improvements to the debugger. + + + + +.. _version 7.23: + +IPython 7.23 and 7.23.1 +======================= + + +Third release of IPython for 2021, mostly containing bug fixes. A couple of not +typical updates: + + - We moved to GitHub actions away from Travis-CI, the transition may not be + 100% complete (not testing on nightly anymore), but as we ran out of + Travis-Ci hours on the IPython organisation that was a necessary step. + :ghpull:`12900`. + + - We have a new dependency: ``matplotlib-inline``, which try to extract + matplotlib inline backend specific behavior. It is available on PyPI and + conda-forge thus should not be a problem to upgrade to this version. If you + are a package maintainer that might be an extra dependency to package first. + :ghpull:`12817` (IPython 7.23.1 fix a typo that made this change fail) + +In the addition/new feature category, ``display()`` now have a ``clear=True`` +option to clear the display if any further outputs arrives, allowing users to +avoid having to use ``clear_output()`` directly. :ghpull:`12823`. + +In bug fixes category, this release fix an issue when printing tracebacks +containing Unicode characters :ghpull:`12758`. + +In code cleanup category :ghpull:`12932` remove usage of some deprecated +functionality for compatibility with Python 3.10. + + + +Thanks +------ + +Many thanks to all the contributors to this release you can find all individual +contributions to this milestone `on github `__. +In particular MrMino for responding to almost all new issues, and triaging many +of the old ones, as well as takluyver, minrk, willingc for reacting quikly when +we ran out of CI Hours. + +Thanks as well to organisations, QuantStack (martinRenou and SylvainCorlay) for +extracting matplotlib inline backend into its own package, and the `D. E. Shaw group +`__ for sponsoring work on IPython and related libraries. + + +.. _version 7.22: + +IPython 7.22 +============ + +Second release of IPython for 2021, mostly containing bug fixes. Here is a quick +rundown of the few changes. + +- Fix some ``sys.excepthook`` shenanigan when embedding with qt, recommended if + you – for example – use `napari `__. :ghpull:`12842`. +- Fix bug when using the new ipdb ``%context`` magic :ghpull:`12844` +- Couples of deprecation cleanup :ghpull:`12868` +- Update for new dpast.com api if you use the ``%pastbin`` magic. :ghpull:`12712` +- Remove support for numpy before 1.16. :ghpull:`12836` + + +Thanks +------ + +We have a new team member that you should see more often on the IPython +repository, Błażej Michalik (@MrMino) have been doing regular contributions to +IPython, and spent time replying to many issues and guiding new users to the +codebase; they now have triage permissions to the IPython repository and we'll +work toward giving them more permission in the future. + +Many thanks to all the contributors to this release you can find all individual +contributions to this milestone `on github `__. + +Thanks as well to organisations, QuantStack for working on debugger +compatibility for Xeus_python, and the `D. E. Shaw group +`__ for sponsoring work on IPython and related libraries. + +.. _version 721: + +IPython 7.21 +============ + +IPython 7.21 is the first release we have back on schedule of one release every +month; it contains a number of minor fixes and improvements, notably, the new +context command for ipdb + + +New "context" command in ipdb +----------------------------- + +It is now possible to change the number of lines shown in the backtrace +information in ipdb using "context" command. :ghpull:`12826` + +(thanks @MrMino, there are other improvement from them on master). + +Other notable changes in IPython 7.21 +------------------------------------- + +- Fix some issues on new osx-arm64 :ghpull:`12804`, :ghpull:`12807`. +- Compatibility with Xeus-Python for debugger protocol, :ghpull:`12809` +- Misc docs fixes for compatibility and uniformity with Numpydoc. + :ghpull:`12824` + + +Thanks +------ + +Many thanks to all the contributors to this release you can find all individual +contribution to this milestone `on github `__. + + +.. _version 720: + +IPython 7.20 +============ + +IPython 7.20 is the accumulation of 3 month of work on IPython, spacing between +IPython release have been increased from the usual once a month for various +reason. + + - Mainly as I'm too busy and the effectively sole maintainer, and + - Second because not much changes happened before mid December. + +The main driver for this release was the new version of Jedi 0.18 breaking API; +which was taken care of in the master branch early in 2020 but not in 7.x as I +though that by now 8.0 would be out. + +The inclusion of a resolver in pip did not help and actually made things worse. +If usually I would have simply pinned Jedi to ``<0.18``; this is not a solution +anymore as now pip is free to install Jedi 0.18, and downgrade IPython. + +I'll do my best to keep the regular release, but as the 8.0-dev branch and 7.x +are starting to diverge this is becoming difficult in particular with my limited +time, so if you have any cycles to spare I'll appreciate your help to respond to +issues and pushing 8.0 forward. + +Here are thus some of the changes for IPython 7.20. + + - Support for PyQt5 >= 5.11 :ghpull:`12715` + - ``%reset`` remove imports more aggressively :ghpull:`12718` + - fix the ``%conda`` magic :ghpull:`12739` + - compatibility with Jedi 0.18, and bump minimum Jedi version. :ghpull:`12793` + + +.. _version 719: + +IPython 7.19 +============ + +IPython 7.19 accumulative two month of works, bug fixes and improvements, there +was exceptionally no release last month. + + - Fix to restore the ability to specify more than one extension using command + line flags when using traitlets 5.0 :ghpull:`12543` + - Docs docs formatting that make the install commands work on zsh + :ghpull:`12587` + - Always display the last frame in tracebacks even if hidden with + ``__tracebackhide__`` :ghpull:`12601` + - Avoid an issue where a callback can be registered multiple times. + :ghpull:`12625` + - Avoid an issue in debugger mode where frames changes could be lost. + :ghpull:`12627` + + - Never hide the frames that invoke a debugger, even if marked as hidden by + ``__tracebackhide__`` :ghpull:`12631` + - Fix calling the debugger in a recursive manner :ghpull:`12659` + + +A number of code changes have landed on master and we are getting close to +enough new features and codebase improvement that a 8.0 start to make sens. +For downstream packages, please start working on migrating downstream testing +away from iptest and using pytest, as nose will not work on Python 3.10 and we +will likely start removing it as a dependency for testing. + +.. _version 718: + +IPython 7.18 +============ + +IPython 7.18 is a minor release that mostly contains bugfixes. + + - ``CRLF`` is now handled by magics my default; solving some issues due to copy + pasting on windows. :ghpull:`12475` + + - Requiring pexpect ``>=4.3`` as we are Python 3.7+ only and earlier version of + pexpect will be incompatible. :ghpull:`12510` + + - Minimum jedi version is now 0.16. :ghpull:`12488` + + + +.. _version 717: + +IPython 7.17 +============ + +IPython 7.17 brings a couple of new improvements to API and a couple of user +facing changes to make the terminal experience more user friendly. + +:ghpull:`12407` introduces the ability to pass extra argument to the IPython +debugger class; this is to help a new project from ``kmaork`` +(https://github.com/kmaork/madbg) to feature a fully remote debugger. + +:ghpull:`12410` finally remove support for 3.6, while the codebase is still +technically compatible; IPython will not install on Python 3.6. + +lots of work on the debugger and hidden frames from ``@impact27`` in +:ghpull:`12437`, :ghpull:`12445`, :ghpull:`12460` and in particular +:ghpull:`12453` which make the debug magic more robust at handling spaces. + +Biggest API addition is code transformation which is done before code execution; +IPython allows a number of hooks to catch non-valid Python syntax (magic, prompt +stripping...etc). Transformers are usually called many time; typically: + + - When trying to figure out whether the code is complete and valid (should we + insert a new line or execute ?) + - During actual code execution pass before giving the code to Python's + ``exec``. + +This lead to issues when transformer might have had side effects; or do external +queries. Starting with IPython 7.17 you can expect your transformer to be called +less time. + +Input transformers are now called only once in the execution path of +`InteractiveShell`, allowing to register transformer that potentially have side +effects (note that this is not recommended). Internal methods `should_run_async`, and +`run_cell_async` now take a recommended optional `transformed_cell`, and +`preprocessing_exc_tuple` parameters that will become mandatory at some point in +the future; that is to say cells need to be explicitly transformed to be valid +Python syntax ahead of trying to run them. :ghpull:`12440`; + +``input_transformers`` can now also have an attribute ``has_side_effects`` set +to `True`, when this attribute is present; this will prevent the transformers +from being ran when IPython is trying to guess whether the user input is +complete. Note that this may means you will need to explicitly execute in some +case where your transformations are now not ran; but will not affect users with +no custom extensions. + + +API Changes +----------- + +Change of API and exposed objects automatically detected using `frappuccino +`_ + + + The following items are new since 7.16.0:: + + + IPython.core.interactiveshell.InteractiveShell.get_local_scope(self, stack_depth) + + The following signatures differ since 7.16.0:: + + - IPython.core.interactiveshell.InteractiveShell.run_cell_async(self, raw_cell, store_history=False, silent=False, shell_futures=True) + + IPython.core.interactiveshell.InteractiveShell.run_cell_async(self, raw_cell, store_history=False, silent=False, shell_futures=True, *, transformed_cell=None, preprocessing_exc_tuple=None) + + - IPython.core.interactiveshell.InteractiveShell.should_run_async(self, raw_cell) + + IPython.core.interactiveshell.InteractiveShell.should_run_async(self, raw_cell, *, transformed_cell=None, preprocessing_exc_tuple=None) + + - IPython.terminal.debugger.TerminalPdb.pt_init(self) + + IPython.terminal.debugger.TerminalPdb.pt_init(self, pt_session_options=None) + +This method was added:: + + + IPython.core.interactiveshell.InteractiveShell.get_local_scope + +Which is now also present on subclasses:: + + + IPython.terminal.embed.InteractiveShellEmbed.get_local_scope + + IPython.terminal.interactiveshell.TerminalInteractiveShell.get_local_scope + + +.. _version 716: + +IPython 7.16.1, 7.16.2 +====================== + +IPython 7.16.1 was release immediately after 7.16.0 to fix a conda packaging issue. +The source is identical to 7.16.0 but the file permissions in the tar are different. + +IPython 7.16.2 pins jedi dependency to "<=0.17.2" which should prevent some +issues for users still on python 3.6. This may not be sufficient as pip may +still allow to downgrade IPython. + +Compatibility with Jedi > 0.17.2 was not added as this would have meant bumping +the minimal version to >0.16. + +IPython 7.16 +============ + + +The default traceback mode will now skip frames that are marked with +``__tracebackhide__ = True`` and show how many traceback frames have been +skipped. This can be toggled by using :magic:`xmode` with the ``--show`` or +``--hide`` attribute. It will have no effect on non verbose traceback modes. + +The ipython debugger also now understands ``__tracebackhide__`` as well and will +skip hidden frames when displaying. Movement up and down the stack will skip the +hidden frames and will show how many frames were hidden. Internal IPython frames +are also now hidden by default. The behavior can be changed with the +``skip_hidden`` while in the debugger, command and accepts "yes", "no", "true" +and "false" case insensitive parameters. + + +Misc Noticeable changes: +------------------------ + +- Exceptions are now (re)raised when running notebooks via the :magic:`%run`, helping to catch issues in workflows and + pipelines. :ghpull:`12301` +- Fix inputhook for qt 5.15.0 :ghpull:`12355` +- Fix wx inputhook :ghpull:`12375` +- Add handling for malformed pathext env var (Windows) :ghpull:`12367` +- use $SHELL in system_piped :ghpull:`12360` for uniform behavior with + ipykernel. + +Reproducible Build +------------------ + +IPython 7.15 reproducible build did not work, so we try again this month +:ghpull:`12358`. + + +API Changes +----------- + +Change of API and exposed objects automatically detected using `frappuccino +`_ (still in beta): + + +The following items are new and mostly related to understanding ``__tracebackhide__``:: + + + IPython.core.debugger.Pdb.do_down(self, arg) + + IPython.core.debugger.Pdb.do_skip_hidden(self, arg) + + IPython.core.debugger.Pdb.do_up(self, arg) + + IPython.core.debugger.Pdb.hidden_frames(self, stack) + + IPython.core.debugger.Pdb.stop_here(self, frame) + + +The following items have been removed:: + + - IPython.core.debugger.Pdb.new_do_down + - IPython.core.debugger.Pdb.new_do_up + +Those were implementation details. + + +.. _version 715: + +IPython 7.15 +============ + +IPython 7.15 brings a number of bug fixes and user facing improvements. + +Misc Noticeable changes: +------------------------ + + - Long completion name have better elision in terminal :ghpull:`12284` + - I've started to test on Python 3.9 :ghpull:`12307` and fix some errors. + - Hi DPI scaling of figures when using qt eventloop :ghpull:`12314` + - Document the ability to have systemwide configuration for IPython. + :ghpull:`12328` + - Fix issues with input autoformatting :ghpull:`12336` + - ``IPython.core.debugger.Pdb`` is now interruptible (:ghpull:`12168`, in 7.14 + but forgotten in release notes) + - Video HTML attributes (:ghpull:`12212`, in 7.14 but forgotten in release + notes) + +Reproducible Build +------------------ + +Starting with IPython 7.15, I am attempting to provide reproducible builds, +that is to say you should be able from the source tree to generate an sdist +and wheel that are identical byte for byte with the publish version on PyPI. + +I've only tested on a couple of machines so far and the process is relatively +straightforward, so this mean that IPython not only have a deterministic build +process, but also I have either removed, or put under control all effects of +the build environments on the final artifact. I encourage you to attempt the +build process on your machine as documented in :ref:`core_developer_guide` +and let me know if you do not obtain an identical artifact. + +While reproducible builds is critical to check that the supply chain of (open +source) software has not been compromised, it can also help to speedup many +of the build processes in large environment (conda, apt...) by allowing +better caching of intermediate build steps. + +Learn more on ``_. `Reflections on trusting +trust `_ is also one of the +cornerstone and recommended reads on this subject. + +.. note:: + + The build commit from which the sdist is generated is also `signed + `_, so you should be able to + check it has not been compromised, and the git repository is a `merkle-tree + `_, you can check the consistency + with `git-fsck `_ which you likely `want + to enable by default + `_. + +NEP29: Last version to support Python 3.6 +----------------------------------------- + +IPython 7.15 will be the Last IPython version to officially support Python +3.6, as stated by `NumPy Enhancement Proposal 29 +`_. Starting with +next minor version of IPython I may stop testing on Python 3.6 and may stop +publishing release artifacts that install on Python 3.6 + +Highlighted features +-------------------- + +Highlighted features are not new, but seem to not be widely known, this +section will help you discover in more narrative form what you can do with +IPython. + +Increase Tab Completion Menu Height +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In terminal IPython it is possible to increase the height of the tab-completion +menu. To do so set the value of +:configtrait:`TerminalInteractiveShell.space_for_menu`, this will reserve more +space at the bottom of the screen for various kind of menus in IPython including +tab completion and searching in history. + +Autoformat Code in the terminal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have a preferred code formatter, you can configure IPython to +reformat your code. Set the value of +:configtrait:`TerminalInteractiveShell.autoformatter` to for example ``'black'`` +and IPython will auto format your code when possible. + + +.. _version 714: + +IPython 7.14 +============ + +IPython 7.14 is a minor release that fix a couple of bugs and prepare +compatibility with new or future versions of some libraries. + +Important changes: +------------------ + + - Fix compatibility with Sphinx 3+ :ghpull:`12235` + - Remove deprecated matplotlib parameter usage, compatibility with matplotlib + 3.3+ :`122250` + +Misc Changes +------------ + + - set ``.py`` extension when editing current buffer in vi/emacs. :ghpull:`12167` + - support for unicode identifiers in ``?``/``??`` :ghpull:`12208` + - add extra options to the ``Video`` Rich objects :ghpull:`12212` + - add pretty-printing to ``SimpleNamespace`` :ghpull:`12230` + +IPython.core.debugger.Pdb is now interruptible +---------------------------------------------- + +A ``KeyboardInterrupt`` will now interrupt IPython's extended debugger, in order to make Jupyter able to interrupt it. (:ghpull:`12168`) + +Video HTML attributes +--------------------- + +Add an option to `IPython.display.Video` to change the attributes of the HTML display of the video (:ghpull:`12212`) + + +Pending deprecated imports +-------------------------- + +Many object present in ``IPython.core.display`` are there for internal use only, +and should already been imported from ``IPython.display`` by users and external +libraries. Trying to import those from ``IPython.core.display`` is still possible +but will trigger a +deprecation warning in later versions of IPython and will become errors in the +future. + +This will simplify compatibility with other Python kernels (like Xeus-Python), +and simplify code base. + + + + +.. _version 713: + +IPython 7.13 +============ + +IPython 7.13 is the final release of the 7.x branch since master is diverging +toward an 8.0. Exiting new features have already been merged in 8.0 and will +not be available on the 7.x branch. All the changes below have been backported +from the master branch. + + + - Fix inability to run PDB when inside an event loop :ghpull:`12141` + - Fix ability to interrupt some processes on windows :ghpull:`12137` + - Fix debugger shortcuts :ghpull:`12132` + - improve tab completion when inside a string by removing irrelevant elements :ghpull:`12128` + - Fix display of filename tab completion when the path is long :ghpull:`12122` + - Many removal of Python 2 specific code path :ghpull:`12110` + - displaying wav files do not require NumPy anymore, and is 5x to 30x faster :ghpull:`12113` + +See the list of all closed issues and pull request on `github +`_. + +.. _version 712: + +IPython 7.12 +============ + +IPython 7.12 is a minor update that mostly brings code cleanup, removal of +longtime deprecated function and a couple update to documentation cleanup as well. + +Notable changes are the following: + + - Exit non-zero when ipython is given a file path to run that doesn't exist :ghpull:`12074` + - Test PR on ARM64 with Travis-CI :ghpull:`12073` + - Update CI to work with latest Pytest :ghpull:`12086` + - Add infrastructure to run ipykernel eventloop via trio :ghpull:`12097` + - Support git blame ignore revs :ghpull:`12091` + - Start multi-line ``__repr__`` s on their own line :ghpull:`12099` + +.. _version 7111: + +IPython 7.11.1 +============== + +A couple of deprecated functions (no-op) have been reintroduces in py3compat as +Cython was still relying on them, and will be removed in a couple of versions. + +.. _version 711: + +IPython 7.11 +============ + +IPython 7.11 received a couple of compatibility fixes and code cleanup. + +A number of function in the ``py3compat`` have been removed; a number of types +in the IPython code base are now non-ambiguous and now always ``unicode`` +instead of ``Union[Unicode,bytes]``; many of the relevant code path have thus +been simplified/cleaned and types annotation added. + +IPython support several verbosity level from exceptions. ``xmode plain`` now +support chained exceptions. :ghpull:`11999` + +We are starting to remove ``shell=True`` in some usages of subprocess. While not directly +a security issue (as IPython is made to run arbitrary code anyway) it is not good +practice and we'd like to show the example. :ghissue:`12023`. This discussion +was started by ``@mschwager`` thanks to a new auditing tool they are working on +with duo-labs (`dlint `_). + +Work around some bugs in Python 3.9 tokenizer :ghpull:`12057` + +IPython will now print its version after a crash. :ghpull:`11986` + +This is likely the last release from the 7.x series that will see new feature. +The master branch will soon accept large code changes and thrilling new +features; the 7.x branch will only start to accept critical bug fixes, and +update dependencies. + +.. _version 7102: + +IPython 7.10.2 +============== + +IPython 7.10.2 fix a couple of extra incompatibility between IPython, ipdb, +asyncio and Prompt Toolkit 3. + +.. _version 7101: + +IPython 7.10.1 +============== + +IPython 7.10.1 fix a couple of incompatibilities with Prompt toolkit 3 (please +update Prompt toolkit to 3.0.2 at least), and fixes some interaction with +headless IPython. + +.. _version 7100: + +IPython 7.10.0 +============== + +IPython 7.10 is the first double digit minor release in the last decade, and +first since the release of IPython 1.0, previous double digit minor release was +in August 2009. + +We've been trying to give you regular release on the last Friday of every month +for a guaranty of rapid access to bug fixes and new features. + +Unlike the previous first few releases that have seen only a couple of code +changes, 7.10 bring a number of changes, new features and bugfixes. + +Stop Support for Python 3.5 – Adopt NEP 29 +------------------------------------------ + +IPython has decided to follow the informational `NEP 29 +`_ which layout a clear +policy as to which version of (C)Python and NumPy are supported. + +We thus dropped support for Python 3.5, and cleaned up a number of code path +that were Python-version dependent. If you are on 3.5 or earlier pip should +automatically give you the latest compatible version of IPython so you do not +need to pin to a given version. + +Support for Prompt Toolkit 3.0 +------------------------------ + +Prompt Toolkit 3.0 was release a week before IPython 7.10 and introduces a few +breaking changes. We believe IPython 7.10 should be compatible with both Prompt +Toolkit 2.x and 3.x, though it has not been extensively tested with 3.x so +please report any issues. + + +Prompt Rendering Performance improvements +----------------------------------------- + +Pull Request :ghpull:`11933` introduced an optimisation in the prompt rendering +logic that should decrease the resource usage of IPython when using the +_default_ configuration but could potentially introduce a regression of +functionalities if you are using a custom prompt. + +We know assume if you haven't changed the default keybindings that the prompt +**will not change** during the duration of your input – which is for example +not true when using vi insert mode that switches between `[ins]` and `[nor]` +for the current mode. + +If you are experiencing any issue let us know. + +Code autoformatting +------------------- + +The IPython terminal can now auto format your code just before entering a new +line or executing a command. To do so use the +``--TerminalInteractiveShell.autoformatter`` option and set it to ``'black'``; +if black is installed IPython will use black to format your code when possible. + +IPython cannot always properly format your code; in particular it will +auto formatting with *black* will only work if: + + - Your code does not contains magics or special python syntax. + + - There is no code after your cursor. + +The Black API is also still in motion; so this may not work with all versions of +black. + +It should be possible to register custom formatter, though the API is till in +flux. + +Arbitrary Mimetypes Handing in Terminal (Aka inline images in terminal) +----------------------------------------------------------------------- + +When using IPython terminal it is now possible to register function to handle +arbitrary mimetypes. While rendering non-text based representation was possible in +many jupyter frontend; it was not possible in terminal IPython, as usually +terminal are limited to displaying text. As many terminal these days provide +escape sequences to display non-text; bringing this loved feature to IPython CLI +made a lot of sens. This functionality will not only allow inline images; but +allow opening of external program; for example ``mplayer`` to "display" sound +files. + +So far only the hooks necessary for this are in place, but no default mime +renderers added; so inline images will only be available via extensions. We will +progressively enable these features by default in the next few releases, and +contribution is welcomed. + +We welcome any feedback on the API. See :ref:`shell_mimerenderer` for more +information. + +This is originally based on work form in :ghpull:`10610` from @stephanh42 +started over two years ago, and still a lot need to be done. + +MISC +---- + + - Completions can define their own ordering :ghpull:`11855` + - Enable Plotting in the same cell than the one that import matplotlib + :ghpull:`11916` + - Allow to store and restore multiple variables at once :ghpull:`11930` + +You can see `all pull-requests `_ for this release. + +API Changes +----------- + +Change of API and exposed objects automatically detected using `frappuccino `_ (still in beta): + +The following items are new in IPython 7.10:: + + + IPython.terminal.shortcuts.reformat_text_before_cursor(buffer, document, shell) + + IPython.terminal.interactiveshell.PTK3 + + IPython.terminal.interactiveshell.black_reformat_handler(text_before_cursor) + + IPython.terminal.prompts.RichPromptDisplayHook.write_format_data(self, format_dict, md_dict='None') + +The following items have been removed in 7.10:: + + - IPython.lib.pretty.DICT_IS_ORDERED + +The following signatures differ between versions:: + + - IPython.extensions.storemagic.restore_aliases(ip) + + IPython.extensions.storemagic.restore_aliases(ip, alias='None') + +Special Thanks +-------------- + + - @stephanh42 who started the work on inline images in terminal 2 years ago + - @augustogoulart who spent a lot of time triaging issues and responding to + users. + - @con-f-use who is my (@Carreau) first sponsor on GitHub, as a reminder if you + like IPython, Jupyter and many other library of the SciPy stack you can + donate to numfocus.org non profit + +.. _version 790: + +IPython 7.9.0 +============= + +IPython 7.9 is a small release with a couple of improvement and bug fixes. + + - Xterm terminal title should be restored on exit :ghpull:`11910` + - special variables ``_``,``__``, ``___`` are not set anymore when cache size + is 0 or less. :ghpull:`11877` + - Autoreload should have regained some speed by using a new heuristic logic to + find all objects needing reload. This should avoid large objects traversal + like pandas dataframes. :ghpull:`11876` + - Get ready for Python 4. :ghpull:`11874` + - `%env` Magic now has heuristic to hide potentially sensitive values :ghpull:`11896` + +This is a small release despite a number of Pull Request Pending that need to +be reviewed/worked on. Many of the core developers have been busy outside of +IPython/Jupyter and we thanks all contributor for their patience; we'll work on +these as soon as we have time. + + +.. _version780: + +IPython 7.8.0 +============= + +IPython 7.8.0 contain a few bugfix and 2 new APIs: + + - Enable changing the font color for LaTeX rendering :ghpull:`11840` + - and Re-Expose some PDB API (see below) + +Expose Pdb API +-------------- + +Expose the built-in ``pdb.Pdb`` API. ``Pdb`` constructor arguments are generically +exposed, regardless of python version. +Newly exposed arguments: + +- ``skip`` - Python 3.1+ +- ``nosiginnt`` - Python 3.2+ +- ``readrc`` - Python 3.6+ + +Try it out:: + + from IPython.terminal.debugger import TerminalPdb + pdb = TerminalPdb(skip=["skipthismodule"]) + + +See :ghpull:`11840` + +.. _version770: + +IPython 7.7.0 +============= + +IPython 7.7.0 contain multiple bug fixes and documentation updates; Here are a +few of the outstanding issue fixed: + + - Fix a bug introduced in 7.6 where the ``%matplotlib`` magic would fail on + previously acceptable arguments :ghpull:`11814`. + - Fix the manage location on freebsd :ghpull:`11808`. + - Fix error message about aliases after ``%reset`` call in ipykernel + :ghpull:`11806` + - Fix Duplication completions in emacs :ghpull:`11803` + +We are planning to adopt `NEP29 `_ +(still currently in draft) which may make this minor version of IPython the +last one to support Python 3.5 and will make the code base more aggressive +toward removing compatibility with older versions of Python. + +GitHub now support to give only "Triage" permissions to users; if you'd like to +help close stale issues and labels issues please reach to us with your GitHub +Username and we'll add you to the triage team. It is a great way to start +contributing and a path toward getting commit rights. + +.. _version761: + +IPython 7.6.1 +============= + +IPython 7.6.1 contain a critical bugfix in the ``%timeit`` magic, which would +crash on some inputs as a side effect of :ghpull:`11716`. See :ghpull:`11812` + + +.. _whatsnew760: + +IPython 7.6.0 +============= + +IPython 7.6.0 contains a couple of bug fixes and number of small features +additions as well as some compatibility with the current development version of +Python 3.8. + + - Add a ``-l`` option to :magic:`psearch` to list the available search + types. :ghpull:`11672` + - Support ``PathLike`` for ``DisplayObject`` and ``Image``. :ghpull:`11764` + - Configurability of timeout in the test suite for slow platforms. + :ghpull:`11756` + - Accept any casing for matplotlib backend. :ghpull:`121748` + - Properly skip test that requires numpy to be installed :ghpull:`11723` + - More support for Python 3.8 and positional only arguments (pep570) + :ghpull:`11720` + - Unicode names for the completion are loaded lazily on first use which + should decrease startup time. :ghpull:`11693` + - Autoreload now update the types of reloaded objects; this for example allow + pickling of reloaded objects. :ghpull:`11644` + - Fix a bug where ``%%time`` magic would suppress cell output. :ghpull:`11716` + + +Prepare migration to pytest (instead of nose) for testing +--------------------------------------------------------- + +Most of the work between 7.5 and 7.6 was to prepare the migration from our +testing framework to pytest. Most of the test suite should now work by simply +issuing ``pytest`` from the root of the repository. + +The migration to pytest is just at its beginning. Many of our test still rely +on IPython-specific plugins for nose using pytest (doctest using IPython syntax +is one example of this where test appear as "passing", while no code has been +ran). Many test also need to be updated like ``yield-test`` to be properly +parametrized tests. + +Migration to pytest allowed me to discover a number of issues in our test +suite; which was hiding a number of subtle issues – or not actually running +some of the tests in our test suite – I have thus corrected many of those; like +improperly closed resources; or used of deprecated features. I also made use of +the ``pytest --durations=...`` to find some of our slowest test and speed them +up (our test suite can now be up to 10% faster). Pytest as also a variety of +plugins and flags which will make the code quality of IPython and the testing +experience better. + +Misc +---- + +We skipped the release of 7.6 at the end of May, but will attempt to get back +on schedule. We are starting to think about making introducing backward +incompatible change and start the 8.0 series. + +Special Thanks to Gabriel (@gpotter2 on GitHub), who among other took care many +of the remaining task for 7.4 and 7.5, like updating the website. + +.. _whatsnew750: + +IPython 7.5.0 +============= + +IPython 7.5.0 consist mostly of bug-fixes, and documentation updates, with one +minor new feature. The `Audio` display element can now be assigned an element +id when displayed in browser. See :ghpull:`11670` + +The major outstanding bug fix correct a change of behavior that was introduce +in 7.4.0 where some cell magics would not be able to access or modify global +scope when using the ``@needs_local_scope`` decorator. This was typically +encountered with the ``%%time`` and ``%%timeit`` magics. See :ghissue:`11659` +and :ghpull:`11698`. + +.. _whatsnew740: + +IPython 7.4.0 +============= + +Unicode name completions +------------------------ + +Previously, we provided completion for a unicode name with its relative symbol. +With this, now IPython provides complete suggestions to unicode name symbols. + +As on the PR, if user types ``\LAT``, IPython provides a list of +possible completions. In this case, it would be something like:: + + 'LATIN CAPITAL LETTER A', + 'LATIN CAPITAL LETTER B', + 'LATIN CAPITAL LETTER C', + 'LATIN CAPITAL LETTER D', + .... + +This help to type unicode character that do not have short latex aliases, and +have long unicode names. for example ``Ͱ``, ``\GREEK CAPITAL LETTER HETA``. + +This feature was contributed by Luciana Marques :ghpull:`11583`. + +Make audio normalization optional +--------------------------------- + +Added 'normalize' argument to `IPython.display.Audio`. This argument applies +when audio data is given as an array of samples. The default of `normalize=True` +preserves prior behavior of normalizing the audio to the maximum possible range. +Setting to `False` disables normalization. + + +Miscellaneous +------------- + + - Fix improper acceptation of ``return`` outside of functions. :ghpull:`11641`. + - Fixed PyQt 5.11 backwards incompatibility causing sip import failure. + :ghpull:`11613`. + - Fix Bug where ``type?`` would crash IPython. :ghpull:`1608`. + - Allow to apply ``@needs_local_scope`` to cell magics for convenience. + :ghpull:`11542`. + +.. _whatsnew730: + +IPython 7.3.0 +============= + + +IPython 7.3.0 bring several bug fixes and small improvements that you will +described below. + +The biggest change to this release is the implementation of the ``%conda`` and +``%pip`` magics, that will attempt to install packages in the **current +environment**. You may still need to restart your interpreter or kernel for the +change to be taken into account, but it should simplify installation of packages +into remote environment. Installing using pip/conda from the command line is +still the prefer method. + +The ``%pip`` magic was already present, but was only printing a warning; now it +will actually forward commands to pip. + +Misc bug fixes and improvements: + + - Compatibility with Python 3.8. + - Do not expand shell variable in execution magics, and added the + ``no_var_expand`` decorator for magic requiring a similar functionality + :ghpull:`11516` + - Add ``%pip`` and ``%conda`` magic :ghpull:`11524` + - Re-initialize posix aliases after a ``%reset`` :ghpull:`11528` + - Allow the IPython command line to run ``*.ipynb`` files :ghpull:`11529` + +.. _whatsnew720: + +IPython 7.2.0 +============= + +IPython 7.2.0 brings minor bugfixes, improvements, and new configuration options: + + - Fix a bug preventing PySide2 GUI integration from working :ghpull:`11464` + - Run CI on Mac OS ! :ghpull:`11471` + - Fix IPython "Demo" mode. :ghpull:`11498` + - Fix ``%run`` magic with path in name :ghpull:`11499` + - Fix: add CWD to sys.path *after* stdlib :ghpull:`11502` + - Better rendering of signatures, especially long ones. :ghpull:`11505` + - Re-enable jedi by default if it's installed :ghpull:`11506` + - Add New ``minimal`` exception reporting mode (useful for educational purpose). See :ghpull:`11509` + + +Added ability to show subclasses when using pinfo and other utilities +--------------------------------------------------------------------- + +When using ``?``/``??`` on a class, IPython will now list the first 10 subclasses. + +Special Thanks to Chris Mentzel of the Moore Foundation for this feature. Chris +is one of the people who played a critical role in IPython/Jupyter getting +funding. + +We are grateful for all the help Chris has given us over the years, +and we're now proud to have code contributed by Chris in IPython. + +OSMagics.cd_force_quiet configuration option +-------------------------------------------- + +You can set this option to force the %cd magic to behave as if ``-q`` was passed:: + + In [1]: cd / + / + + In [2]: %config OSMagics.cd_force_quiet = True + + In [3]: cd /tmp + + In [4]: + +See :ghpull:`11491` + +In vi editing mode, whether the prompt includes the current vi mode can now be configured +----------------------------------------------------------------------------------------- + +Set the ``TerminalInteractiveShell.prompt_includes_vi_mode`` to a boolean value +(default: True) to control this feature. See :ghpull:`11492` + +.. _whatsnew710: + +IPython 7.1.0 +============= + +IPython 7.1.0 is the first minor release after 7.0.0 and mostly brings fixes to +new features, internal refactoring, and fixes for regressions that happened during the 6.x->7.x +transition. It also brings **Compatibility with Python 3.7.1**, as we're +unwillingly relying on a bug in CPython. + +New Core Dev: + + - We welcome Jonathan Slenders to the committers. Jonathan has done a fantastic + work on prompt_toolkit, and we'd like to recognise his impact by giving him + commit rights. :ghissue:`11397` + +Notable Changes + + - Major update of "latex to unicode" tab completion map (see below) + +Notable New Features: + + - Restore functionality and documentation of the **sphinx directive**, which + is now stricter (fail on error by daefault), has new configuration options, + has a brand new documentation page :ref:`ipython_directive` (which needs + some cleanup). It is also now *tested* so we hope to have less regressions. + :ghpull:`11402` + + - ``IPython.display.Video`` now supports ``width`` and ``height`` arguments, + allowing a custom width and height to be set instead of using the video's + width and height. :ghpull:`11353` + + - Warn when using ``HTML('\n", - " " - ], - "text/plain": [ - "" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import YouTubeVideo\n", - "YouTubeVideo('sjfsUzECqK0')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using the nascent video capabilities of modern browsers, you may also be able to display local\n", - "videos. At the moment this doesn't work very well in all browsers, so it may or may not work for you;\n", - "we will continue testing this and looking for ways to make it more robust. \n", - "\n", - "The following cell loads a local file called `animation.m4v`, encodes the raw video as base64 for http\n", - "transport, and uses the HTML5 video tag to load it. On Chrome 15 it works correctly, displaying a control bar at the bottom with a play/pause button and a location slider." - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/html": [ - "

Elementary operations

" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$\\frac{3 \\pi}{2} + \\frac{e^{i x}}{x^{2} + y}$$" - ], - "text/plain": [ - " ⅈ⋅x \n", - "3⋅π ℯ \n", - "─── + ──────\n", - " 2 2 \n", - " x + y" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Rational(3,2)*pi + exp(I*x) / (x**2 + y)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$-1.0$$" - ], - "text/plain": [ - "-1.00000000000000" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exp(I*x).subs(x,pi).evalf()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "e = x + 2*y" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\"Add(Symbol('x'), Mul(Integer(2), Symbol('y')))\"" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "srepr(e)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$262537412640768743.99999999999925007259719818568888$$" - ], - "text/plain": [ - "262537412640768743.99999999999925007259719818568888" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exp(pi * sqrt(163)).evalf(50)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "

Algebra

" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$\\left(x + 1\\right) \\left(x + y\\right)^{2}$$" - ], - "text/plain": [ - " 2\n", - "(x + 1)⋅(x + y) " - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "eq = ((x+y)**2 * (x+1))\n", - "eq" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$x^{3} + 2 x^{2} y + x^{2} + x y^{2} + 2 x y + y^{2}$$" - ], - "text/plain": [ - " 3 2 2 2 2\n", - "x + 2⋅x ⋅y + x + x⋅y + 2⋅x⋅y + y " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "expand(eq)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$\\frac{1}{x} \\left(x \\sin{\\left (x \\right )} - 1\\right) + \\frac{1}{x}$$" - ], - "text/plain": [ - "x⋅sin(x) - 1 1\n", - "──────────── + ─\n", - " x x" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "a = 1/x + (x*sin(x) - 1)/x\n", - "a" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$\\sin{\\left (x \\right )}$$" - ], - "text/plain": [ - "sin(x)" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "simplify(a)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$x^{3} + 2 x^{2} + 4 x + 8 = 0$$" - ], - "text/plain": [ - " 3 2 \n", - "x + 2⋅x + 4⋅x + 8 = 0" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "eq = Eq(x**3 + 2*x**2 + 4*x + 8, 0)\n", - "eq" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$\\left [ -2, \\quad - 2 i, \\quad 2 i\\right ]$$" - ], - "text/plain": [ - "[-2, -2⋅ⅈ, 2⋅ⅈ]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "solve(eq, x)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$\\sum_{n=a}^{b} \\left(2^{n} + 6 n^{2}\\right)$$" - ], - "text/plain": [ - " b \n", - " ___ \n", - " ╲ \n", - " ╲ ⎛ n 2⎞\n", - " ╱ ⎝2 + 6⋅n ⎠\n", - " ╱ \n", - " ‾‾‾ \n", - "n = a " - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "a, b = symbols('a b')\n", - "Sum(6*n**2 + 2**n, (n, a, b))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "

Calculus

" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$- \\frac{1}{6}$$" - ], - "text/plain": [ - "-1/6" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "limit((sin(x)-x)/x**3, x, 0)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$1 + \\frac{x^{2}}{2} + \\frac{5 x^{4}}{24} + \\mathcal{O}\\left(x^{6}\\right)$$" - ], - "text/plain": [ - " 2 4 \n", - " x 5⋅x ⎛ 6⎞\n", - "1 + ── + ──── + O⎝x ⎠\n", - " 2 24 " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(1/cos(x)).series(x, 0, 6)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$- \\frac{4 x \\cos{\\left (x^{2} \\right )}}{x + 1} \\sin{\\left (x^{2} \\right )} - \\frac{\\cos^{2}{\\left (x^{2} \\right )}}{\\left(x + 1\\right)^{2}}$$" - ], - "text/plain": [ - " ⎛ 2⎞ ⎛ 2⎞ 2⎛ 2⎞\n", - " 4⋅x⋅sin⎝x ⎠⋅cos⎝x ⎠ cos ⎝x ⎠\n", - "- ─────────────────── - ────────\n", - " x + 1 2\n", - " (x + 1) " - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "diff(cos(x**2)**2 / (1+x), x)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$-2 + \\frac{\\pi^{2}}{4}$$" - ], - "text/plain": [ - " 2\n", - " π \n", - "-2 + ──\n", - " 4 " - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "integrate(x**2 * cos(x), (x, 0, pi/2))" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$9 f{\\left (x \\right )} + \\frac{d^{2}}{d x^{2}} f{\\left (x \\right )} = 1$$" - ], - "text/plain": [ - " 2 \n", - " d \n", - "9⋅f(x) + ───(f(x)) = 1\n", - " 2 \n", - " dx " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/latex": [ - "$$f{\\left (x \\right )} = C_{1} \\sin{\\left (3 x \\right )} + C_{2} \\cos{\\left (3 x \\right )} + \\frac{1}{9}$$" - ], - "text/plain": [ - "f(x) = C₁⋅sin(3⋅x) + C₂⋅cos(3⋅x) + 1/9" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "eqn = Eq(Derivative(f(x),x,x) + 9*f(x), 1)\n", - "display(eqn)\n", - "dsolve(eqn, f(x))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Illustrating Taylor series\n", - "\n", - "We will define a function to compute the Taylor series expansions of a symbolically defined expression at\n", - "various orders and visualize all the approximations together with the original function" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# You can change the default figure size to be a bit larger if you want,\n", - "# uncomment the next line for that:\n", - "#plt.rc('figure', figsize=(10, 6))" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def plot_taylor_approximations(func, x0=None, orders=(2, 4), xrange=(0,1), yrange=None, npts=200):\n", - " \"\"\"Plot the Taylor series approximations to a function at various orders.\n", - "\n", - " Parameters\n", - " ----------\n", - " func : a sympy function\n", - " x0 : float\n", - " Origin of the Taylor series expansion. If not given, x0=xrange[0].\n", - " orders : list\n", - " List of integers with the orders of Taylor series to show. Default is (2, 4).\n", - " xrange : 2-tuple or array.\n", - " Either an (xmin, xmax) tuple indicating the x range for the plot (default is (0, 1)),\n", - " or the actual array of values to use.\n", - " yrange : 2-tuple\n", - " (ymin, ymax) tuple indicating the y range for the plot. If not given,\n", - " the full range of values will be automatically used. \n", - " npts : int\n", - " Number of points to sample the x range with. Default is 200.\n", - " \"\"\"\n", - " if not callable(func):\n", - " raise ValueError('func must be callable')\n", - " if isinstance(xrange, (list, tuple)):\n", - " x = np.linspace(float(xrange[0]), float(xrange[1]), npts)\n", - " else:\n", - " x = xrange\n", - " if x0 is None: x0 = x[0]\n", - " xs = sym.Symbol('x')\n", - " # Make a numpy-callable form of the original function for plotting\n", - " fx = func(xs)\n", - " f = sym.lambdify(xs, fx, modules=['numpy'])\n", - " # We could use latex(fx) instead of str(), but matploblib gets confused\n", - " # with some of the (valid) latex constructs sympy emits. So we play it safe.\n", - " plt.plot(x, f(x), label=str(fx), lw=2)\n", - " # Build the Taylor approximations, plotting as we go\n", - " apps = {}\n", - " for order in orders:\n", - " app = fx.series(xs, x0, n=order).removeO()\n", - " apps[order] = app\n", - " # Must be careful here: if the approximation is a constant, we can't\n", - " # blindly use lambdify as it won't do the right thing. In that case, \n", - " # evaluate the number as a float and fill the y array with that value.\n", - " if isinstance(app, sym.numbers.Number):\n", - " y = np.zeros_like(x)\n", - " y.fill(app.evalf())\n", - " else:\n", - " fa = sym.lambdify(xs, app, modules=['numpy'])\n", - " y = fa(x)\n", - " tex = sym.latex(app).replace('$', '')\n", - " plt.plot(x, y, label=r'$n=%s:\\, %s$' % (order, tex) )\n", - " \n", - " # Plot refinements\n", - " if yrange is not None:\n", - " plt.ylim(*yrange)\n", - " plt.grid()\n", - " plt.legend(loc='best').get_frame().set_alpha(0.8)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With this function defined, we can now use it for any sympy function or expression" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "image/png": [ - "iVBORw0KGgoAAAANSUhEUgAABK8AAAL7CAYAAAA7y6Y/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n", - "AAAWJQAAFiUBSVIk8AAAIABJREFUeJzs3Xl0U2X+P/D3TfeN0lIIBYoQFimLiKwii1AhgigjSiTg\n", - "bkWugoLMV6e/qJSOmSpyQBSNIOKCejEoCqgYFgWqI7uKsktZCi0BytqF0jb5/ZHUQWjpluTJ8n6d\n", - "Myfc3O0t8zmh/eR5nivZ7XYQERERERERERF5I5XoAERERERERERERFVh84qIiIiIiIiIiLwWm1dE\n", - "REREREREROS12LwiIiIiIiIiIiKvxeYVERERERERERF5LTaviIiIiIiIiIjIa7F5RURERERERERE\n", - "XovNKyIiIiIiIiIi8lpsXhERERERERERkddi84qIiIiIiIiIiLwWm1dEREREREREROS12LwiIiIi\n", - "IiIiIiKvxeYVERERERERERF5LTaviIiIiIiIiIjIawV7+oY6nS4MwNMAHgKgAXAWwCoA6Waz+VAN\n", - "r6EG8G8AwwDEA8gGMNdsNs9zR2YiIiIiIiIiIhJDstvtHruZTqdTAfgSgBbAHADrASQBeBZAHIBe\n", - "1TWwdDpdQwCb4Bg19gqAXAADAEwBMNtsNqe5Kz8REREREREREXmWp0de3en830Nms3lRxZs6ne4L\n", - "ADsBvATg0WqukQGgIYCuZrP5uPO9lTqdbj+A+TqdbrHZbP7N9dGJiIiIiIiIiMjTPL3mVSGA1wB8\n", - "fPmbZrP5FBxTB3tf62TnlMOH4JgiePyK3e8DOARgvKvCEhERERERERGRWB4deWU2m9cAWFPF7nAA\n", - "l6q5xE0AYgB8U8m17TqdbiWAwfUKSUREREREREREXsMrnjao0+kaw7H4+oZqDm3rfN1Txf59ANq4\n", - "KhcREREREREREYklvHml0+kkAO/CMQpsVjWHxwMoM5vNRVXsPwsgVKfTRbowIhERERERERERCSK8\n", - "eQVHw+ouAP80m82Hqzk2BsDFa+yvaGrFuiIYERERERERERGJJbR5pdPpXgDwDBwLsM+twSkX4Fgb\n", - "qyoVI67O1TcbERERERERERGJ59EF2y+n0+kmAMgA8JHZbH66hqedBhCs0+kiq5g6GAvg0jWmFV5l\n", - "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\n", - "OgAXANxuNOn3iU1FRETeRJumiQTwD+fmYpFZiIgChdFoVO/fvz+iWbNml0RnqQ273Y5OnTqVAMCJ\n", - "EydCbrnllvNNmzYtz8jIyI2KirKJzkfu4VMdViLySf8G8ASAEgB3Gk367YLzEBGR9xkOIBrAVktm\n", - "9p+iwxAR+btDhw6FbNiwISYxMdGnGlcAkJaWdrLiz5s3b47u379/AQDccMMNJW3atCl19/1zcnKC\n", - "u3bt2sHd96G/Y/OKiNzGICtTABgAlAPQGU369YIjERGRd/pryqDQFEREAWLy5MktXnvtNZ9exiM/\n", - "Pz9o7969kYMGDSrw5H2LiopUp06dCvHkPYnNKyJyE4OsPARglnPzUaNJv1xkHiIi8k7aNE0sgDsA\n", - "2OHepwwSERGAL7/8skHz5s0v+eIi5iUlJdLy5ctjAGD16tXR0dHR5d26dbsIAD/++GPkypUro8Um\n", - "JHdh84pIMEmS0kVncDWDrNwF4D3n5rNGk/4jkXmocv5Ye+Q7WH90mX8ACAOwwZKZfczdN2PtkSis\n", - "vcCQl5cXPGDAgHbJyckdU1NTWwDA6dOnVampqS369u3bLjk5uWOfPn3a79y5M8yTuXJycpoBQHFx\n", - "sfTaa6+pX3311asWcK+J1NTUFgkJCV0tFouQJtGsWbMSRo8e3a6goED69ttvY+Pi4koBoLy8HB99\n", - "9FH80KFDPToKq7bmzp3bqE+fPu1vvPHGDikpKW1yc3OD9+/fHzp48OC2ffv2ba/VatscOnSIo7oq\n", - "wQXbicSbBiBddAhXMcjKrQDMAIIAGI0m/Wyxiega/Kr2yOew/qiCp6cMsvZIFNZeAHj22Webz5w5\n", - "86gkSfbevXt3at269aV169bFZGRk5N58883FNpsNnTp16vjggw+22rZt215P5bJarYlJSUm5L774\n", - "YtOnnnrqRHR0dJ2e6rp48eLGRUVFqs8++yxOq9V6vFF02223Xdi2bdvpF154IfGxxx47FRoaanvq\n", - "qaeaS5KEqVOnnggKCvJ0pBpLT09Xnz17Nuinn37aFxQUBK1W22bUqFGaiIgI2wcffHB47969oWPH\n", - "jm2bkZHRdOHChTmi83obNq+IxJsuOoCrGGTlJgDL4fgG/R0AL4pNRNXwm9ojn8T6I2jTNI0B3Aag\n", - "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\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 381, - "width": 599 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "plot_taylor_approximations(sin, 0, [2, 4, 6], (0, 2*pi), (-2,2))" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "image/png": [ - "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\n", - "S5SWli596623ut9zzz1Fzz333FbDhg1bMXXq1E8eeeSReQ2XB4bD4VRVVdXHf/7znz9PpVI89NBD\n", - "/R5//PGti4qK1txyyy3z7r///h/9Nv/oo49e+sEHH2xRVVW1BcCNN97Y91//+lfvX//614tOOOGE\n", - "DZr+l5WVLRw1atSyf//7370mT568DcC8efMKZ86c2euII45Y2rVr1xZ/6K+trW3uFvMiWTV1/cKj\n", - "gVpgOHBowOFI/pkQdACSt5R7EhTlngRJ+Sc5LeiZVxv7wP85Xh+KHxUS4vH4xdFo9FPgTOA8vKLV\n", - "M8D4eDz+bVsFKtnRu3fvOtu2F9i2vWBTx+63336rHnnkkXnNPXdhYSGTJ0+unTx58iZ7T5111lkL\n", - "77///m1uvvnmbUaMGDHvggsuWHTBBRdstI9ZwxlXALfccku/urq60Omnn96q/mdFRUWb/F6ItIW1\n", - "pCYBa4CrAQt4PtiIJM9MCjoAyVvKPQmKck+CpPyTnBZYw/b2Qg3b89d+++23+9tvv73l7Nmz3x8+\n", - "fHhyc9771Vdfddp11133GDBgwJqPPvrog7aKUdqXjtSwvV5FONIbr8/QlsBesWRCfQNFRERERKTV\n", - "stmwvT0vGxRpU9ddd11NKpUKjRs3bvvNfW9ZWdmAFStWdLrpppvmt0VsIn6JJRNLgXvTTy8OMhYR\n", - "EREREZHGqHgleWvkyJErTz755K+PO+64pZs++sd233331dFodNHo0aO1y6B0BDcC64ETKsKRHYMO\n", - "RkREREREpKGge16JBCqzkXtzXXrppQuzHYtIUGLJxGcV4cg04ETgfOCCgEMSERERERH5nmZeiYgI\n", - "gJ3++od0HywREREREZF2QcUrkYDV1NQMCDoGyU+hUGhi/X+nG7W/gNe4/YygYpL80TD/RPyk3JOg\n", - "KPckSMo/yXUqXokErLa2tn/QMUjempDxvH721bkV4UjY72Ak72Tmn4hflHsSFOWeBEn5JzlNxSuR\n", - "gBUVFS0IOgbJW5Mynj8P/BfYFviN/+FInsnMPxG/KPckKMo9CZLyT3KailciARs4cOCXQccg+SmV\n", - "Sk1s+DyWTKSA69JPrYpwRP9GSJvJzD8Rvyj3JCjKPQmS8k9ynT6YiIhIQ9OAGmB34OcBxyIiIiIi\n", - "IqLilYiI/CCWTKwFbkw/tYKMRUREREREBFS8EhGRDd0LLAMOrghHDgg6GBERERERyW8qXomIyI/E\n", - "konvgDvSTy8OMhYREREREREVr0QCVlNTMyDoGCQ/hUKhiRsZngKsBcZUhCOD/IlI8skm8k+kzSj3\n", - "JCjKPQmS8k9ynYpXIgGrra3tH3QMkrcmNDUQSya+BKbi/Ttxrm8RST5pMv9E2phyT4Ki3JMgKf8k\n", - "p6l4JRKwoqKiBUHHIHlr0ibG6xu3n1oRjvRq62Ak72wq/0TainJPgqLckyAp/ySnqXglErCBAwd+\n", - "GXQMkp9SqdTEjY3Hkol3gJeAHsCpPoQkeWRT+SfSVpR7EhTlngRJ+Se5TsUrERHZmPrZV+dWhCOd\n", - "A41ERERERETykopXIiKyMU8Bc4AdgTEBxyIiIiIiInlIxSsREWlSLJmoA25KP70gyFhERERERCQ/\n", - "qXgl0sBjjz3WY9SoUbsYhrH7oEGDho4ePbr4hRde2NLvOGprazvtuuuuQ+fNm1fo97VFGnE/sBQ4\n", - "sCIcOSDgWEREREREJM+oeCWSdvvtt2996623bvPEE09Uu6770dtvv50AOOKIIwZfffXV27TVdWtq\n", - "agbU//eyZcsK7rjjjq333HPPIdXV1V3Xrl0baqvrioRCoYnNOS6WTKwA7ko/1ewryYrm5p9Itin3\n", - "JCjKPQmS8k9ynYpXIsD8+fM7X3fddf1nzJjxac+ePesAevfuXTd9+vRPe/fuvXbChAkDX3755S3a\n", - "4tq1tbX9AUaPHl186KGH7jp79uwtunXrVtcW1xLJMGEzjr0VWAf8qiIc2bGN4pH8sjn5J5JNyj0J\n", - "inJPgqT8k5ym4pUIcOedd/YdM2bMkvrCVb3u3bunfv7zny+tq6vj5ptvbpPZV0VFRQsAnn322WrX\n", - "dT+69957P+/bt++6VCrVFpcTaWhScw+MJRNfANPw/t04p80iknzS7PwTyTLlngRFuSdBUv5JTlPx\n", - "SgRwXXfLKVOm9B83blz/zLHBgwevBvjwww+7tcW1Bw4c+GVbnFdkU1Kp1MTNfMuN6a9/qAhHemY5\n", - "HMkzLcg/kaxQ7klQlHsSJOWf5DoVr0SA9evXh9atWxd67LHHemeOrVu3LlR/jP+RibQfsWRiNvAy\n", - "0BM4JeBwREREREQkT6h4Jb5asGBB55EjR+4aiUSGnHbaadsDLFmypOC0007b/qc//emukUhkyAEH\n", - "HLDb+++/H/YzrksvvXTBsGHDVlx00UVfZY795z//2QJg+PDhKzZ1ntNOO237vn37Dq+srOzeFnGK\n", - "tAM3pL+eVxGOdAo0EhERERERyQudgw6goxp/ZsU+QceQDVffEZudzfNdeOGF21133XWfh0Kh1P77\n", - "7z900KBBa1566aUekydP/vLAAw9cVVdXx9ChQ4eMHTt2p9mzZ3+UzWtvzOGHH77i8MMP/zDz9UWL\n", - "FnV69tlnexcUFHDOOecs3NR5pk6d2m/lypUF06ZN611aWrq8baIVCdRTwFxgZ+BY4JFgwxERERER\n", - "kY5OxSvxzapVq0ILFiwo3G+//VY999xzWwLccMMN/d955533d9hhh3UABQUF7LLLLquef/75DZbv\n", - "NXTTTTf1qaio6NOSOAoKCvj73/8+b/DgwWs2dew111yzzcqVKwt+97vffX3wwQev3NTx48aN++Lx\n", - "xx/vfeaZZ26y0CWSi2LJxPqKcOQmYApwASpeiYiIiIhIG1Pxqo1ke8ZSR/Dyyy9v+bOf/ew7gDff\n", - "fHNLgIkTJ35eX7iqV1tb26VXr17rGjtHvfPPP3/x+eefv7jtooXZs2d3nTJlSn/TNL+55557aprz\n", - "niuuuOLrK6644uvNuU5NTc0ANW2XIIRCoYktbN55P3AlcFBFOLJfLJl4K6uBSV5oRf6JtIpyT4Ki\n", - "3JMgKf8k16nnlfimtLR0+aRJk2oBXn755R6dO3dO/e53v1va8JiVK1eG/ve//22x1157Bbrkbtmy\n", - "ZQWxWKzYNM1vnn322bmdOrVda5/a2toNdjgU8cmElrwplkwsB+5OP70ge+FInmlR/olkgXJPgqLc\n", - "kyAp/ySnqXglvqurq8N13R5Dhw5d2aNHj7qGY4899ljPZDJZMHr06GVBxbdu3Tp+8YtfFA8fPnzl\n", - "008/XV1YWNim1ysqKlrQphcQadqkVrz3VmA9cHxFOLJDluKR/NKa/BNpDeWeBEW5J0FS/klOU/FK\n", - "fPfGG290+/bbbzsddNBB32WOzZgxo3dhYWHqpJNO+iaI2ABOOeWUHfr37792+vTp8woKfvgr8tZb\n", - "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\n", - "33fffR0++OCDZpMnTz68tvNLUqz//d56662TRo8e/aPLLrsst1evXj86++yzj/nss88aRWv8aJ5f\n", - "tWpVw9LS0sxQKKS33347a9WqVQmVb28H++9DIp8PhUIFCZZvcuTw5X5fXpaD+Tkfx/MJWH+cT5Pz\n", - "VbfNJGo+zqfu+eq3bCViPs6n9nnqj/OJcr6+PJf700SeNrhC0vnW2peiMN59ki6y1h5d288sWLAg\n", - "JEn9+/ev9TKPRYsWfdenT5+EW/WTDLZu3ZrRpUuX4+64444111xzzT4bfl988cVHLV26tFmrVq3K\n", - "p06durJ79+7BeGe01rYYMWLEMdu2bWtQdczzPLVs2bL8tdde++JAT8m75JJLcl966aVWTz755Dfn\n", - "nHPO9v1dF28jR47scOutt37fqVOnPZL061//uuOiRYtafPTRR5+1a9dun5WMh+KLL75o1K9fvx+v\n", - "WbPmk7p87rzzzuv0zjvvtNi0aVNDL7LqKhQKqXHjxpXZ2dnl77zzzudHH330nmhmratFixa16dOn\n", - "T0eXGVKd35f3vqSekq7IDwamuc4DAAAAoH7q02/Zn6Tb88oY40k6xlr7TQ2nT5P0aZwjoQ5atGhR\n", - "WVpa+vH+zj/11FOr4pmnJsaYrcaYD+vz2RkzZhRLKo5ypENSVlbmTZ8+/YisrKzKP//5zyWSNHbs\n", - "2JIePXq0njJlSuvCwsJS1xklqaa9zZCWJit86/covy/vsfxggCcAAAAAAGnuUG8bjCljTIYxJmev\n", - "wzMlvWOMabvXtfmSekmaGq98QDIoLy/3WrVqVb5z585/f7136dJltyStWLHCF+35mjdvXnnEEUc4\n", - "XSGFpGYlbZTUQ9LJjrMAAAAASACJvvJqiqThxpg+1tp3I8f+LOkchRtY90paJennkq6V9Ji19kU3\n", - "UYHE1KJFi8q9b+ELBAI+SercuXPUb8ts3759+bJlyz6P9rhID/nBwE6/L2+6pBslXSVpsdtEAAAA\n", - "AFxLhJVXB7olZLWkTZK2VB2w1i5VeIXVYkn/T9ILkk6XdKW19ooY5gRi4kAbucdKUVFRq9atW++5\n", - "9tpr2bstjUV7E8UoeiTymu/35R3mNAliJoHrDymO2oMr1B5cov6Q7Jxu2J4I2LAdri1ZsuSkk08+\n", - "eWm85vvqq68anXLKKXl///vfvxs6dOiWg38CVVJtw3bP80KhUOiQN0+MBb8vb4GkfpKuzQ8GJrrO\n", - "g+hL5PpDaqP24Aq1B5eoP7gQzQ3bE2HlFZDWcnJySuI1165du7xhw4Z1fPDBB1fSuIKkca4DHMCU\n", - "yOtVfl8ef9FKTYlcf0ht1B5cofbgEvWHpEbzCnAsNzd3bbzmGjFiRO5NN9207tJLL90sScuXL4/6\n", - "hu1IHqFQqMB1hgOYLWmdpG4KP0kWKSbB6w8pjNqDK9QeXKL+kOxoXgFporCw8Ihzzz1384UXXrhV\n", - "koLBoFdUVJTtOhdQk/xgYI+kaZG3V7nMAgAAAMCtRH/aIIAoKCoqOmzOnDmHBYPBrR9++GGWJK1Z\n", - "syazV69eO1xnAw7gUUm3SbrQ78u7Pj8Y+MF1IAAAAADxR/MKSHHr1q1rcNVVV3UKBoMZ7733XvOq\n", - "457nafjw4QGX2YADyQ8GVvp9ea9KOkfSZZLudxwJAAAAgAM0r4AU17Zt24qysrJlrnMA9fR3hZtX\n", - "V/p9eePzg4FK14EAAAAAxBd7XgGOFRcXt3edAenJ87wC1xlqYY6kVZKOkTTAcRZEUZLUH1IQtQdX\n", - "qD24RP0h2dG8AhwrLS1t5zoD0tadrgMcTH4wUCHpkchbNm5PLQlff0hZ1B5cofbgEvWHpEbzCnAs\n", - "JyenxHUGpK1xrgPU0mOSyiX90u/L6+A6DKImWeoPqYfagyvUHlyi/pDUaF4BjuXm5q51nQHpKRQK\n", - "FbjOUBv5wUCJpFmSGkga7jgOoiRZ6g+ph9qDK9QeXKL+kOxoXgEAksHfI6+/9/vyeNgIAAAAkEZo\n", - "XgEAksHrkr6SdKSkXzjOAgAAACCOaF4BABJefjBQKenhyNuRLrMAAAAAiC+aVwCAZDFdUlDSQL8v\n", - "7xjHWQAAAADECc0rwLHi4uL2rjMgPXmeV+A6Q13kBwMbJFlJnqTfO46DQ5Rs9YfUQe3BFWoPLlF/\n", - "SHY0rwDHSktL27nOgLR1p+sA9VC1cftwvy+vkdMkOFTJWH9IDdQeXKH24BL1h6RG8wpwLCcnp8R1\n", - "BqStca4D1MO7kj6RdLikXznOgkOTjPWH1EDtwRVqDy5Rf0hqNK8Ax3Jzc9e6zoD0FAqFClxnqKv8\n", - "YCCk/6y+usplFhyaZKw/pAZqD65Qe3CJ+kOyo3kFAEg2T0raIekMvy+vq+swAAAAAGKL5hUAIKnk\n", - "BwNbJc2MvGX1FQAAAJDiaF4BAJJR1a2Dw/y+vCZOkwAAAACIKZpXAICkkx8MLJO0VFK2pF87jgMA\n", - "AAAghmheAY4VFxe3r+tnXnnlleZ//OMf28YiT31NnDix9YQJE9qcd955nYqKig5znQcH53legesM\n", - "h+iRyOvvnaZAvaRA/SFJUXtwhdqDS9Qfkh3NK8Cx0tLSdnW5fv369Q0mTJiQU15e7sUqU10tWLCg\n", - "aYcOHfbceOON6x966KHiK6+8stO6desauM6Fg7rTdYBD5Fd44/bT/b68H7sOgzpL9vpD8qL24Aq1\n", - "B5eoPyQ1mleAYzk5OSV1uX7GjBnZgwcP3hQKhWIVqc4+//xz36RJk46QpNzc3PLGjRtXfvvtt41c\n", - "58JBjXMd4FDkBwPbJD0deXuFyyyol6SuPyQ1ag+uUHtwifpDUqN5BTiWm5u7trbXPvfccy3OP//8\n", - "LRkZifWlO3LkyI0zZ878TpIWL17cOCsrq7Jnz547HcfCQYRCoQLXGaLg0cjrpX5fns9pEtRJitQf\n", - "khC1B1eoPbhE/SHZJdZPwAD26+uvv87MyMhQ586d9yTSqitJysjIULt27corKip02223dSgqKlrR\n", - "sGFD17GQHt6X9LGkNpIGO84CAAAAIAZoXgFJYv78+c3XrFmTOXHixNbz589v8fHHH2fNmTOnmetc\n", - "1Y0ZM6bd7bffXtK/f/8drrMgPeQHAyH9Z/UVG7cDAAAAKYilEUCSuOqqqzZW/fO3337r8zwvNGjQ\n", - "oO0uM1U3efLkVkOGDNnUs2fPXW+99VZWVlZW5YknnrjLdS6khZmSHpA0wO/L65wfDHzjOhAAAACA\n", - "6KF5Bezl448/9t1xxx3tv/nmm8ZNmjSplKR77rln9YABA2K2mqiyslKFhYU5wWDQ++STT7LGjx+/\n", - "+uGHH25TXl7uZWdnlxcWFpZWXfvCCy+0eOONN1p4nheaNWvW9vPPP39brHLVNt+JJ55Ydssttxzt\n", - "8/kqJSkUCnnr16//MNa56qq0tLTBaaed1nXevHlfduzYcY/rPIiO/GBgk9+XZyUNkzRc0m2OIwEA\n", - "AACIIm4bBKqZM2dOsz59+uT169dv66effhpYvHjxF0888cS3I0aM6Lhu3boGsZizuLi4/f3333/4\n", - "kCFDNt9zzz3runfvXnb66ad3/cMf/lBaXFzcaNasWdnVr7/gggu2fvDBB58vXbr0i3g0riTpYPnO\n", - "P//8bWVlZcs2bdr00aZNmz7avHnzh4m059WWLVsypkyZ0uqEE07otmLFisZ79uzxXGdKBJ7nFbjO\n", - "EEVVtw5e5vflZTpNglpJsfpDEqH24Aq1B5eoPyQ7mldAxGeffdbo4osv7nzdddeVXHfddRuqjhcW\n", - "FrZduXJl47lz5zaPxbylpaXtQqGQunfvHpSk77//PrN3795b27ZtW1FYWLj2xRdfdH4LVKLnO5BB\n", - "gwYdM2DAgC5Lly7NqlpJh3+703WAKHpbUkBSW0nnOs6C2kml+kNyofbgCrUHl6g/JDWaV0DEDTfc\n", - "kNu0adPKgoKC0urH16xZ06hBgwahLl26BGMxb05OTsmYMWN+qHr//vvvN+vTp892SfrJT34S7Ny5\n", - "s/Pb21znKy4ubnj88cd3rc9n58yZs2Lx4sVfTJ06dXWbNm3KE+1JjY6Ncx0gWiIbt0+NvGXj9uSQ\n", - "MvWHpEPtwRVqDy5Rf0hqNK8ASUuXLm08f/78w84555xNGRn//WUxf/78r1etWvVRr169dsZi7tzc\n", - "3LVV/7xhw4YGX3zxRdaZZ56ZMBuxV+cqX1lZWcb69eu5FSzKQqFQgesMUVYkabeks/2+vKNch8GB\n", - "pWD9IUlQe3CF2oNL1B+SHc0rQNLTTz+dHQqF1KNHj7K9z2VmZqpt27YVsZo7GAx6L730UnNJmjdv\n", - "XrNmzZpV9OjRY5ckvfXWW1lz5sxpFqu5UyEfUCU/GFgv6QVJnqTLHccBAAAAECU0rxBXJSUlDU8/\n", - "/fQueXl53a644oojJWnjxo0ZV1xxxZE/+9nPuuTl5XX76U9/+qPly5f74pnrzTffbCFJ7du33zNz\n", - "5syWffr06XLCCSd07dmz54/9fn/L2o5zxRVXHNmmTZvjX3vttVo3dCZMmNBmyJAhXbZv3+69+uqr\n", - "LbOzs/dIUkVFhYqKilr9/Oc/d7oKK9HzAXup2rh9uN+XF5OHLAAAAACIr8R5HFiKGTjmmJNcZ4iG\n", - "1+5ZsTSa4914440dxo8fv9rzvNCpp57avVOnTrvfeOON5oWFhWt79eq1s7KyUt27d+82bNiwjkuX\n", - "Lv0imnMfyMqVK32S9M477zQNhUJatGjRV1L46YODBw/+0Zdffrn6zjvv/P5g4zz99NOHl5WVZTzz\n", - "zDPZAwcOrFVTZ8CAAduWLl268fbbb283fPjw9Y0aNaocNWpUB8/zdNNNN33foIHbn78TPR+wlzck\n", - "fSOps6SzJf3DaRoAAAAAh4zmFeJm586dXklJSeYpp5yyc+7cuU0lacKECe2WLVu2/KijjiqXpIyM\n", - "DB177LE7582bl32gsf7yl7+09vv9reuTIyMjQ48//vh3Xbt23V11bNOmTQ0lad68eS2rN80GDRq0\n", - "ffDgwRvvuuuuI3/xi19s7dmz564DjX3rrbeumT17dvbIkSN/ONB11fXs2XPXc889913V+759++5z\n", - "66JLiZ4PqC4/GKj0+/IelXSvwhu307wCAAAAkhzNqxiJ9oqlVLBw4cKmffv23SZJ7733XlNJKigo\n", - "WF3VuKpSWlraqGXLluU1jVHl+uuv33D99ddviFa2zMzMUHl5uXfWWWdt2ftc7969tz3//POtH3zw\n", - "wZynnnpq5YHGGTt27Pdjx4496Aqt6oqLi9tX37QdiBfP8wpSdPPO6ZLuknSu35fXPj8Y4OsrAaVw\n", - "/SHBUXtwhdqDS9Qfkh3NK8TNwIEDt1fdSrdw4cLmDRs2DF166aWbql9TVlbmffLJJ1l9+/bdp4kU\n", - "Sy1btizfuXNno+OPP36fJwq2a9euXJLefvvt5rGYu7S0tB3NK2nixImtn3zyyRpX0wWDwYwNGzZk\n", - "nnrqqT+q6bzneZo0adKqk08++YAr47CPOyUVuA4RbfnBQKnfl/eSpAskXSbpbseRULOUrD8kBWoP\n", - "rlB7cIn6Q1KjeYW4q6ys1OLFi5t37969rHnz5pXVz82aNatFMBjMGDRoUFybV9nZ2eXr1q1r1KxZ\n", - "s8q9zzVp0qRSCq8Ii8XcOTk5JbEYV5IGDx7cad26dZl1/dxFF1208YYbblgfi0z7M3r06A2jR4+u\n", - "cTXdl19+2ejMM8/88XvvvfdlPDOlgXGuA8TQIwo3r67w+/LuyQ8G9vnahnOpXH9IbNQeXKH24BL1\n", - "h6RG8wpx969//avJ1q1bG/Tu3Xvb3udeeOGF7MzMzNBvfvObzfHM9OMf/3hnIBDI2rZt2z5P4AyF\n", - "QpKkZs2aVcRi7liuupo9e/a3sRo7nqr+P0B0pfjS8XmSVkrqKGmApLlO02AfKV5/SGDUHlyh9uAS\n", - "9YdkR/MKcTd//vzmktSvX7//al5t377de+211w4744wztrRp06airKzMGzp0aMeXX355nwbMgw8+\n", - "2Obpp59uVZ/5MzIyNG3atO+6dev27w3b+/fvv3XWrFmt165du8+7DkzuAAAVZUlEQVQqpS1btjSQ\n", - "pG7dusVko/KMjIyoPZnS8zxVVFREdb+1RM8H1CSycfs0SYUKb9xO8woAAABIUjSvEHdvvvlm84YN\n", - "G4bOOuus7dWPz507t/mOHTsaXHzxxRsk6bHHHms1cODArTWNccMNN6yP5m1t+fn5m//4xz9WfPjh\n", - "h1l7n1uyZEmWJP32t7896Hzjxo074uWXX85+5JFHVp544om12n+psrIyoZs5iZ6vrjzPcx0B8fO4\n", - "wns7DPb78o7IDwbq9DAFAAAAAIlhn1ukgFiqqKjQkiVLatzvqqqpcNxxx+36+uuvM5999tnsq6++\n", - "OmpPFDyQVq1aVV577bXrXnnllVbr1q1rUHV8165d3ssvv5zds2fPbSNGjNh0oDEk6b777uvwwQcf\n", - "NJs8efLhdc2wbdu2jJtvvrndZZddlturV68fnX322cd89tln+91n69e//nXHefPmNa3rPNFS17yu\n", - "rVq1qmFpaWlmKBTS22+/nVVZyRZIqS4/GFgt6R+SMiVd4jgOAAAAgHqieYW42rFjR0bjxo0rhw8f\n", - "/sPe5371q19tveiii364+OKLj/nd737X6aGHHirOyIhfid59993rzjnnnI39+vX70aOPPpo9Y8aM\n", - "w84888wuHTt2DM6dO/fr2owxZMiQ9S1btiy/4IILDtro2tutt97abtSoUesff/zx4nfffffLpk2b\n", - "Vp5xxhldS0pK9lkh+cILL7R48cUXW5eXlztbRlSXvC6dd955nVq3bn18x44dj1+9erXP8zxdfvnl\n", - "nZs1a9ajQ4cO/7Ny5co6b2iPpPJo5PX3fl8ey+4AAACAJJRQP2Qi9bVo0aKytLT04/2df+qpp1bF\n", - "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\n", - "Z4QAAAAASUVORK5CYII=\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 381, - "width": 599 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "plot_taylor_approximations(cos, 0, [2, 4, 6], (0, 2*pi), (-2,2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This shows easily how a Taylor series is useless beyond its convergence radius, illustrated by \n", - "a simple function that has singularities on the real axis:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "image/png": [ - "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\n", - "bp8yZcoJ12PKysqk7777rk3//v3PiknpcOrUKY3BYLgwMzPz5IYNG34OCwsTGYeo1QKp+cRbYFMw\n", - "mis6gAfSAbguoFhalJ3p029tWsAb7we1o55eMuVZFC+8XqAJ5trzCqOsjQCwBsA1AKxwNJ72i00V\n", - "MkK+/kgY1p46BcrvARYArqNcknX5he1ctll/KmOz2VBUVBTbt2/fstjYWJvrY+vWrYtTFEUzevTo\n", - "U6LyVVdXY8yYMRm9e/cOf//99y0RERGiohB5zK/T7hpSUFCwDwBbuBSM5okO4AHV3ulOn5ORAuAm\n", - "ADUAlgqO4yvBXHseM8raMAArAdwA4ASAawyKebfYVCElpOuPhGLtkc8UZWdW6fIL9+D86Xa98fvN\n", - "kVh/KvPll19Gnz59Omzo0KFn3B9766234iMiIuy33nrrSRHZAODOO+/smpycXLVgwYISjeb3cSPb\n", - "tm2LHjRoULmoXEStERDNJ6JgZbfbc0Vn8IBq13sCMB2O97e1pjzLQdFhfCHIa88jRlkrwdFUNAA4\n", - "C2CUQTF/JzZVaAnl+iOxWHvkBz/i/GskLZzNJ9af+mzcuDEWADIzM89rPp09e1YymUzthw8ffiox\n", - "MbGmrKxMmjBhQvr69et/cX+N5557LnH16tUdWnN+jUaDl19+eV+fPn3qLOT9t7/9rdO5c+c0//nP\n", - "f/a57rfZbMjJyUnZtGnTz605J5EobD4RhS5VjnzS52REwtF8AoBFIrOQ9zkbT8/CcTfEcgB/Mijm\n", - "bWJTERGRipgBjHXZdr9eIhX59NNPY8PDw+3XXHPNees6ffjhh7Hnzp0Lmzhx4jEAeOWVVzro9frT\n", - "9b3GAw88UPrAAw949UYnb7zxRrutW7fGrF279peqqqrzHvv444/bdu7cuaqBp/5m3rx5ndavXx//\n", - "0ksv7b/00ktbfbc7AJCkQF2mjYIJm09Eocv9YkotI59uBpAE4DsAmwVnIS9yNp6eAjALQBWAcQbF\n", - "zP+NiYjIm9yvh9h8Uqmamhps37693vWeapstF110UcXevXsj3nzzzfiPP/54rz9yffHFF9HTpk3L\n", - "qKio0LRp0ya+vmPmz5/f5B3vnn766dSKigrN4sWLO7788svNukOeuwMHDoRbrdYIu92Ozz//vE33\n", - "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\n", - "jRu3HOBvf/tbv8rKytVbOsdVV1217KqrrlqWrpgCgcC3v/jFL1ree++98tRjb731VjnAmWeeud3r\n", - "3XzzzQOfe+65vn/5y1++OOCAAwqyh1BXZRj5+U+oZ/sPEREXnAQMS7pfj1McSavKqorjgOuAFmBc\n", - "bU1dVqcVb0NWPoCFvL6SkNf3e+AZnMLTv4H9VXgSERHJqq5UePkLzueuViOBI12KpUtraWnhrbfe\n", - "2mK/p9Zf7r/73e+u/+STT0r++c9/9r3kkkvStqPdtvTr1y92+eWXL3n++ef7LVmypKh1fP369cZz\n", - "zz3X1zTNNRdccMF2e8Deeuutw955550e99577w6djWnNmjWea6+9dsg555wz/JBDDtn9hz/8YcW8\n", - "efO22nfqlFNOGTF9+vTunb1uprX3dbntyy+/LF66dGlJPB7n1VdfLY/F8qtFr4pPIrnpkpT7fw8H\n", - "/WltdlhZVTEQmJS4W11bU/daOs+f6xLL7F4Crsb5EBgEfhKIRrLywUJERES6nnDQvxDnS69kqZ/7\n", - "JAvWrVvnKSsri5133nnfpB47+eSTV59++unfjBs3ruJnP/vZzvfdd98Cjyd7vzr/5je/WXLCCSes\n", - "8Pv9uz/wwAN9H3nkkT7HHHPMbiNGjIhOmzbtk7acY8yYMct69+7dPHr06E5vVnTdddcNufTSS5c9\n", - "9NBDC15//fX53bt3jx199NF7Ll68eLOVVE899VSvp59+un9zc3POT89pz+ty04knnrhz//799x0x\n", - "YsS+Cxcu9BqGwbnnnrtLjx499h82bNjeX3zxRcn2z+K+nPpDFREwLdsH+FOG703nNSqrKjw4u7kN\n", - "BmYBNek8f64LeX1jgAeA3sBCnGV2Xar4JiIiIq65FxiddH+0adlDwkH/YrcC6op69eoVW7p06dyt\n", - "HX/88ce/zGY8qR577LEvX3vttW6zZs3qEYvFjN/97ncLjzrqqDb3enrkkUcW4LST6JT6+nrj4Ycf\n", - "HlheXh77/e9/vxjgpptuWrz//vv3v//++/tPnDhxaetj16xZ47Ftu2dnr5kN7XldbttSr7F8pJlP\n", - "Ip1gGEZ1Bk6b+u3XK+Ggf6tvjB10OXA8sAI4s7amLiM7ZuSakNfXPeT1/RWnsXpvnN3s9s/HwlOG\n", - "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\n", - "l7GPAH/JzHs3OzcJmMZ/CwHSRPs1ZUfPzX/fTQEuAhYAvwTW9zeWuiAiXgPstnlfxYh4NaUfz/VV\n", - "gqkLNlC+X0czDXiJMilDasvBwBuBH0zUBQeu+NS4ALgqIvbLzDW9wYh4K2Xq/9erJZOkFkTEDGAl\n", - "pbfEp8foQSG1YVvgMuD+psHpyDX/R1N2ob2uSjINvcz8J/DzzccjYmrzx3sz86a+hlKX3ApsHxHT\n", - "M/OZEeNfozyULa8TSx1wJTAnIhZm5h97gxHxOkrT8dWZaY9jtWkuZVLPuom64EAWnzLz6ohYCayM\n", - "iDMob8WmAScCdwNLa+aTpBbcQNlI4RxgVsTLV5i4447akJlPRcQc4FLg5qbZ6TOU2VBfAlZk5tqa\n", - "GSWpJd8FrgZuj4jFlN6zhwJHAqdk5j01w2moLQQOBO6MiDOB+4HdgeMpL4XmVsymIRcRbwc+QCl0\n", - "TphB7PnUczhwFvBFymyAE4CfAAc4I0B9tomXLweQJtoOlGUmq4C1o3zWjP1PpfFpdhn7EKXn4rmU\n", - "nmPvA+Zlpr3HVIvfvWpVZq4C9gUeo7z8uQLYEzgkMxdWjKYhl5lPA7OAZZQXPdcAp1Bm4+2dmQ9X\n", - "jKfhNwfYCKyYyItO2rTJ721JkiRJkiS1Y5BnPkmSJEmSJOn/nMUnSZIkSZIktcbikyRJkiRJklpj\n", - "8UmSJEmSJEmtsfgkSZIkSZKk1lh8kiRJkiRJUmssPkmSJEmSJKk1Fp8kSZIkSZLUGotPkiRJkiRJ\n", - "ao3FJ0mSJEmSJLXG4pMkSZIkSZJaY/FJkiRJkiRJrbH4JEmSJEmSpNZYfJIkSZIkSVJrLD5JkiRJ\n", - "kiSpNRafJEmSJEmS1BqLT5IkSZIkSWqNxSdJkiRJkiS1xuKTJEmSJEmSWvMfdhLgUx9xCjkAAAAA\n", - "SUVORK5CYII=\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 376, - "width": 591 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "# For an expression made from elementary functions, we must first make it into\n", - "# a callable function, the simplest way is to use the Python lambda construct.\n", - "plot_taylor_approximations(lambda x: 1/cos(x), 0, [2,4,6], (0, 2*pi), (-5,5))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/IPython Kernel/Terminal Usage.ipynb b/examples/IPython Kernel/Terminal Usage.ipynb deleted file mode 100644 index e6bd4c0f116..00000000000 --- a/examples/IPython Kernel/Terminal Usage.ipynb +++ /dev/null @@ -1,285 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# A few things that work best/only at the IPython terminal or Qt console clients" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Running code with `%run`" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing script.py\n" - ] - } - ], - "source": [ - "%%writefile script.py\n", - "x = 10\n", - "y = 20\n", - "z = x+y\n", - "print('z is: %s' % z)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "z is: 30\n" - ] - } - ], - "source": [ - "%run script" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "10" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Event loop and GUI integration" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `%gui` magic enables the integration of GUI event loops with the interactive execution loop, allowing you to run GUI code without blocking IPython.\n", - "\n", - "Consider for example the execution of Qt-based code. Once we enable the Qt gui support:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%gui qt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can define a simple Qt application class (simplified version from [this Qt tutorial](http://zetcode.com/tutorials/pyqt4/firstprograms)):" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import sys\n", - "from PyQt4 import QtGui, QtCore\n", - "\n", - "class SimpleWindow(QtGui.QWidget):\n", - " def __init__(self, parent=None):\n", - " QtGui.QWidget.__init__(self, parent)\n", - "\n", - " self.setGeometry(300, 300, 200, 80)\n", - " self.setWindowTitle('Hello World')\n", - "\n", - " quit = QtGui.QPushButton('Close', self)\n", - " quit.setGeometry(10, 10, 60, 35)\n", - "\n", - " self.connect(quit, QtCore.SIGNAL('clicked()'),\n", - " self, QtCore.SLOT('close()'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And now we can instantiate it:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "app = QtCore.QCoreApplication.instance()\n", - "if app is None:\n", - " app = QtGui.QApplication([])\n", - "\n", - "sw = SimpleWindow()\n", - "sw.show()\n", - "\n", - "from IPython.lib.guisupport import start_event_loop_qt4\n", - "start_event_loop_qt4(app)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But IPython still remains responsive:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "12" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "10+2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `%gui` magic can be similarly used to control Wx, Tk, glut and pyglet applications, [as can be seen in our examples](https://github.com/ipython/ipython/tree/master/examples/lib)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Embedding IPython in a terminal application" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing simple-embed.py\n" - ] - } - ], - "source": [ - "%%writefile simple-embed.py\n", - "# This shows how to use the new top-level embed function. It is a simpler\n", - "# API that manages the creation of the embedded shell.\n", - "\n", - "from IPython import embed\n", - "\n", - "a = 10\n", - "b = 20\n", - "\n", - "embed(header='First time', banner1='')\n", - "\n", - "c = 30\n", - "d = 40\n", - "\n", - "embed(header='The second time')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The example in kernel-embedding shows how to embed a full kernel into an application and how to connect to this kernel from an external process." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Logging terminal sessions and transitioning to a notebook" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `%logstart` magic lets you log a terminal session with various degrees of control, and the `%notebook` one will convert an interactive console session into a notebook with all input cells already created for you (but no output)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/IPython Kernel/Third Party Rich Output.ipynb b/examples/IPython Kernel/Third Party Rich Output.ipynb deleted file mode 100644 index e0403107c1e..00000000000 --- a/examples/IPython Kernel/Third Party Rich Output.ipynb +++ /dev/null @@ -1,540 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Third Party Libraries With Rich Output" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A number of third party libraries defined their own custom display logic. This gives their objcts rich output by default when used in the Notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from IPython.display import display" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Pandas" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Pandas](http://pandas.pydata.org/) is a data analysis library for Python. Its `DataFrame` objects have an HTML table representation in the Notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import pandas" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is a small amount of stock data for APPL:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing data.csv\n" - ] - } - ], - "source": [ - "%%writefile data.csv\n", - "Date,Open,High,Low,Close,Volume,Adj Close\n", - "2012-06-01,569.16,590.00,548.50,584.00,14077000,581.50\n", - "2012-05-01,584.90,596.76,522.18,577.73,18827900,575.26\n", - "2012-04-02,601.83,644.00,555.00,583.98,28759100,581.48\n", - "2012-03-01,548.17,621.45,516.22,599.55,26486000,596.99\n", - "2012-02-01,458.41,547.61,453.98,542.44,22001000,540.12\n", - "2012-01-03,409.40,458.24,409.00,456.48,12949100,454.53" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Read this as into a `DataFrame`:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "df = pandas.read_csv('data.csv')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And view the HTML representation:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DateOpenHighLowCloseVolumeAdj Close
0 2012-06-01 569.16 590.00 548.50 584.00 14077000 581.50
1 2012-05-01 584.90 596.76 522.18 577.73 18827900 575.26
2 2012-04-02 601.83 644.00 555.00 583.98 28759100 581.48
3 2012-03-01 548.17 621.45 516.22 599.55 26486000 596.99
4 2012-02-01 458.41 547.61 453.98 542.44 22001000 540.12
5 2012-01-03 409.40 458.24 409.00 456.48 12949100 454.53
\n", - "
" - ], - "text/plain": [ - " Date Open High Low Close Volume Adj Close\n", - "0 2012-06-01 569.16 590.00 548.50 584.00 14077000 581.50\n", - "1 2012-05-01 584.90 596.76 522.18 577.73 18827900 575.26\n", - "2 2012-04-02 601.83 644.00 555.00 583.98 28759100 581.48\n", - "3 2012-03-01 548.17 621.45 516.22 599.55 26486000 596.99\n", - "4 2012-02-01 458.41 547.61 453.98 542.44 22001000 540.12\n", - "5 2012-01-03 409.40 458.24 409.00 456.48 12949100 454.53" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## SymPy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[SymPy](http://sympy.org/) is a symbolic computing library for Python. Its equation objects have LaTeX representations that are rendered in the Notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from sympy.interactive.printing import init_printing\n", - "init_printing(use_latex='mathjax')" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from __future__ import division\n", - "import sympy as sym\n", - "from sympy import *\n", - "x, y, z = symbols(\"x y z\")\n", - "k, m, n = symbols(\"k m n\", integer=True)\n", - "f, g, h = map(Function, 'fgh')" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$\\frac{3 \\pi}{2} + \\frac{e^{i x}}{x^{2} + y}$$" - ], - "text/plain": [ - " ⅈ⋅x \n", - "3⋅π ℯ \n", - "─── + ──────\n", - " 2 2 \n", - " x + y" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Rational(3,2)*pi + exp(I*x) / (x**2 + y)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$\\frac{1}{x} \\left(x \\sin{\\left (x \\right )} - 1\\right) + \\frac{1}{x}$$" - ], - "text/plain": [ - "x⋅sin(x) - 1 1\n", - "──────────── + ─\n", - " x x" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "a = 1/x + (x*sin(x) - 1)/x\n", - "a" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/latex": [ - "$$1 + \\frac{x^{2}}{2} + \\frac{5 x^{4}}{24} + \\mathcal{O}\\left(x^{6}\\right)$$" - ], - "text/plain": [ - " 2 4 \n", - " x 5⋅x ⎛ 6⎞\n", - "1 + ── + ──── + O⎝x ⎠\n", - " 2 24 " - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(1/cos(x)).series(x, 0, 6)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Vincent" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Vincent](https://vincent.readthedocs.io/en/latest/) is a visualization library that uses the [Vega](http://trifacta.github.io/vega/) visualization grammar to build [d3.js](http://d3js.org/) based visualizations in the Notebook and on http://nbviewer.ipython.org. `Visualization` objects in Vincetn have rich HTML and JavaSrcript representations." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import vincent\n", - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import pandas.io.data as web\n", - "import datetime\n", - "all_data = {}\n", - "date_start = datetime.datetime(2010, 1, 1)\n", - "date_end = datetime.datetime(2014, 1, 1)\n", - "for ticker in ['AAPL', 'IBM', 'YHOO', 'MSFT']:\n", - " all_data[ticker] = web.DataReader(ticker, 'yahoo', date_start, date_end)\n", - "price = pd.DataFrame({tic: data['Adj Close']\n", - " for tic, data in all_data.items()})" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "vincent.initialize_notebook()" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "line = vincent.Line(price[['AAPL', 'IBM', 'YHOO', 'MSFT']], width=600, height=300)\n", - "line.axis_titles(x='Date', y='Price')\n", - "line.legend(title='Ticker')\n", - "display(line)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/IPython Kernel/Trapezoid Rule.ipynb b/examples/IPython Kernel/Trapezoid Rule.ipynb deleted file mode 100644 index 5ccd13f9164..00000000000 --- a/examples/IPython Kernel/Trapezoid Rule.ipynb +++ /dev/null @@ -1,971 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Basic Numerical Integration: the Trapezoid Rule" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A simple illustration of the trapezoid rule for definite integration:\n", - "\n", - "$$\n", - "\\int_{a}^{b} f(x)\\, dx \\approx \\frac{1}{2} \\sum_{k=1}^{N} \\left( x_{k} - x_{k-1} \\right) \\left( f(x_{k}) + f(x_{k-1}) \\right).\n", - "$$\n", - "
\n", - "First, we define a simple function and sample it between 0 and 10 at 200 points" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def f(x):\n", - " return (x-3)*(x-5)*(x-7)+85\n", - "\n", - "x = np.linspace(0, 10, 200)\n", - "y = f(x)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Choose a region to integrate over and take only a few points in that region" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "a, b = 1, 8 # the left and right boundaries\n", - "N = 5 # the number of points\n", - "xint = np.linspace(a, b, N)\n", - "yint = f(xint)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plot both the function and the area below it in the trapezoid approximation" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "image/png": [ - "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\n", - "gBiqrKzc7ZK+lJSUxuLi4k228kRTZWXlrrsQDhw4cK+XtIXr169fuSQ1NDQM3Ne+If/+979nNjc3\n", - "50tSU1NTYUNDQ5oUuLRw9erVJ4b2y8jIaOjo9c8999x0v9+fPHPmzA/395yVlZUDJSk1NbVhzJgx\n", - "Hc6KcrlcK0KXKe7YsSO/o33q6urSn3322e+EFlfPycnZtTZXU1NTSmVl5RGTJk36zOZ7BQAA8UVz\n", - "Cla5PN4CST+MKN/jK3FvtJEHABBbW7du3a051bt379WWokRd+ELvY8eO3e+ZU2PGjFkpSS0tLbkV\n", - "FRW99+c1O3fu7BN6XlRU9F7fvn2bJOnZZ5890+/3Jw0YMGCBJNXU1AyLfO2cOXOOXLFixVkzZsx4\n", - "OCMjo21/c1ZVVQ2XpNzc3FWd7ZOZmdk2fvz41ySpurr68MbGxt1m6VdVVWX/9a9//X5WVlZN7969\n", - "y6TA4vGh7e+8886U9PT02qOOOmqNzfcKAADii8v6YNsPJfUJG2+V9HtLWQAAMVZXV7fbZX2FhYVl\n", - "trJEW319/VBJysjIqNnbXeoiTZo0af3LL7+8o62tLXvp0qXDBw0a9OW+XjN27NiPt2zZMrmgoGDh\n", - "6aef/sb69etz33vvvePLy8tP+/rXv35PYWHh9r///e/DqqqqjvznP/95ytSpUz+rqqrK/eSTT06s\n", - "qKg4Zvr06fdPmTJlvxtoklRbWztMkgYOHLhsb/tdeOGF3qqqqtGVlZVHP/bYY1eeddZZL6SkpLR/\n", - "9tlnhy9evPic/v37L7z66quf/vDDD8d7vd6bt2/fPuqrr74aWF9fn/3pp59efNRRRz1n+70CAID4\n", - "ojkFa1we72BJN0WU7/SVuLfZyAMAiK3W1takHTt2DAqvFRUVJURzasWKFf3a2toyJalv374rD+S1\n", - "KSkp/gEDBny+cePGE8rKyg477bTT9tmcmjFjxqfp6enNpaWl0x5//PGfJycntxYUFHx1+eWX3zli\n", - "xIgaSbrmmmt+/+qrr565fPnyaYsWLbo4PT19+4ABA7645pprbh80aFDdgWSsqKjo3dDQMFSS/9hj\n", - "j/10b/smJyfr+uuvf+ill15aunLlypNmz559e3Jycktubu7KU0899W/HH3/8Ckk66aSTliYlJd27\n", - "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\n", - "AAAAAACw5v8DPWODrRZIv60AAAAASUVORK5CYII=\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 381, - "width": 595 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.plot(x, y, lw=2)\n", - "plt.axis([0, 9, 0, 140])\n", - "plt.fill_between(xint, 0, yint, facecolor='gray', alpha=0.4)\n", - "plt.text(0.5 * (a + b), 30,r\"$\\int_a^b f(x)dx$\", horizontalalignment='center', fontsize=20);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Compute the integral both at high accuracy and with the trapezoid approximation" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The integral is: 565.2499999999999 +/- 6.275535646693696e-12\n", - "The trapezoid approximation with 5 points is: 559.890625\n" - ] - } - ], - "source": [ - "from __future__ import print_function\n", - "from scipy.integrate import quad\n", - "integral, error = quad(f, a, b)\n", - "integral_trapezoid = sum( (xint[1:] - xint[:-1]) * (yint[1:] + yint[:-1]) ) / 2\n", - "print(\"The integral is:\", integral, \"+/-\", error)\n", - "print(\"The trapezoid approximation with\", len(xint), \"points is:\", integral_trapezoid)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/IPython Kernel/Updating Displays.ipynb b/examples/IPython Kernel/Updating Displays.ipynb deleted file mode 100644 index 33b86670171..00000000000 --- a/examples/IPython Kernel/Updating Displays.ipynb +++ /dev/null @@ -1,337 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Updatable Displays\n", - "\n", - "Note: This feature requires notebook >= 5.0 or JupyterLab, and \n", - "\n", - "\n", - "IPython 6 implements a new API as part of the Jupyter Protocol version 5.1 for easily updating displays.\n", - "\n", - "When you display something, you can now pass a `display_id` argument to attach an id to that output.\n", - "\n", - "Any future display with the same ID will also update other displays that had the same ID.\n", - "\n", - "`display` with a `display_id` will return a `DisplayHandle`\n", - "object, which gives you easy access to update the output:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import display, update_display" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'z'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "handle = display('x', display_id='update-me')\n", - "handle" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we call `handle.display('y')`, we get a new display of 'y',\n", - "but in addition to that, we updated the previous display." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'z'" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "handle.display('y')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also *just* update the existing displays,\n", - "without creating a new display:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "handle.update('z')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You don't have to generate display_ids yourself,\n", - "if you specify `display_id=True`, then a unique ID will be assigned:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'hello'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "handle = display(\"hello\", display_id=True)\n", - "handle" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Calling `handle.display(obj)` is the same as calling `display(obj, handle.display_id)`,\n", - "so you don't need to use the handle objects if you don't want to:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'z'" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "display('x', display_id='here');" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'z'" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "display('y', display_id='here');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And just like `display`, there is now `update_display`,\n", - "which is what `DisplayHandle.update` calls:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "update_display('z', display_id='here')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## More detailed example\n", - "\n", - "One of the motivating use cases for this is simple progress bars.\n", - "\n", - "Here is an example ProgressBar using these APIs:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "[============================================================] 10/10" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import ProgressBar\n", - "\n", - "bar = ProgressBar(10)\n", - "bar.display()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And the ProgressBar has `.display` and `.update` methods:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "[============================================================] 10/10" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import time\n", - "\n", - "bar.display()\n", - "\n", - "for i in range(11):\n", - " bar.progress = i\n", - " bar.update()\n", - " time.sleep(0.25)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The ProgressBar also has an update built into iteration:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "[============================================================] 10/10" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "for i in ProgressBar(10):\n", - " time.sleep(0.25)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We would encourage any updatable-display objects that track their own display_ids to follow-suit with `.display()` and `.update()` or `.update_display()` methods." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.2" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/examples/IPython Kernel/Working With External Code.ipynb b/examples/IPython Kernel/Working With External Code.ipynb deleted file mode 100644 index c5e5c840a78..00000000000 --- a/examples/IPython Kernel/Working With External Code.ipynb +++ /dev/null @@ -1,691 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Working With External Code" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The IPython Kernel makes it easy to incorporate external code from sources such as the internet or copy/paste. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Pasting code into cells" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can copy and paste code from other sources directly into cells. Pasting code with `>>>` prompts works as expected:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Be careful not to fall off!\n" - ] - } - ], - "source": [ - ">>> the_world_is_flat = 1\n", - ">>> if the_world_is_flat:\n", - "... print(\"Be careful not to fall off!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The %load magic" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `%load` magic lets you load code from URLs or local files:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%load?" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "%load http://matplotlib.org/mpl_examples/showcase/integral_demo.py" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "image/png": [ - "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\n", - "WZKkfJXcNH3XXXdNKUnjWrVqFe+99x4AW2+9ddX+TWvTokULunbtCrDGO+atj8rSZm37M82fP5+J\n", - "EyfW+z2XL19edbZX9TKquuqfNX/+/DVeFtjYmuL7SpKUbyyp1DhC2A24ODH9OzAphTSSJDWZ//3v\n", - "f7U23S6UTa7fe++9qg3hG7If1ZZbbgnAJ5980qDPW7p0Ka+//jpLliyplaNyb6iBAweucX+mBx98\n", - "kEcffbTen1eZL4RAr1696nzO3nvvzSabbAJk9q1KFpIbItvfV5KkfGNJpQ0XQlvgNmreLfILYHSj\n", - "7qAqSVIOePfdd2schxAKpqSaMWNG1eOGlFRbb701AB9//HG9X/Pf//6XoUOH8s1vfpMDDjiAVatW\n", - "Va09/vjjVY933HHHOl9fXl7O5MmTOeaYY+r9mdVLtMrMSS1btuS0004DMpusP/zww+t835UrV9a4\n", - "y2Fd0vi+kiTlG0sqNYYrgeSmFScT45w0wkiS1JT+9a9/1Tju0KED2223XUppGlf1kqo+m6ZXqryz\n", - "4X/+8596v+baa6/lf//7HwCzZ8+u2v9p1apV3H777VXP69KlS52vv/766ykvL29QafP+++8D0Llz\n", - "56qzv+pyyimnsPvuuwNw00038cUXX6z1fa+//nrmzZtXdbx48eJaz0nj+0qSlG8sqbRhQjgE+FFi\n", - "+idivC+NOJIkNbVkSVVZ0BSCypIqhNCgkmqPPfYA4NNPP11noVPps88+q3p8/PHH0759ewAmTZpE\n", - "RUUFRx11VI1M1T300ENcd911TJo0qUF34HvrrbcAqgqoNSkqKmLSpElst912zJs3j1NOOYVFixbV\n", - "+dzbbruNyZMn06FDByBz9tWdd95Z62TyNL6vJEn5xpJK6y+E7sDUxPR9YEwKaSRJyopkSVUom6bD\n", - "6rsW9ujRg0033bTer9thhx3o3LkzMUbefPPNer2mspQ54IADOP300/n000+ZOHEiU6ZM4cYbb2T8\n", - "+PH07t2bhx56iJtvvpnPP/+cmTNncv7553P22WczderUBhVpsLqk2meffdb53B49enD//fczbNgw\n", - "pk2bxkEHHcQdd9zBrFmzmD17No8++ijHHnssU6ZM4e6776Zjx9U3N77hhhvo378/gwcPrtprKo3v\n", - "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\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 403, - "width": 596 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "# %load http://matplotlib.org/mpl_examples/showcase/integral_demo.py\n", - "\"\"\"\n", - "Plot demonstrating the integral as the area under a curve.\n", - "\n", - "Although this is a simple example, it demonstrates some important tweaks:\n", - "\n", - " * A simple line plot with custom color and line width.\n", - " * A shaded region created using a Polygon patch.\n", - " * A text label with mathtext rendering.\n", - " * figtext calls to label the x- and y-axes.\n", - " * Use of axis spines to hide the top and right spines.\n", - " * Custom tick placement and labels.\n", - "\"\"\"\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from matplotlib.patches import Polygon\n", - "\n", - "\n", - "def func(x):\n", - " return (x - 3) * (x - 5) * (x - 7) + 85\n", - "\n", - "\n", - "a, b = 2, 9 # integral limits\n", - "x = np.linspace(0, 10)\n", - "y = func(x)\n", - "\n", - "fig, ax = plt.subplots()\n", - "plt.plot(x, y, 'r', linewidth=2)\n", - "plt.ylim(ymin=0)\n", - "\n", - "# Make the shaded region\n", - "ix = np.linspace(a, b)\n", - "iy = func(ix)\n", - "verts = [(a, 0)] + list(zip(ix, iy)) + [(b, 0)]\n", - "poly = Polygon(verts, facecolor='0.9', edgecolor='0.5')\n", - "ax.add_patch(poly)\n", - "\n", - "plt.text(0.5 * (a + b), 30, r\"$\\int_a^b f(x)\\mathrm{d}x$\",\n", - " horizontalalignment='center', fontsize=20)\n", - "\n", - "plt.figtext(0.9, 0.05, '$x$')\n", - "plt.figtext(0.1, 0.9, '$y$')\n", - "\n", - "ax.spines['right'].set_visible(False)\n", - "ax.spines['top'].set_visible(False)\n", - "ax.xaxis.set_ticks_position('bottom')\n", - "\n", - "ax.set_xticks((a, b))\n", - "ax.set_xticklabels(('$a$', '$b$'))\n", - "ax.set_yticks([])\n", - "\n", - "plt.show()\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/IPython Kernel/data/flare.json b/examples/IPython Kernel/data/flare.json deleted file mode 100644 index b976cd1304b..00000000000 --- a/examples/IPython Kernel/data/flare.json +++ /dev/null @@ -1,380 +0,0 @@ -{ - "name": "flare", - "children": [ - { - "name": "analytics", - "children": [ - { - "name": "cluster", - "children": [ - {"name": "AgglomerativeCluster", "size": 3938}, - {"name": "CommunityStructure", "size": 3812}, - {"name": "HierarchicalCluster", "size": 6714}, - {"name": "MergeEdge", "size": 743} - ] - }, - { - "name": "graph", - "children": [ - {"name": "BetweennessCentrality", "size": 3534}, - {"name": "LinkDistance", "size": 5731}, - {"name": "MaxFlowMinCut", "size": 7840}, - {"name": "ShortestPaths", "size": 5914}, - {"name": "SpanningTree", "size": 3416} - ] - }, - { - "name": "optimization", - "children": [ - {"name": "AspectRatioBanker", "size": 7074} - ] - } - ] - }, - { - "name": "animate", - "children": [ - {"name": "Easing", "size": 17010}, - {"name": "FunctionSequence", "size": 5842}, - { - "name": "interpolate", - "children": [ - {"name": "ArrayInterpolator", "size": 1983}, - {"name": "ColorInterpolator", "size": 2047}, - {"name": "DateInterpolator", "size": 1375}, - {"name": "Interpolator", "size": 8746}, - {"name": "MatrixInterpolator", "size": 2202}, - {"name": "NumberInterpolator", "size": 1382}, - {"name": "ObjectInterpolator", "size": 1629}, - {"name": "PointInterpolator", "size": 1675}, - {"name": "RectangleInterpolator", "size": 2042} - ] - }, - {"name": "ISchedulable", "size": 1041}, - {"name": "Parallel", "size": 5176}, - {"name": "Pause", "size": 449}, - {"name": "Scheduler", "size": 5593}, - {"name": "Sequence", "size": 5534}, - {"name": "Transition", "size": 9201}, - {"name": "Transitioner", "size": 19975}, - {"name": "TransitionEvent", "size": 1116}, - {"name": "Tween", "size": 6006} - ] - }, - { - "name": "data", - "children": [ - { - "name": "converters", - "children": [ - {"name": "Converters", "size": 721}, - {"name": "DelimitedTextConverter", "size": 4294}, - {"name": "GraphMLConverter", "size": 9800}, - {"name": "IDataConverter", "size": 1314}, - {"name": "JSONConverter", "size": 2220} - ] - }, - {"name": "DataField", "size": 1759}, - {"name": "DataSchema", "size": 2165}, - {"name": "DataSet", "size": 586}, - {"name": "DataSource", "size": 3331}, - {"name": "DataTable", "size": 772}, - {"name": "DataUtil", "size": 3322} - ] - }, - { - "name": "display", - "children": [ - {"name": "DirtySprite", "size": 8833}, - {"name": "LineSprite", "size": 1732}, - {"name": "RectSprite", "size": 3623}, - {"name": "TextSprite", "size": 10066} - ] - }, - { - "name": "flex", - "children": [ - {"name": "FlareVis", "size": 4116} - ] - }, - { - "name": "physics", - "children": [ - {"name": "DragForce", "size": 1082}, - {"name": "GravityForce", "size": 1336}, - {"name": "IForce", "size": 319}, - {"name": "NBodyForce", "size": 10498}, - {"name": "Particle", "size": 2822}, - {"name": "Simulation", "size": 9983}, - {"name": "Spring", "size": 2213}, - {"name": "SpringForce", "size": 1681} - ] - }, - { - "name": "query", - "children": [ - {"name": "AggregateExpression", "size": 1616}, - {"name": "And", "size": 1027}, - {"name": "Arithmetic", "size": 3891}, - {"name": "Average", "size": 891}, - {"name": "BinaryExpression", "size": 2893}, - {"name": "Comparison", "size": 5103}, - {"name": "CompositeExpression", "size": 3677}, - {"name": "Count", "size": 781}, - {"name": "DateUtil", "size": 4141}, - {"name": "Distinct", "size": 933}, - {"name": "Expression", "size": 5130}, - {"name": "ExpressionIterator", "size": 3617}, - {"name": "Fn", "size": 3240}, - {"name": "If", "size": 2732}, - {"name": "IsA", "size": 2039}, - {"name": "Literal", "size": 1214}, - {"name": "Match", "size": 3748}, - {"name": "Maximum", "size": 843}, - { - "name": "methods", - "children": [ - {"name": "add", "size": 593}, - {"name": "and", "size": 330}, - {"name": "average", "size": 287}, - {"name": "count", "size": 277}, - {"name": "distinct", "size": 292}, - {"name": "div", "size": 595}, - {"name": "eq", "size": 594}, - {"name": "fn", "size": 460}, - {"name": "gt", "size": 603}, - {"name": "gte", "size": 625}, - {"name": "iff", "size": 748}, - {"name": "isa", "size": 461}, - {"name": "lt", "size": 597}, - {"name": "lte", "size": 619}, - {"name": "max", "size": 283}, - {"name": "min", "size": 283}, - {"name": "mod", "size": 591}, - {"name": "mul", "size": 603}, - {"name": "neq", "size": 599}, - {"name": "not", "size": 386}, - {"name": "or", "size": 323}, - {"name": "orderby", "size": 307}, - {"name": "range", "size": 772}, - {"name": "select", "size": 296}, - {"name": "stddev", "size": 363}, - {"name": "sub", "size": 600}, - {"name": "sum", "size": 280}, - {"name": "update", "size": 307}, - {"name": "variance", "size": 335}, - {"name": "where", "size": 299}, - {"name": "xor", "size": 354}, - {"name": "_", "size": 264} - ] - }, - {"name": "Minimum", "size": 843}, - {"name": "Not", "size": 1554}, - {"name": "Or", "size": 970}, - {"name": "Query", "size": 13896}, - {"name": "Range", "size": 1594}, - {"name": "StringUtil", "size": 4130}, - {"name": "Sum", "size": 791}, - {"name": "Variable", "size": 1124}, - {"name": "Variance", "size": 1876}, - {"name": "Xor", "size": 1101} - ] - }, - { - "name": "scale", - "children": [ - {"name": "IScaleMap", "size": 2105}, - {"name": "LinearScale", "size": 1316}, - {"name": "LogScale", "size": 3151}, - {"name": "OrdinalScale", "size": 3770}, - {"name": "QuantileScale", "size": 2435}, - {"name": "QuantitativeScale", "size": 4839}, - {"name": "RootScale", "size": 1756}, - {"name": "Scale", "size": 4268}, - {"name": "ScaleType", "size": 1821}, - {"name": "TimeScale", "size": 5833} - ] - }, - { - "name": "util", - "children": [ - {"name": "Arrays", "size": 8258}, - {"name": "Colors", "size": 10001}, - {"name": "Dates", "size": 8217}, - {"name": "Displays", "size": 12555}, - {"name": "Filter", "size": 2324}, - {"name": "Geometry", "size": 10993}, - { - "name": "heap", - "children": [ - {"name": "FibonacciHeap", "size": 9354}, - {"name": "HeapNode", "size": 1233} - ] - }, - {"name": "IEvaluable", "size": 335}, - {"name": "IPredicate", "size": 383}, - {"name": "IValueProxy", "size": 874}, - { - "name": "math", - "children": [ - {"name": "DenseMatrix", "size": 3165}, - {"name": "IMatrix", "size": 2815}, - {"name": "SparseMatrix", "size": 3366} - ] - }, - {"name": "Maths", "size": 17705}, - {"name": "Orientation", "size": 1486}, - { - "name": "palette", - "children": [ - {"name": "ColorPalette", "size": 6367}, - {"name": "Palette", "size": 1229}, - {"name": "ShapePalette", "size": 2059}, - {"name": "SizePalette", "size": 2291} - ] - }, - {"name": "Property", "size": 5559}, - {"name": "Shapes", "size": 19118}, - {"name": "Sort", "size": 6887}, - {"name": "Stats", "size": 6557}, - {"name": "Strings", "size": 22026} - ] - }, - { - "name": "vis", - "children": [ - { - "name": "axis", - "children": [ - {"name": "Axes", "size": 1302}, - {"name": "Axis", "size": 24593}, - {"name": "AxisGridLine", "size": 652}, - {"name": "AxisLabel", "size": 636}, - {"name": "CartesianAxes", "size": 6703} - ] - }, - { - "name": "controls", - "children": [ - {"name": "AnchorControl", "size": 2138}, - {"name": "ClickControl", "size": 3824}, - {"name": "Control", "size": 1353}, - {"name": "ControlList", "size": 4665}, - {"name": "DragControl", "size": 2649}, - {"name": "ExpandControl", "size": 2832}, - {"name": "HoverControl", "size": 4896}, - {"name": "IControl", "size": 763}, - {"name": "PanZoomControl", "size": 5222}, - {"name": "SelectionControl", "size": 7862}, - {"name": "TooltipControl", "size": 8435} - ] - }, - { - "name": "data", - "children": [ - {"name": "Data", "size": 20544}, - {"name": "DataList", "size": 19788}, - {"name": "DataSprite", "size": 10349}, - {"name": "EdgeSprite", "size": 3301}, - {"name": "NodeSprite", "size": 19382}, - { - "name": "render", - "children": [ - {"name": "ArrowType", "size": 698}, - {"name": "EdgeRenderer", "size": 5569}, - {"name": "IRenderer", "size": 353}, - {"name": "ShapeRenderer", "size": 2247} - ] - }, - {"name": "ScaleBinding", "size": 11275}, - {"name": "Tree", "size": 7147}, - {"name": "TreeBuilder", "size": 9930} - ] - }, - { - "name": "events", - "children": [ - {"name": "DataEvent", "size": 2313}, - {"name": "SelectionEvent", "size": 1880}, - {"name": "TooltipEvent", "size": 1701}, - {"name": "VisualizationEvent", "size": 1117} - ] - }, - { - "name": "legend", - "children": [ - {"name": "Legend", "size": 20859}, - {"name": "LegendItem", "size": 4614}, - {"name": "LegendRange", "size": 10530} - ] - }, - { - "name": "operator", - "children": [ - { - "name": "distortion", - "children": [ - {"name": "BifocalDistortion", "size": 4461}, - {"name": "Distortion", "size": 6314}, - {"name": "FisheyeDistortion", "size": 3444} - ] - }, - { - "name": "encoder", - "children": [ - {"name": "ColorEncoder", "size": 3179}, - {"name": "Encoder", "size": 4060}, - {"name": "PropertyEncoder", "size": 4138}, - {"name": "ShapeEncoder", "size": 1690}, - {"name": "SizeEncoder", "size": 1830} - ] - }, - { - "name": "filter", - "children": [ - {"name": "FisheyeTreeFilter", "size": 5219}, - {"name": "GraphDistanceFilter", "size": 3165}, - {"name": "VisibilityFilter", "size": 3509} - ] - }, - {"name": "IOperator", "size": 1286}, - { - "name": "label", - "children": [ - {"name": "Labeler", "size": 9956}, - {"name": "RadialLabeler", "size": 3899}, - {"name": "StackedAreaLabeler", "size": 3202} - ] - }, - { - "name": "layout", - "children": [ - {"name": "AxisLayout", "size": 6725}, - {"name": "BundledEdgeRouter", "size": 3727}, - {"name": "CircleLayout", "size": 9317}, - {"name": "CirclePackingLayout", "size": 12003}, - {"name": "DendrogramLayout", "size": 4853}, - {"name": "ForceDirectedLayout", "size": 8411}, - {"name": "IcicleTreeLayout", "size": 4864}, - {"name": "IndentedTreeLayout", "size": 3174}, - {"name": "Layout", "size": 7881}, - {"name": "NodeLinkTreeLayout", "size": 12870}, - {"name": "PieLayout", "size": 2728}, - {"name": "RadialTreeLayout", "size": 12348}, - {"name": "RandomLayout", "size": 870}, - {"name": "StackedAreaLayout", "size": 9121}, - {"name": "TreeMapLayout", "size": 9191} - ] - }, - {"name": "Operator", "size": 2490}, - {"name": "OperatorList", "size": 5248}, - {"name": "OperatorSequence", "size": 4190}, - {"name": "OperatorSwitch", "size": 2581}, - {"name": "SortOperator", "size": 2023} - ] - }, - {"name": "Visualization", "size": 16540} - ] - } - ] -} diff --git a/examples/IPython Kernel/gui/gui-gtk.py b/examples/IPython Kernel/gui/gui-gtk.py deleted file mode 100755 index 80d888aff8b..00000000000 --- a/examples/IPython Kernel/gui/gui-gtk.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -"""Simple GTK example to manually test event loop integration. - -This is meant to run tests manually in ipython as: - -In [5]: %gui gtk - -In [6]: %run gui-gtk.py -""" - -import pygtk -pygtk.require('2.0') -import gtk - - -def hello_world(wigdet, data=None): - print("Hello World") - -def delete_event(widget, event, data=None): - return False - -def destroy(widget, data=None): - gtk.main_quit() - -window = gtk.Window(gtk.WINDOW_TOPLEVEL) -window.connect("delete_event", delete_event) -window.connect("destroy", destroy) -button = gtk.Button("Hello World") -button.connect("clicked", hello_world, None) - -window.add(button) -button.show() -window.show() - -try: - from IPython.lib.inputhook import enable_gui - enable_gui('gtk') -except ImportError: - gtk.main() diff --git a/examples/IPython Kernel/gui/gui-gtk3.py b/examples/IPython Kernel/gui/gui-gtk3.py deleted file mode 100644 index 935c026d5e3..00000000000 --- a/examples/IPython Kernel/gui/gui-gtk3.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -"""Simple Gtk example to manually test event loop integration. - -This is meant to run tests manually in ipython as: - -In [1]: %gui gtk3 - -In [2]: %run gui-gtk3.py -""" - -from gi.repository import Gtk - - -def hello_world(wigdet, data=None): - print("Hello World") - -def delete_event(widget, event, data=None): - return False - -def destroy(widget, data=None): - Gtk.main_quit() - -window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) -window.connect("delete_event", delete_event) -window.connect("destroy", destroy) -button = Gtk.Button(label="Hello World") -button.connect("clicked", hello_world, None) - -window.add(button) -button.show() -window.show() - -try: - from IPython.lib.inputhook import enable_gui - enable_gui('gtk3') -except ImportError: - Gtk.main() diff --git a/examples/IPython Kernel/gui/gui-gtk4.py b/examples/IPython Kernel/gui/gui-gtk4.py new file mode 100644 index 00000000000..44ff1d97ed4 --- /dev/null +++ b/examples/IPython Kernel/gui/gui-gtk4.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +"""Simple Gtk example to manually test event loop integration. + +This is meant to run tests manually in ipython as: + +In [1]: %gui gtk4 + +In [2]: %run gui-gtk4.py +""" + +import gi + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, GLib # noqa + + +def hello_world(widget, data=None): + print("Hello World") + + +def close_request_cb(widget, data=None): + global running + running = False + + +running = True +window = Gtk.Window() +window.connect("close-request", close_request_cb) +button = Gtk.Button(label="Hello World") +button.connect("clicked", hello_world, None) + +window.set_child(button) +window.show() + +context = GLib.MainContext.default() +while running: + context.iteration(True) diff --git a/examples/IPython Kernel/gui/gui-pyglet.py b/examples/IPython Kernel/gui/gui-pyglet.py deleted file mode 100644 index f64162286a0..00000000000 --- a/examples/IPython Kernel/gui/gui-pyglet.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python -"""Simple pyglet example to manually test event loop integration. - -This is meant to run tests manually in ipython as: - -In [5]: %gui pyglet - -In [6]: %run gui-pyglet.py -""" - -import pyglet - - -window = pyglet.window.Window() -label = pyglet.text.Label('Hello, world', - font_name='Times New Roman', - font_size=36, - x=window.width//2, y=window.height//2, - anchor_x='center', anchor_y='center') -@window.event -def on_close(): - window.close() - -@window.event -def on_draw(): - window.clear() - label.draw() - -try: - from IPython.lib.inputhook import enable_gui - enable_gui('pyglet') -except ImportError: - pyglet.app.run() diff --git a/examples/IPython Kernel/gui/gui-tk.py b/examples/IPython Kernel/gui/gui-tk.py deleted file mode 100755 index 89caf3ed622..00000000000 --- a/examples/IPython Kernel/gui/gui-tk.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -"""Simple Tk example to manually test event loop integration. - -This is meant to run tests manually in ipython as: - -In [5]: %gui tk - -In [6]: %run gui-tk.py -""" - -from tkinter import * - - -class MyApp: - - def __init__(self, root): - frame = Frame(root) - frame.pack() - - self.button = Button(frame, text="Hello", command=self.hello_world) - self.button.pack(side=LEFT) - - def hello_world(self): - print("Hello World!") - -root = Tk() - -app = MyApp(root) - -try: - from IPython.lib.inputhook import enable_gui - enable_gui('tk', root) -except ImportError: - root.mainloop() diff --git a/examples/IPython Kernel/gui/gui-wx.py b/examples/IPython Kernel/gui/gui-wx.py deleted file mode 100755 index 86e37acfc9d..00000000000 --- a/examples/IPython Kernel/gui/gui-wx.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python -""" -A Simple wx example to test IPython's event loop integration. - -To run this do: - -In [5]: %gui wx # or start IPython with '--gui wx' - -In [6]: %run gui-wx.py - -Ref: Modified from wxPython source code wxPython/samples/simple/simple.py -""" - -import wx - - -class MyFrame(wx.Frame): - """ - This is MyFrame. It just shows a few controls on a wxPanel, - and has a simple menu. - """ - def __init__(self, parent, title): - wx.Frame.__init__(self, parent, -1, title, - pos=(150, 150), size=(350, 200)) - - # Create the menubar - menuBar = wx.MenuBar() - - # and a menu - menu = wx.Menu() - - # add an item to the menu, using \tKeyName automatically - # creates an accelerator, the third param is some help text - # that will show up in the statusbar - menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample") - - # bind the menu event to an event handler - self.Bind(wx.EVT_MENU, self.OnTimeToClose, id=wx.ID_EXIT) - - # and put the menu on the menubar - menuBar.Append(menu, "&File") - self.SetMenuBar(menuBar) - - self.CreateStatusBar() - - # Now create the Panel to put the other controls on. - panel = wx.Panel(self) - - # and a few controls - text = wx.StaticText(panel, -1, "Hello World!") - text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)) - text.SetSize(text.GetBestSize()) - btn = wx.Button(panel, -1, "Close") - funbtn = wx.Button(panel, -1, "Just for fun...") - - # bind the button events to handlers - self.Bind(wx.EVT_BUTTON, self.OnTimeToClose, btn) - self.Bind(wx.EVT_BUTTON, self.OnFunButton, funbtn) - - # Use a sizer to layout the controls, stacked vertically and with - # a 10 pixel border around each - sizer = wx.BoxSizer(wx.VERTICAL) - sizer.Add(text, 0, wx.ALL, 10) - sizer.Add(btn, 0, wx.ALL, 10) - sizer.Add(funbtn, 0, wx.ALL, 10) - panel.SetSizer(sizer) - panel.Layout() - - - def OnTimeToClose(self, evt): - """Event handler for the button click.""" - print("See ya later!") - self.Close() - - def OnFunButton(self, evt): - """Event handler for the button click.""" - print("Having fun yet?") - - -class MyApp(wx.App): - def OnInit(self): - frame = MyFrame(None, "Simple wxPython App") - self.SetTopWindow(frame) - - print("Print statements go to this stdout window by default.") - - frame.Show(True) - return True - - -if __name__ == '__main__': - - app = wx.GetApp() - if app is None: - app = MyApp(redirect=False, clearSigInt=False) - else: - frame = MyFrame(None, "Simple wxPython App") - app.SetTopWindow(frame) - print("Print statements go to this stdout window by default.") - frame.Show(True) - - try: - from IPython.lib.inputhook import enable_gui - enable_gui('wx', app) - except ImportError: - app.MainLoop() diff --git a/examples/IPython Kernel/ipython-get-history.py b/examples/IPython Kernel/ipython-get-history.py index 3768aea4e52..ee0ed17074e 100755 --- a/examples/IPython Kernel/ipython-get-history.py +++ b/examples/IPython Kernel/ipython-get-history.py @@ -16,22 +16,26 @@ to build much more flexible and powerful tools to browse and pull from the history database. """ + import sys +from pathlib import Path from IPython.core.history import HistoryAccessor session_number = int(sys.argv[1]) if len(sys.argv) > 2: - dest = open(sys.argv[2], "w") - raw = not sys.argv[2].endswith('.py') + filepath = Path(sys.argv[2]) + dest = open(filepath, "w", encoding="utf-8") + raw = not filepath.name.endswith(".py") else: dest = sys.stdout raw = True -dest.write("# coding: utf-8\n") -# Profiles other than 'default' can be specified here with a profile= argument: -hist = HistoryAccessor() +with dest: + dest.write("# coding: utf-8\n") + + # Profiles other than 'default' can be specified here with a profile= argument: + hist = HistoryAccessor() -for session, lineno, cell in hist.get_range(session=session_number, raw=raw): - cell = cell.encode('utf-8') # This line is only needed on Python 2. - dest.write(cell + '\n') + for session, lineno, cell in hist.get_range(session=session_number, raw=raw): + dest.write(cell + "\n") diff --git a/examples/IPython Kernel/nbpackage/__init__.py b/examples/IPython Kernel/nbpackage/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/IPython Kernel/nbpackage/mynotebook.ipynb b/examples/IPython Kernel/nbpackage/mynotebook.ipynb deleted file mode 100644 index fd3920ca420..00000000000 --- a/examples/IPython Kernel/nbpackage/mynotebook.ipynb +++ /dev/null @@ -1,69 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# My Notebook" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def foo():\n", - " return \"foo\"" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def has_ip_syntax():\n", - " listing = !ls\n", - " return listing" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def whatsmyname():\n", - " return __name__" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/IPython Kernel/nbpackage/nbs/__init__.py b/examples/IPython Kernel/nbpackage/nbs/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/IPython Kernel/nbpackage/nbs/other.ipynb b/examples/IPython Kernel/nbpackage/nbs/other.ipynb deleted file mode 100644 index dad7e734a70..00000000000 --- a/examples/IPython Kernel/nbpackage/nbs/other.ipynb +++ /dev/null @@ -1,44 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook just defines `bar`" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def bar(x):\n", - " return \"bar\" * x" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Index.ipynb b/examples/Index.ipynb deleted file mode 100644 index 30e201a7e31..00000000000 --- a/examples/Index.ipynb +++ /dev/null @@ -1,61 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# IPython Documentation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This directory contains IPython's notebook-based documentation. This augments our [Sphinx-based documentation](https://ipython.org/ipython-doc/stable/index.html) with notebooks that contain interactive tutorials and examples. Over time, more of our documentation will be pulled into this format." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Topics" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "* [IPython Kernel](IPython Kernel/Index.ipynb): IPython's core syntax and command line features available in all frontends\n", - "* [Embedding](Embedding/Index.ipynb): Embedding and reusing IPython's components into other applications\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/auto_suggest_llm.py b/examples/auto_suggest_llm.py new file mode 100644 index 00000000000..d137241eff4 --- /dev/null +++ b/examples/auto_suggest_llm.py @@ -0,0 +1,174 @@ +""" +This is an example of Fake LLM Completer for IPython, as +well as example on how to configure IPython for LLMs. + +8.32 – this is provisional and may change. + +To test this you can run the following command from the root of IPython +directory: + + $ ipython --TerminalInteractiveShell.llm_provider_class=examples.auto_suggest_llm.ExampleCompletionProvider + +Or you can set the value in your config file, which also allows you to set a +keyboard shortcut:: + + c.TerminalInteractiveShell.llm_provider_class = "examples.auto_suggest_llm.ExampleCompletionProvider" + c.TerminalInteractiveShell.shortcuts = [ + { + "new_keys": ["c-q"], + "command": "IPython:auto_suggest.llm_autosuggestion", + "new_filter": "navigable_suggestions & default_buffer_focused", + "create": True, + }, + ] + + +You can use the following configuration option to:: + + c.TerminalInteractiveShell.llm_constructor_kwargs = {"model_id": "mymodel"} + + +For convenience and testing you can bind a shortcut at runtime:: + + In [1]: from examples.auto_suggest_llm import setup_shortcut + ...: setup_shortcut('c-q') + + +Getting access to history content +--------------------------------- + +This uses the same providers as Jupyter AI, In JupyterAI, providers may get +access to the current notebook content to pass as to the LLM as context. + +Here Jupyter AI documents how to get such context. + +https://jupyter-ai.readthedocs.io/en/latest/developers/index.html + + +When reusing these models you may want to pass them more context as well in +IPython to do so you can set the +`c.TerminalInteractiveShell.llm_prefix_from_history` to `"no_prefix"`, +`"input_history"` or a fully qualified name of a function that will get +imported, get passed a `HistoryManager`, and return a prefix to be added the LLM +context. + + +For more flexibility, subclass the provider, and access the history of IPython +via: + + ``` + ip = get_ipython() + hm = ip.history_manager() + hm.get_range(...) # will let you select how many input/output... etc. + ``` + +""" + +import asyncio +import textwrap +from typing import Any +from collections.abc import AsyncIterable, AsyncIterator + +from jupyter_ai_magics.models.completion import ( + InlineCompletionList, + InlineCompletionReply, + InlineCompletionRequest, + InlineCompletionStreamChunk, +) +from jupyter_ai_magics.providers import BaseProvider +from langchain_community.llms import FakeListLLM + + +from IPython.terminal.shortcuts.filters import ( + navigable_suggestions, + default_buffer_focused, +) +from IPython.terminal.shortcuts.auto_suggest import llm_autosuggestion + + +def setup_shortcut(seq): + import IPython + + ip = IPython.get_ipython() + ip.pt_app.key_bindings.add_binding( + seq, filter=(navigable_suggestions & default_buffer_focused) + )(llm_autosuggestion), + + +class ExampleCompletionProvider(BaseProvider, FakeListLLM): # type: ignore[misc, valid-type] + """ + This is an example Fake LLM provider for IPython + + As of 8.32 this is provisional and may change without any warnings + """ + + id = "my_provider" + name = "My Provider" + model_id_key = "model" + models = ["model_a"] + + def __init__(self, **kwargs: Any): + kwargs["responses"] = ["This fake response will not be used for completion"] + kwargs["model_id"] = "model_a" + super().__init__(**kwargs) + + async def generate_inline_completions( + self, request: InlineCompletionRequest + ) -> InlineCompletionReply: + raise ValueError("IPython 8.32 only support streaming models for now.") + + async def stream_inline_completions( + self, request: InlineCompletionRequest + ) -> AsyncIterator[InlineCompletionStreamChunk]: + token_1 = f"t{request.number}s0" + + yield InlineCompletionReply( + list=InlineCompletionList( + items=[ + {"insertText": "It", "isIncomplete": True, "token": token_1}, + ] + ), + reply_to=request.number, + ) + + reply: InlineCompletionStreamChunk + async for reply in self._stream( + textwrap.dedent( + """ + was then that the fox appeared. + “Good morning,” said the fox. + “Good morning,” the little prince responded politely, although when he turned around he saw nothing. + “I am right here,” the voice said, “under the apple tree.” + “Who are you?” asked the little prince, and added, “You are very pretty to look at.” + “I am a fox,” said the fox. + “Come and play with me,” proposed the little prince. “I am so unhappy.” + """ + ).strip(), + request.number, + token_1, + start_with="It", + ): + yield reply + + async def _stream( + self, sentence: str, request_number: int, token: str, start_with: str = "" + ) -> AsyncIterable[InlineCompletionStreamChunk]: + suggestion = start_with + + for fragment in sentence.split(" "): + await asyncio.sleep(0.05) + suggestion += " " + fragment + yield InlineCompletionStreamChunk( + type="stream", + response={"insertText": suggestion, "token": token}, + reply_to=request_number, + done=False, + ) + + # finally, send a message confirming that we are done + yield InlineCompletionStreamChunk( + type="stream", + response={"insertText": suggestion, "token": token}, + reply_to=request_number, + done=True, + ) diff --git a/examples/images/FrontendKernel.graffle/data.plist b/examples/images/FrontendKernel.graffle/data.plist deleted file mode 100644 index 6179209c407..00000000000 --- a/examples/images/FrontendKernel.graffle/data.plist +++ /dev/null @@ -1,461 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGraffle - 139.18.0.187838 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {576, 733}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2014-05-27 21:39:30 +0000 - Creator - bgranger - DisplayScale - 1 0/72 in = 1.0000 in - GraphDocumentVersion - 8 - GraphicsList - - - Class - LineGraphic - ControlPoints - - {0, 0} - {-7, 8} - {6.9999849080788863, -8.0000033519149838} - {0, 0} - - ID - 29 - Points - - {164, 341.5} - {186.5, 338} - {196, 327.5} - - Style - - stroke - - Bezier - - HeadArrow - 0 - Legacy - - LineType - 1 - TailArrow - 0 - - - - - Bounds - {{107.64779663085938, 305.5}, {69.088050842285156, 84.499992370605469}} - Class - ShapedGraphic - ID - 9 - ImageID - 1 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - - Class - LineGraphic - Head - - ID - 6 - Position - 0.53676468133926392 - - ID - 8 - Points - - {288.09285678056523, 276} - {288.49999833106995, 304.50001973116196} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 7 - - - - Bounds - {{207, 263}, {162, 13}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 7 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 xkcd-Regular;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs20 \cf0 Interactive Computing Protocol} - VerticalPad - 0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 1 - - ID - 6 - Points - - {252, 304.50001973116196} - {320, 304.50001973116196} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - Pattern - 1 - TailArrow - FilledArrow - - - Tail - - ID - 5 - - - - Bounds - {{186.5, 286.5}, {65, 36}} - Class - ShapedGraphic - ID - 5 - Shape - Rectangle - Style - - shadow - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 xkcd-Regular;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs20 \cf0 Frontend} - - - - Bounds - {{320.5, 286.5}, {65, 36}} - Class - ShapedGraphic - ID - 1 - Shape - Rectangle - Style - - shadow - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 xkcd-Regular;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs20 \cf0 Kernel} - - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 2 - ImageLinkBack - - - - ImageList - - image1.png - - KeepToScale - - Layers - - - Lock - NO - Name - Layer 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2014-05-27 22:28:18 +0000 - Modifier - bgranger - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {612, 792} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - - name - Canvas 1 - - - Frame - {{277, 7}, {832, 871}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{96.5, 197.5}, {348.5, 366}} - Zoom - 2 - ZoomValues - - - Canvas 1 - 2 - 1 - - - - - diff --git a/examples/images/FrontendKernel.graffle/image1.png b/examples/images/FrontendKernel.graffle/image1.png deleted file mode 100644 index c01e3497f5b..00000000000 Binary files a/examples/images/FrontendKernel.graffle/image1.png and /dev/null differ diff --git a/examples/images/FrontendKernel.png b/examples/images/FrontendKernel.png deleted file mode 100644 index 62aa890274a..00000000000 Binary files a/examples/images/FrontendKernel.png and /dev/null differ diff --git a/examples/images/animation.m4v b/examples/images/animation.m4v deleted file mode 100644 index 13ecf88c5de..00000000000 Binary files a/examples/images/animation.m4v and /dev/null differ diff --git a/examples/images/ipython_logo.png b/examples/images/ipython_logo.png deleted file mode 100644 index e9bdce39415..00000000000 Binary files a/examples/images/ipython_logo.png and /dev/null differ diff --git a/examples/images/python_logo.svg b/examples/images/python_logo.svg deleted file mode 100644 index 116eaac3111..00000000000 --- a/examples/images/python_logo.svg +++ /dev/null @@ -1,269 +0,0 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/utils/cwd_prompt.py b/examples/utils/cwd_prompt.py index 1d1b03ac66b..6accf4db212 100644 --- a/examples/utils/cwd_prompt.py +++ b/examples/utils/cwd_prompt.py @@ -1,26 +1,22 @@ -"""This is an example that shows how to create new prompts for IPython -""" +"""This is an example that shows how to create new prompts for IPython""" from IPython.terminal.prompts import Prompts, Token import os + class MyPrompt(Prompts): + def in_prompt_tokens(self): + return [(Token, os.getcwd()), (Token.Prompt, ">>>")] - def in_prompt_tokens(self, cli=None): - return [(Token, os.getcwd()), - (Token.Prompt, '>>>')] def load_ipython_extension(shell): new_prompts = MyPrompt(shell) new_prompts.old_prompts = shell.prompts shell.prompts = new_prompts + def unload_ipython_extension(shell): - if not hasattr(shell.prompts, 'old_prompts'): + if not hasattr(shell.prompts, "old_prompts"): print("cannot unload") else: shell.prompts = shell.prompts.old_prompts - - - - diff --git a/examples/utils/list_pyfiles.ipy b/examples/utils/list_pyfiles.ipy index 71a9e5d75ce..4b15482e766 100644 --- a/examples/utils/list_pyfiles.ipy +++ b/examples/utils/list_pyfiles.ipy @@ -3,4 +3,4 @@ from IPython.display import FileLink, display files =!ls *.py for f in files: - display(FileLink(f)) \ No newline at end of file + display(FileLink(f)) diff --git a/examples/utils/list_subdirs.ipy b/examples/utils/list_subdirs.ipy index a1bbb74ca9d..b8a16124565 100644 --- a/examples/utils/list_subdirs.ipy +++ b/examples/utils/list_subdirs.ipy @@ -4,4 +4,4 @@ from IPython.display import FileLinks, display dirs =!ls -d */ for d in dirs: if d != '__pycache__/': - display(FileLinks(d)) \ No newline at end of file + display(FileLinks(d)) diff --git a/long_description.rst b/long_description.rst new file mode 100644 index 00000000000..6fabbd207c8 --- /dev/null +++ b/long_description.rst @@ -0,0 +1,37 @@ +IPython provides a rich toolkit to help you make the most out of using Python +interactively. Its main components are: + + * A powerful interactive Python shell + * A `Jupyter `_ kernel to work with Python code in Jupyter + notebooks and other interactive frontends. + +The enhanced interactive Python shells have the following main features: + + * Comprehensive object introspection. + + * Input history, persistent across sessions. + + * Caching of output results during a session with automatically generated + references. + + * Extensible tab completion, with support by default for completion of python + variables and keywords, filenames and function keywords. + + * Extensible system of 'magic' commands for controlling the environment and + performing many tasks related either to IPython or the operating system. + + * A rich configuration system with easy switching between different setups + (simpler than changing $PYTHONSTARTUP environment variables every time). + + * Session logging and reloading. + + * Extensible syntax processing for special purpose situations. + + * Access to the system shell with user-extensible alias system. + + * Easily embeddable in other Python programs and GUIs. + + * Integrated access to the pdb debugger and the Python profiler. + +The latest development version is always available from IPython's `GitHub +site `_. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..f56e5153399 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,593 @@ +[build-system] +requires = ["setuptools>=80.0"] +# We need access to the 'setupbase' module at build time. +# Hence we declare a custom build backend. +build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions +backend-path = ["."] + +[project] +name = "ipython" +description = "IPython: Productive Interactive Computing" +keywords = ["Interactive", "Interpreter", "Shell", "Embedding"] +classifiers = [ + "Framework :: IPython", + "Framework :: Jupyter", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Topic :: System :: Shells", +] +license = "BSD-3-Clause" +license-files = ["LICENSE", "COPYING.rst"] +requires-python = ">=3.11" +dependencies = [ + 'colorama>=0.4.4; sys_platform == "win32"', + "ipython-pygments-lexers>=1.0.0", + "jedi>=0.18.2", + "matplotlib-inline>=0.1.6", + 'pexpect>4.6; sys_platform != "win32" and sys_platform != "emscripten"', + "prompt_toolkit>=3.0.41,<3.1.0", + 'psutil>=7; sys_platform != "emscripten" and sys_platform != "cygwin" and sys_platform != "android"', + "pygments>=2.14.0", # wheel + "stack_data>=0.6.0", + "traitlets>=5.13.0", + "typing_extensions>=4.6; python_version<'3.12'", +] +dynamic = ["authors", "version"] + +[project.scripts] +ipython = "IPython:start_ipython" +ipython3 = "IPython:start_ipython" + +[project.readme] +file = "long_description.rst" +content-type = "text/x-rst" + +[project.urls] +Homepage = "https://ipython.org" +Documentation = "https://ipython.readthedocs.io/" +Funding = "https://jupyter.org/about#donate" +Source = "https://github.com/ipython/ipython" +Tracker = "https://github.com/ipython/ipython/issues" + +[project.optional-dependencies] +black = [ + "black", +] +doc = [ + "docrepr", + "intersphinx_registry", + "ipykernel", + "ipython[test,matplotlib]", + "setuptools>=80.0", + "sphinx_toml==0.0.4", + "sphinx-rtd-theme>=0.1.8", # wheel + "sphinx>=8.0", + "typing_extensions", +] +test = [ + "pytest>=7.0.0", # keep in sync with tool.pytest.ini_options.minversion + "pytest-asyncio>=1.0.0", + "testpath>=0.2", + "packaging>=23.0.0", + "setuptools>=80.0", +] +test_extra = [ + "ipython[test]", + "curio", + "jupyter_ai", + "ipython[matplotlib]", + "nbformat", + "nbclient", + "ipykernel>6.30", + "numpy>=2.0", + "pandas>2.1", + "trio>=0.22.0", +] +matplotlib = [ + "matplotlib>3.9" +] +all = [ + "ipython[doc,matplotlib,test,test_extra]", + "argcomplete>=3.0", +] + + +[tool.mypy] +python_version = "3.11" +ignore_missing_imports = true +follow_imports = 'silent' +exclude = [ + 'test_\.+\.py', + 'IPython.utils.tests.test_wildcard', + 'testing', + 'tests', + 'IPython/core/application.py', + 'IPython/core/profileapp.py', + 'IPython/lib/deepreload.py', + 'IPython/sphinxext/ipython_directive.py', + 'IPython/terminal/ipapp.py', + 'IPython/utils/path.py', + 'IPython/core/debugger_backport.py', +] +check_untyped_defs = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +# ignore_errors = false +# ignore_missing_imports = false +disallow_incomplete_defs = true +disallow_untyped_defs = true +warn_redundant_casts = true +warn_unused_ignores = true +strict_equality = true +warn_unreachable = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] + +[[tool.mypy.overrides]] +module = "sphinx.*" +follow_imports = "skip" +#warn_unreachable = true +#strict = true + +[[tool.mypy.overrides]] +# numpy's stubs use PEP 695 `type` statements (3.12+), which mypy rejects +# when parsing under python_version = "3.11". Skip following its stubs. +module = "numpy.*" +follow_imports = "skip" +follow_imports_for_stubs = true + +[[tool.mypy.overrides]] +# mypy narrows on `sys.platform`, so the platform guard in +# `_supports_kitty_graphics` can be reported as unreachable in the +# cross-platform CI runs. Turn that off for this module only. +module = "IPython.core.kitty" +warn_unreachable = false + +[[tool.mypy.overrides]] +module = [ + "IPython.core.events", + "IPython.core.magic", + "IPython.utils._process_common", + "IPython.utils._process_win32", +] +# Strictest configuration, everything turned up to the max +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +disallow_any_generics = true +disallow_subclassing_any = true +warn_return_any = true +warn_unused_ignores = true +warn_unreachable = true +strict_equality = true +no_implicit_optional = true +ignore_errors = false +ignore_missing_imports = false + +[[tool.mypy.overrides]] +module = [ + "IPython.core.ultratb", +] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_untyped_calls = false +disallow_untyped_decorators = true +disallow_untyped_defs = false +ignore_errors = false +ignore_missing_imports = false + +[[tool.mypy.overrides]] +module = [ + "IPython.core.crashhandler", + "IPython.utils.text", + "IPython.utils.PyColorize", + "IPython.utils.sentinel", + "IPython.utils.data", + "IPython.utils.importstring", + "IPython.utils.syspathcontext", + "IPython.utils.generics", + "IPython.utils.encoding", + "IPython.utils.module_paths", +] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +ignore_errors = false +ignore_missing_imports = false + +[[tool.mypy.overrides]] +module = [ + "IPython.core.debugger", +] +disallow_untyped_defs = false +ignore_errors = false +ignore_missing_imports = true +disallow_untyped_calls = false +disallow_incomplete_defs = false +check_untyped_defs = true +disallow_untyped_decorators = true + +[[tool.mypy.overrides]] +module = [ + "IPython.core.interactiveshell", +] +disallow_untyped_defs = false +ignore_errors = false +ignore_missing_imports = true +disallow_untyped_calls = false +disallow_incomplete_defs = false +check_untyped_defs = true +disallow_untyped_decorators = false +disable_error_code = [ + "arg-type", + "assignment", + "attr-defined", + "index", + "var-annotated", + "union-attr" +] + +# Newly promoted to type-checking (previously fully ignored). +# These pass a lenient check (real type errors surfaced) but are not yet +# fully annotated, so untyped defs are still permitted for now. +[[tool.mypy.overrides]] +module = [ + "IPython.core.async_helpers", + "IPython.core.builtin_trap", + "IPython.core.extensions", + "IPython.core.hooks", + "IPython.core.macro", + "IPython.core.magic_arguments", + "IPython.core.magics.ast_mod", + "IPython.core.payload", + "IPython.core.profiledir", + "IPython.external.qt_for_kernel", + "IPython.lib.guisupport", + "IPython.paths", + "IPython.sphinxext.ipython_console_highlighting", + "IPython.terminal.pt_inputhooks.asyncio", + "IPython.terminal.pt_inputhooks.gtk", + "IPython.terminal.pt_inputhooks.gtk3", + "IPython.terminal.pt_inputhooks.gtk4", + "IPython.terminal.pt_inputhooks.pyglet", + "IPython.terminal.pt_inputhooks.qt", + "IPython.terminal.pt_inputhooks.wx", + "IPython.terminal.ptutils", + "IPython.terminal.shortcuts.auto_match", + "IPython.utils._process_emscripten", + "IPython.utils.ipstruct", + "IPython.utils.process", + "IPython.utils.strdispatch", + "IPython.utils.terminal", +] +check_untyped_defs = true +disallow_incomplete_defs = false +disallow_untyped_defs = false +disallow_untyped_calls = false +disallow_untyped_decorators = false +ignore_errors = false +ignore_missing_imports = true + +# gloabl ignore error +[[tool.mypy.overrides]] +module = [ + "IPython", + "IPython.terminal.pt_inputhooks", + "IPython.terminal.shortcuts", + "IPython.core.alias", + "IPython.core.autocall", + "IPython.core.compilerop", + "IPython.core.completer", + "IPython.core.completerlib", + "IPython.core.display", + "IPython.core.display_functions", + "IPython.core.display_trap", + "IPython.core.displayhook", + "IPython.core.displaypub", + "IPython.core.formatters", + "IPython.core.getipython", + "IPython.core.guarded_eval", + "IPython.core.historyapp", + "IPython.core.inputtransformer2", + "IPython.core.logger", + "IPython.core.magics.auto", + "IPython.core.magics.basic", + "IPython.core.magics.code", + "IPython.core.magics.config", + "IPython.core.magics.display", + "IPython.core.magics.execution", + "IPython.core.magics.extension", + "IPython.core.magics.history", + "IPython.core.magics.logging", + "IPython.core.magics.namespace", + "IPython.core.magics.osm", + "IPython.core.magics.packaging", + "IPython.core.magics.pylab", + "IPython.core.magics.script", + "IPython.core.oinspect", + "IPython.core.page", + "IPython.core.payloadpage", + "IPython.core.prefilter", + "IPython.core.pylabtools", + "IPython.core.shellapp", + "IPython.core.splitinput", + "IPython.extensions.autoreload", + "IPython.extensions.storemagic", + "IPython.external.pickleshare", + "IPython.external.qt_loaders", + "IPython.lib.backgroundjobs", + "IPython.lib.clipboard", + "IPython.lib.demo", + "IPython.lib.display", + "IPython.lib.editorhooks", + "IPython.lib.latextools", + "IPython.lib.pretty", + "IPython.terminal.debugger", + "IPython.terminal.embed", + "IPython.terminal.interactiveshell", + "IPython.terminal.magics", + "IPython.terminal.prompts", + "IPython.terminal.pt_inputhooks.glut", + "IPython.terminal.pt_inputhooks.osx", + "IPython.terminal.pt_inputhooks.tk", + "IPython.terminal.shortcuts.auto_suggest", + "IPython.terminal.shortcuts.filters", + "IPython.utils.capture", + "IPython.utils.dir2", + "IPython.utils.io", + "IPython.utils.openpy", + "IPython.utils.tempdir", + "IPython.utils.timing", + "IPython.utils.tokenutil", + "IPython.utils.wildcard", + +] +disallow_untyped_defs = false +ignore_errors = true +ignore_missing_imports = true +disallow_untyped_calls = false +disallow_incomplete_defs = false +check_untyped_defs = true +disallow_untyped_decorators = false + +[tool.pytest.ini_options] +minversion = "7" # keep in sync with project.optional-dependencies.test +addopts = [ + "--durations=10", + "-pIPython.testing.plugin.pytest_ipdoctest", + "--ipdoctest-modules", + "--ignore=docs", + "--ignore=examples", + "--ignore=htmlcov", + "--ignore=ipython_kernel", + "--ignore=ipython_parallel", + "--ignore=results", + "--ignore=tmp", + "--ignore=tools", + "--ignore=traitlets", + "--ignore=tests/fake_ext_dir/", + "--ignore=IPython/sphinxext", + "--ignore=IPython/terminal/pt_inputhooks", + "--ignore=IPython/__main__.py", + "--ignore=IPython/external/qt_for_kernel.py", + "--ignore=IPython/utils/_process_posix.py" +] +doctest_optionflags = [ + "NORMALIZE_WHITESPACE", + "ELLIPSIS" +] +ipdoctest_optionflags = [ + "NORMALIZE_WHITESPACE", + "ELLIPSIS" +] +asyncio_mode = "strict" +asyncio_default_fixture_loop_scope = "function" +filterwarnings = [ + "error::ResourceWarning", +] + +[tool.pyright] +pythonPlatform="All" + +[tool.setuptools] +zip-safe = false +platforms = ["Linux", "Mac OSX", "Windows"] +include-package-data = false + +[tool.setuptools.packages.find] +include = ["IPython*"] +exclude = ["setupext", "tests*"] +namespaces = false + +[tool.setuptools.package-data] +"IPython" = ["py.typed"] +"IPython.core" = ["profile/README*"] +"IPython.utils" = ["themes/*.json"] +"IPython.testing.plugin" = ["*.txt"] + +[tool.setuptools.dynamic] +version = {attr = "IPython.core.release.__version__"} + +[tool.coverage.run] +omit = [ + # omit everything in /tmp as we run tempfile + "/tmp/*", +] + +[tool.ruff.lint] +extend-select = [ + # "B", # flake8-bugbear + # "I", # isort + # that will be a problem for pytest fixture unless you swap with the usefixture decorator https://docs.pytest.org/en/7.1.x/how-to/fixtures.html#use-fixtures-in-classes-and-modules-with-usefixtures + # "ARG", # flake8-unused-arguments + # "C4", # flake8-comprehensions + # "EM", # flake8-errmsg + # "ICN", # flake8-import-conventions + # "G", # flake8-logging-format + # "PGH", # pygrep-hooks + # "PIE", # flake8-pie + # "PL", # pylint + # "PTH", # flake8-use-pathlib + # "PT", # flake8-pytest-style + # "RET", # flake8-return + # "RUF", # Ruff-specific + # "SIM", # flake8-simplify + # "T20", # flake8-print + # "UP", # pyupgrade + "UP004", # 1 [*] Class `...` inherits from `object` + # "UP006", # 1 [*] Use `list` instead of `t.List` for type annotation + # "UP006", # 1 [*] Use `tuple` instead of `t.Tuple` for type annotation + # "UP010", # 1 [*] Unnecessary `__future__` import `print_function` for target Python version + "UP011", # 1 [*] Unnecessary parentheses to `functools.lru_cache` + "UP018", # 1 [*] Unnecessary `bool` call (rewrite as a literal) + # "UP006", #182 [*] Use `...` instead of `...` for type annotation + # "UP007", # 5 Use `X | Y` for type annotations + # "UP007", #149 [*] Use `X | Y` for type annotations + # "UP008", # 63 Use `super()` instead of `super(__class__, self)` + # "UP009", # 61 [*] UTF-8 encoding declaration is unnecessary + "UP015", # 0 [*] Unnecessary open modes (0 violations, locked in) + # "UP017", # 2 [*] Use `datetime.UTC` alias + # "UP020", # 15 [*] Use builtin `open` + # "UP024", # 16 [*] Replace aliased errors with `OSError` + # "UP025", # 91 [*] Remove unicode literals from strings + "UP028", # 3 Replace `yield` over `for` loop with `yield from` + "UP029", # 1 Unnecessary builtin import + # "UP030", # 28 Use implicit references for positional format fields + # "UP032", # 75 [*] Use f-string instead of `format` call + "UP034", # 1 [*] Avoid extraneous parentheses + "UP035", # 0 [*] Deprecated typing imports (0 violations, locked in) + # "UP036", # 3 Version block is outdated for minimum Python version + "UP037", # 1 [*] Remove quotes from type annotation + # "UP038", # 21 Use `X | Y` in `isinstance` call instead of `(X, Y)` + "YTT", # flake8-2020 (0 violations, locked in) + "FURB", # refurb (readability/modernization) + "B008", # function-call-in-default-argument (0 violations, locked in) + "B022", # useless-contextlib-suppress (0 violations, locked in) + "B025", # duplicate-try-block-exception (0 violations, locked in) + "C420", # unnecessary-dict-comprehension-for-iterable (0 violations, locked in) + "PLW0127", # self-assigning-variable (0 violations, locked in) + "PLW0128", # redeclared-assigned-name (0 violations, locked in) + "PLW0406", # import-self (0 violations, locked in) + "RUF007", # zip-instead-of-pairwise (0 violations, locked in) + "S324", # hashlib-insecure-hash-function (0 violations, locked in) + "SIM110", # reimplemented-builtin (0 violations, locked in) + "SIM211", # if-expr-with-false-true (0 violations, locked in) + "TRY203", # useless-try-except (0 violations, locked in) + # "EXE", # flake8-executable (file-mode/shebang churn across examples) + # "PYI", # flake8-pyi (stub-oriented; little value on source) + # "ICN", # flake8-import-conventions (codebase uses plain `matplotlib`, not `mpl`) + # "RUF100", # unused `noqa` (entangled with the F401 global-ignore; strips intentional markers) + # "PT", # flake8-pytest-style (PT015 false-positives on non-test `assert False`) + # "S", # flake8-bandit +] +ignore = [ + # "E501", # E501 Line too long (158 > 100 characters) + # "SIM105", # SIM105 Use `contextlib.suppress(...)` + # "PLR", # Design related pylint codes + # "S101", # Use of `assert` detected + "UP031", # 297 Use format specifiers instead of percent format + "I001", # 162 unsorted imports + "BLE001", # 111 blind-except + "UP032", # 90 f-strings + "C408", # 33 unnecessary collection call + "SIM201", # 27 collapsible IF + "PLC0414", # WRONG CONFLICT WITH EXPLICT REEXPORT + "E713", # 1 Test for membership should be `not in` + "F601", # 3 Dictionary key literal `True` repeated (`True` hashes to the same value as `1`) + "E702", # 3 Multiple statements on one line (semicolon) + "E721", # 3 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks + "E401", # 5 Multiple imports on one line + "E711", # 14 Comparison to `None` should be `cond is None` + "E701", # 19 Multiple statements on one line (colon) + "E741", # 25 Ambiguous variable name: `` + "E731", # 27 Do not assign a `lambda` expression, use a `def` + "E402", # 28 Module level import not at top of file + "E722", # 75 Do not use bare `except` + "F523", # 1 ; at end of line + "F403", # 7 import *` used; unable to detect undefined names + "F841", # 38 Local variable `...` is assigned to but never used + "F401", # 69 imported but unused + "F821", # 624 Undefined name + "FURB110", # prefer explicit `x if x else y` ternary over `x or y` (readability) + "SIM102", # collapsible-if + "S110", # try-except-pass + "PIE790", # unnecessary-placeholder + "RUF012", # mutable-class-default + "SIM115", # open-file-with-context-handler + "S102", # exec-builtin + "N999", # invalid-module-name + "SIM118", # in-dict-keys + "LOG015", # root-logger-call + "RUF059", # unused-unpacked-variable + "RUF022", # unsorted-dunder-all + "FLY002", # static-join-to-f-string + "RET501", # unnecessary-return-none + "T100", # debugger + "TRY002", # raise-vanilla-class + "RUF100", # unused-noqa + "UP020", # open-alias + "TRY004", # type-check-without-type-error + "B018", # useless-expression + "SIM114", # if-with-same-arms + "B009", # get-attr-with-constant + "C419", # unnecessary-comprehension-in-call + "DTZ005", # call-datetime-now-without-tzinfo + "EXE002", # shebang-missing-executable-file + "PIE810", # multiple-starts-ends-with + "RUF010", # explicit-f-string-type-conversion + "SIM103", # needless-bool + "UP009", # utf8-encoding-declaration + "B006", # mutable-argument-default + "PTH124", # py-path + "B004", # unreliable-callable-check + "EXE001", # shebang-not-executable + "G201", # logging-exc-info + "ISC004", # implicit-string-concatenation-in-collection-literal + "PIE794", # duplicate-class-field-definition + "PIE808", # unnecessary-range-start + "PLC0206", # dict-index-missing-items + "PLC0208", # iteration-over-set + "PLR1711", # useless-return + "PLW0120", # useless-else-on-loop + "PLW0602", # global-variable-not-assigned + "PLW1508", # invalid-envvar-default + "PYI034", # non-self-return-type + "RUF015", # unnecessary-iterable-allocation-for-first-element + "SIM101", # duplicate-isinstance-call + "SIM210", # if-expr-with-true-false + "SIM905", # split-static-string + "B010", # set-attr-with-constant + "B019", # cached-instance-method + "DTZ006", # call-datetime-fromtimestamp + "EXE005", # shebang-not-first-line + "PERF102", # incorrect-dict-iterator + "PIE800", # unnecessary-spread + "PLR0402", # manual-from-import + "PLR1722", # sys-exit-alias + "PLR1730", # if-stmt-min-max + "PLR2044", # empty-comment + "PYI036", # bad-exit-annotation + "PYI063", # pep484-style-positional-only-parameter + "SIM117", # multiple-with-statements + "UP036", # outdated-version-block +] +unfixable = [ + # Don't touch print statements + "T201", + # Don't touch noqa lines + "RUF100", +] + +[tool.ruff] +target-version = "py311" +extend-exclude = ["tests"] + +[tool.black] +# moved here from setup.cfg, where it had been in (ignored) INI syntax +exclude = 'timing\.py' diff --git a/readthedocs.yml b/readthedocs.yml deleted file mode 100644 index b9eadb806d6..00000000000 --- a/readthedocs.yml +++ /dev/null @@ -1,4 +0,0 @@ -conda: - file: docs/environment.yml -python: - version: 3 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 0c9e0fc1447..00000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -license_file = LICENSE diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 78529cf6dbc..541985ec9df --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Setup script for IPython. @@ -17,8 +16,6 @@ # The full license is in the file COPYING.rst, distributed with this software. #----------------------------------------------------------------------------- -from __future__ import print_function - import os import sys @@ -26,97 +23,52 @@ # # This check is also made in IPython/__init__, don't forget to update both when # changing Python version requirements. -if sys.version_info < (3, 5): - pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.' - try: - import pip - pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]]) - if pip_version < (9, 0, 1) : - pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\ - 'pip {} detected.'.format(pip.__version__) - else: - # pip is new enough - it must be something else - pip_message = '' - except Exception: - pass - - +if sys.version_info < (3, 11): error = """ -IPython 7.0+ supports Python 3.5 and above. -When using Python 2.7, please install IPython 5.x LTS Long Term Support version. -Python 3.3 and 3.4 were supported up to IPython 6.x. - -See IPython `README.rst` file for more information: - - https://github.com/ipython/ipython/blob/master/README.rst +IPython 9.x supports Python 3.11 and above, following SPEC0 +IPython 8.19+ supports Python 3.10 and above, following SPEC0 +IPython 8.13+ supports Python 3.9 and above, following NEP 29. +IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29. Python {py} detected. -{pip} -""".format(py=sys.version_info, pip=pip_message ) +""".format( + py=sys.version_info + ) print(error, file=sys.stderr) sys.exit(1) # At least we're on the python version we need, move on. -# BEFORE importing distutils, remove MANIFEST. distutils doesn't properly -# update it when the contents of directories change. -if os.path.exists('MANIFEST'): os.remove('MANIFEST') - -from distutils.core import setup +from setuptools import setup # Our own imports + from setupbase import target_update from setupbase import ( setup_args, - find_packages, - find_package_data, check_package_data_first, - find_entry_points, - build_scripts_entrypt, find_data_files, git_prebuild, - install_symlinked, - install_lib_symlink, - install_scripts_for_symlink, - unsymlink, ) -isfile = os.path.isfile -pjoin = os.path.join - -#------------------------------------------------------------------------------- -# Handle OS specific things -#------------------------------------------------------------------------------- - -if os.name in ('nt','dos'): - os_name = 'windows' -else: - os_name = os.name - -# Under Windows, 'sdist' has not been supported. Now that the docs build with -# Sphinx it might work, but let's not turn it on until someone confirms that it -# actually works. -if os_name == 'windows' and 'sdist' in sys.argv: - print('The sdist command is not available under Windows. Exiting.') - sys.exit(1) - - #------------------------------------------------------------------------------- # Things related to the IPython documentation #------------------------------------------------------------------------------- # update the manuals when building a source dist -if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'): +if len(sys.argv) >= 2 and sys.argv[1] == 'sdist': # List of things to be updated. Each entry is a triplet of args for # target_update() to_update = [ - ('docs/man/ipython.1.gz', - ['docs/man/ipython.1'], - 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'), - ] + ( + "docs/man/ipython.1.gz", + ["docs/man/ipython.1"], + "cd docs/man && python -m gzip --best ipython.1", + ), + ] [ target_update(*t) for t in to_update ] @@ -125,140 +77,25 @@ # Find all the packages, package data, and data_files #--------------------------------------------------------------------------- -packages = find_packages() -package_data = find_package_data() - data_files = find_data_files() -setup_args['packages'] = packages -setup_args['package_data'] = package_data setup_args['data_files'] = data_files #--------------------------------------------------------------------------- # custom distutils commands #--------------------------------------------------------------------------- # imports here, so they are after setuptools import if there was one -from distutils.command.sdist import sdist +from setuptools.command.sdist import sdist setup_args['cmdclass'] = { 'build_py': \ check_package_data_first(git_prebuild('IPython')), 'sdist' : git_prebuild('IPython', sdist), - 'symlink': install_symlinked, - 'install_lib_symlink': install_lib_symlink, - 'install_scripts_sym': install_scripts_for_symlink, - 'unsymlink': unsymlink, } - -#--------------------------------------------------------------------------- -# Handle scripts, dependencies, and setuptools specific things -#--------------------------------------------------------------------------- - -# For some commands, use setuptools. Note that we do NOT list install here! -# If you want a setuptools-enhanced install, just run 'setupegg.py install' -needs_setuptools = {'develop', 'release', 'bdist_egg', 'bdist_rpm', - 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel', - 'egg_info', 'easy_install', 'upload', 'install_egg_info', - } - -if len(needs_setuptools.intersection(sys.argv)) > 0: - import setuptools - -# This dict is used for passing extra arguments that are setuptools -# specific to setup -setuptools_extra_args = {} - -# setuptools requirements - -extras_require = dict( - parallel = ['ipyparallel'], - qtconsole = ['qtconsole'], - doc = ['Sphinx>=1.3'], - test = ['nose>=0.10.1', 'requests', 'testpath', 'pygments', 'nbformat', 'ipykernel', 'numpy'], - terminal = [], - kernel = ['ipykernel'], - nbformat = ['nbformat'], - notebook = ['notebook', 'ipywidgets'], - nbconvert = ['nbconvert'], -) - -install_requires = [ - 'setuptools>=18.5', - 'jedi>=0.10', - 'decorator', - 'pickleshare', - 'simplegeneric>0.8', - 'traitlets>=4.2', - 'prompt_toolkit>=2.0.0,<2.1.0', - 'pygments', - 'backcall', -] - -# Platform-specific dependencies: -# This is the correct way to specify these, -# but requires pip >= 6. pip < 6 ignores these. - -extras_require.update({ - ':python_version == "3.4"': ['typing'], - ':sys_platform != "win32"': ['pexpect'], - ':sys_platform == "darwin"': ['appnope'], - ':sys_platform == "win32"': ['colorama'], - ':sys_platform == "win32" and python_version < "3.6"': ['win_unicode_console>=0.5'], -}) -# FIXME: re-specify above platform dependencies for pip < 6 -# These would result in non-portable bdists. -if not any(arg.startswith('bdist') for arg in sys.argv): - if sys.platform == 'darwin': - install_requires.extend(['appnope']) - - if not sys.platform.startswith('win'): - install_requires.append('pexpect') - - # workaround pypa/setuptools#147, where setuptools misspells - # platform_python_implementation as python_implementation - if 'setuptools' in sys.modules: - for key in list(extras_require): - if 'platform_python_implementation' in key: - new_key = key.replace('platform_python_implementation', 'python_implementation') - extras_require[new_key] = extras_require.pop(key) - -everything = set() -for key, deps in extras_require.items(): - if ':' not in key: - everything.update(deps) -extras_require['all'] = everything - -if 'setuptools' in sys.modules: - setuptools_extra_args['python_requires'] = '>=3.5' - setuptools_extra_args['zip_safe'] = False - setuptools_extra_args['entry_points'] = { - 'console_scripts': find_entry_points(), - 'pygments.lexers': [ - 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer', - 'ipython = IPython.lib.lexers:IPythonLexer', - 'ipython3 = IPython.lib.lexers:IPython3Lexer', - ], - } - setup_args['extras_require'] = extras_require - setup_args['install_requires'] = install_requires - -else: - # scripts has to be a non-empty list, or install_scripts isn't called - setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()] - - setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt - #--------------------------------------------------------------------------- # Do the actual setup now #--------------------------------------------------------------------------- -setup_args.update(setuptools_extra_args) - - - -def main(): +if __name__ == "__main__": setup(**setup_args) - -if __name__ == '__main__': - main() diff --git a/setupbase.py b/setupbase.py index 9460f94278d..17b4dd0fe0f 100644 --- a/setupbase.py +++ b/setupbase.py @@ -12,135 +12,67 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - -import re import os +from pathlib import Path +import re import sys - -from distutils import log -from distutils.command.build_py import build_py -from distutils.command.build_scripts import build_scripts -from distutils.command.install import install -from distutils.command.install_scripts import install_scripts -from distutils.cmd import Command from glob import glob +from logging import log + +from setuptools import Command +from setuptools.command.build_py import build_py + +from setuptools.command.install import install +from setuptools.command.install_scripts import install_scripts -from setupext import install_data_ext #------------------------------------------------------------------------------- # Useful globals and utility functions #------------------------------------------------------------------------------- # A few handy globals -isfile = os.path.isfile -pjoin = os.path.join -repo_root = os.path.dirname(os.path.abspath(__file__)) +repo_root = Path(__file__).resolve().parent -def execfile(fname, globs, locs=None): +def execfile(path, globs, locs=None): locs = locs or globs - exec(compile(open(fname).read(), fname, "exec"), globs, locs) - -# A little utility we'll need below, since glob() does NOT allow you to do -# exclusion on multiple endings! -def file_doesnt_endwith(test,endings): - """Return true if test is a file and its name does NOT end with any - of the strings listed in endings.""" - if not isfile(test): - return False - for e in endings: - if test.endswith(e): - return False - return True + with path.open(encoding="utf-8") as f: + exec(compile(f.read(), str(path), "exec"), globs, locs) #--------------------------------------------------------------------------- # Basic project information #--------------------------------------------------------------------------- # release.py contains version, authors, license, url, keywords, etc. -execfile(pjoin(repo_root, 'IPython','core','release.py'), globals()) +execfile(Path(repo_root, "IPython", "core", "release.py"), globals()) # Create a dict with the basic information # This dict is eventually passed to setup after additional keys are added. setup_args = dict( - name = name, - version = version, - description = description, - long_description = long_description, author = author, author_email = author_email, - url = url, license = license, - platforms = platforms, - keywords = keywords, - classifiers = classifiers, - cmdclass = {'install_data': install_data_ext}, - project_urls={ - 'Documentation': 'https://ipython.readthedocs.io/', - 'Funding' : 'https://numfocus.org/', - 'Source' : 'https://github.com/ipython/ipython', - 'Tracker' : 'https://github.com/ipython/ipython/issues', - } ) - #--------------------------------------------------------------------------- -# Find packages +# Check package data #--------------------------------------------------------------------------- -def find_packages(): - """ - Find all of IPython's packages. - """ - excludes = ['deathrow', 'quarantine'] - packages = [] - for dir,subdirs,files in os.walk('IPython'): - package = dir.replace(os.path.sep, '.') - if any(package.startswith('IPython.'+exc) for exc in excludes): - # package is to be excluded (e.g. deathrow) - continue - if '__init__.py' not in files: - # not a package - continue - packages.append(package) - return packages - -#--------------------------------------------------------------------------- -# Find package data -#--------------------------------------------------------------------------- - -def find_package_data(): - """ - Find IPython's package_data. - """ - # This is not enough for these things to appear in an sdist. - # We need to muck with the MANIFEST to get this to work - - package_data = { - 'IPython.core' : ['profile/README*'], - 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'], - 'IPython.lib.tests' : ['*.wav'], - 'IPython.testing.plugin' : ['*.txt'], - } - - return package_data - - def check_package_data(package_data): """verify that package_data globs make sense""" print("checking package data") for pkg, data in package_data.items(): - pkg_root = pjoin(*pkg.split('.')) + pkg_root = Path(*pkg.split(".")) for d in data: - path = pjoin(pkg_root, d) - if '*' in path: - assert len(glob(path)) > 0, "No files match pattern %s" % path + path = pkg_root / d + if "*" in str(path): + assert len(glob(str(path))) > 0, "No files match pattern %s" % path else: - assert os.path.exists(path), "Missing package data: %s" % path + assert path.exists(), f"Missing package data: {path}" def check_package_data_first(command): """decorator for checking package_data before running a given command - + Probably only needs to wrap build_py """ class DecoratedCommand(command): @@ -161,24 +93,27 @@ def find_data_files(): Just man pages at this point. """ - manpagebase = pjoin('share', 'man', 'man1') + if "freebsd" in sys.platform: + manpagebase = Path("man") / "man1" + else: + manpagebase = Path("share") / "man" / "man1" # Simple file lists can be made by hand - manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)] + manpages = [f for f in Path("docs/man").glob("*.1.gz") if f.is_file()] if not manpages: # When running from a source tree, the manpages aren't gzipped - manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)] + manpages = [f for f in Path("docs/man").glob("*.1") if f.is_file()] # And assemble the entire output list - data_files = [ (manpagebase, manpages) ] + data_files = [(str(manpagebase), [str(f) for f in manpages])] return data_files # The two functions below are copied from IPython.utils.path, so we don't need -# to import IPython during setup, which fails on Python 3. +# to import IPython (and hence its dependencies) during setup. -def target_outdated(target,deps): +def target_outdated(target, deps): """Determine whether a target is out of date. target_outdated(target,deps) -> 1/0 @@ -190,19 +125,19 @@ def target_outdated(target,deps): true, otherwise return false. """ try: - target_time = os.path.getmtime(target) - except os.error: + target_time = Path(target).stat().st_mtime + except FileNotFoundError: return 1 for dep in deps: - dep_time = os.path.getmtime(dep) + dep_time = Path(dep).stat().st_mtime if dep_time > target_time: - #print "For target",target,"Dep failed:",dep # dbg - #print "times (dep,tar):",dep_time,target_time # dbg + # print("For target",target,"Dep failed:",dep) # dbg + # print("times (dep,tar):",dep_time,target_time) # dbg return 1 return 0 -def target_update(target,deps,cmd): +def target_update(target, deps, cmd): """Update a target with a given command given a list of dependencies. target_update(target,deps,cmd) -> runs cmd if target is outdated. @@ -210,155 +145,27 @@ def target_update(target,deps,cmd): This is just a wrapper around target_outdated() which calls the given command if target is outdated.""" - if target_outdated(target,deps): + if target_outdated(target, deps): os.system(cmd) -#--------------------------------------------------------------------------- -# Find scripts -#--------------------------------------------------------------------------- - -def find_entry_points(): - """Defines the command line entry points for IPython - - This always uses setuptools-style entry points. When setuptools is not in - use, our own build_scripts_entrypt class below parses these and builds - command line scripts. - - Each of our entry points gets both a plain name, e.g. ipython, and one - suffixed with the Python major version number, e.g. ipython3. - """ - ep = [ - 'ipython%s = IPython:start_ipython', - 'iptest%s = IPython.testing.iptestcontroller:main', - ] - suffix = str(sys.version_info[0]) - return [e % '' for e in ep] + [e % suffix for e in ep] - -script_src = """#!{executable} -# This script was automatically generated by setup.py -if __name__ == '__main__': - from {mod} import {func} - {func}() -""" - -class build_scripts_entrypt(build_scripts): - """Build the command line scripts - - Parse setuptools style entry points and write simple scripts to run the - target functions. - - On Windows, this also creates .cmd wrappers for the scripts so that you can - easily launch them from a command line. - """ - def run(self): - self.mkpath(self.build_dir) - outfiles = [] - for script in find_entry_points(): - name, entrypt = script.split('=') - name = name.strip() - entrypt = entrypt.strip() - outfile = os.path.join(self.build_dir, name) - outfiles.append(outfile) - print('Writing script to', outfile) - - mod, func = entrypt.split(':') - with open(outfile, 'w') as f: - f.write(script_src.format(executable=sys.executable, - mod=mod, func=func)) - - if sys.platform == 'win32': - # Write .cmd wrappers for Windows so 'ipython' etc. work at the - # command line - cmd_file = os.path.join(self.build_dir, name + '.cmd') - cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format( - python=sys.executable, script=name) - log.info("Writing %s wrapper script" % cmd_file) - with open(cmd_file, 'w') as f: - f.write(cmd) - - return outfiles, outfiles - -class install_lib_symlink(Command): - user_options = [ - ('install-dir=', 'd', "directory to install to"), - ] - - def initialize_options(self): - self.install_dir = None - - def finalize_options(self): - self.set_undefined_options('symlink', - ('install_lib', 'install_dir'), - ) - - def run(self): - if sys.platform == 'win32': - raise Exception("This doesn't work on Windows.") - pkg = os.path.join(os.getcwd(), 'IPython') - dest = os.path.join(self.install_dir, 'IPython') - if os.path.islink(dest): - print('removing existing symlink at %s' % dest) - os.unlink(dest) - print('symlinking %s -> %s' % (pkg, dest)) - os.symlink(pkg, dest) - -class unsymlink(install): - def run(self): - dest = os.path.join(self.install_lib, 'IPython') - if os.path.islink(dest): - print('removing symlink at %s' % dest) - os.unlink(dest) - else: - print('No symlink exists at %s' % dest) - -class install_symlinked(install): - def run(self): - if sys.platform == 'win32': - raise Exception("This doesn't work on Windows.") - - # Run all sub-commands (at least those that need to be run) - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - - # 'sub_commands': a list of commands this command might have to run to - # get its work done. See cmd.py for more info. - sub_commands = [('install_lib_symlink', lambda self:True), - ('install_scripts_sym', lambda self:True), - ] - -class install_scripts_for_symlink(install_scripts): - """Redefined to get options from 'symlink' instead of 'install'. - - I love distutils almost as much as I love setuptools. - """ - def finalize_options(self): - self.set_undefined_options('build', ('build_scripts', 'build_dir')) - self.set_undefined_options('symlink', - ('install_scripts', 'install_dir'), - ('force', 'force'), - ('skip_build', 'skip_build'), - ) - - #--------------------------------------------------------------------------- # VCS related #--------------------------------------------------------------------------- - def git_prebuild(pkg_dir, build_cmd=build_py): """Return extended build or sdist command class for recording commit - + records git commit in IPython.utils._sysinfo.commit - + for use in IPython.utils.sysinfo.sys_info() calls after installation. """ - + class MyBuildPy(build_cmd): ''' Subclass to write commit data into installation tree ''' def run(self): # loose as `.dev` is suppose to be invalid print("check version number") - loose_pep440re = re.compile('^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$') + loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$') if not loose_pep440re.match(version): raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version) @@ -367,12 +174,12 @@ def run(self): # this one will only fire for build commands if hasattr(self, 'build_lib'): self._record_commit(self.build_lib) - + def make_release_tree(self, base_dir, files): # this one will fire for sdist build_cmd.make_release_tree(self, base_dir, files) self._record_commit(base_dir) - + def _record_commit(self, base_dir): import subprocess proc = subprocess.Popen('git rev-parse --short HEAD', @@ -381,23 +188,25 @@ def _record_commit(self, base_dir): shell=True) repo_commit, _ = proc.communicate() repo_commit = repo_commit.strip().decode("ascii") - - out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py') - if os.path.isfile(out_pth) and not repo_commit: + + out_pth = Path(base_dir) / pkg_dir / "utils" / "_sysinfo.py" + if out_pth.is_file() and not repo_commit: # nothing to write, don't clobber return - - print("writing git commit '%s' to %s" % (repo_commit, out_pth)) - + + print(f"writing git commit '{repo_commit}' to {out_pth}") + # remove to avoid overwriting original via hard link try: - os.remove(out_pth) - except (IOError, OSError): + out_pth.unlink() + except FileNotFoundError: pass - with open(out_pth, 'w') as out_file: - out_file.writelines([ - '# GENERATED BY setup.py\n', - 'commit = u"%s"\n' % repo_commit, - ]) - return MyBuildPy + with out_pth.open("w", encoding="utf-8") as out_file: + out_file.writelines( + [ + "# GENERATED BY setup.py\n", + f'commit = "{repo_commit}"\n', + ] + ) + return MyBuildPy diff --git a/setupegg.py b/setupegg.py deleted file mode 100755 index 7b4b5687e4b..00000000000 --- a/setupegg.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python -"""Wrapper to run setup.py using setuptools.""" - -# Import setuptools and call the actual setup -import setuptools -with open('setup.py', 'rb') as f: - exec(compile(f.read(), 'setup.py', 'exec')) diff --git a/setupext/__init__.py b/setupext/__init__.py deleted file mode 100644 index c8ce0378e7f..00000000000 --- a/setupext/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# load extended setup modules for distuils - -from .install_data_ext import install_data_ext diff --git a/setupext/install_data_ext.py b/setupext/install_data_ext.py deleted file mode 100644 index ce6623b34f1..00000000000 --- a/setupext/install_data_ext.py +++ /dev/null @@ -1,80 +0,0 @@ -# install_data_ext.py -# -# Subclass of normal distutils install_data command to allow more -# configurable installation of data files. - -import os -from distutils.command.install_data import install_data -from distutils.util import change_root, convert_path - -class install_data_ext(install_data): - - def initialize_options(self): - self.install_base = None - self.install_platbase = None - self.install_purelib = None - self.install_headers = None - self.install_lib = None - self.install_scripts = None - self.install_data = None - - self.outfiles = [] - self.root = None - self.force = 0 - self.data_files = self.distribution.data_files - self.warn_dir = 1 - - - def finalize_options(self): - self.set_undefined_options('install', - ('root', 'root'), - ('force', 'force'), - ('install_base', 'install_base'), - ('install_platbase', - 'install_platbase'), - ('install_purelib', - 'install_purelib'), - ('install_headers', - 'install_headers'), - ('install_lib', 'install_lib'), - ('install_scripts', - 'install_scripts'), - ('install_data', 'install_data')) - - - def run(self): - """ - This is where the meat is. Basically the data_files list must - now be a list of tuples of 3 entries. The first - entry is one of 'base', 'platbase', etc, which indicates which - base to install from. The second entry is the path to install - too. The third entry is a list of files to install. - """ - for lof in self.data_files: - if lof[0]: - base = getattr(self, 'install_' + lof[0]) - else: - base = getattr(self, 'install_base') - dir = convert_path(lof[1]) - if not os.path.isabs(dir): - dir = os.path.join(base, dir) - elif self.root: - dir = change_root(self.root, dir) - self.mkpath(dir) - - files = lof[2] - if len(files) == 0: - # If there are no files listed, the user must be - # trying to create an empty directory, so add the - # directory to the list of output files. - self.outfiles.append(dir) - else: - # Copy files, adding them to the list of output files. - for f in files: - f = convert_path(f) - (out, _) = self.copy_file(f, dir) - #print "DEBUG: ", out # dbg - self.outfiles.append(out) - - - return self.outfiles diff --git a/IPython/core/tests/2x2.jpg b/tests/2x2.jpg similarity index 100% rename from IPython/core/tests/2x2.jpg rename to tests/2x2.jpg diff --git a/IPython/core/tests/2x2.png b/tests/2x2.png similarity index 100% rename from IPython/core/tests/2x2.png rename to tests/2x2.png diff --git a/IPython/utils/tests/__init__.py b/tests/__init__.py similarity index 100% rename from IPython/utils/tests/__init__.py rename to tests/__init__.py diff --git a/IPython/core/tests/bad_all.py b/tests/bad_all.py similarity index 65% rename from IPython/core/tests/bad_all.py rename to tests/bad_all.py index a7716ab6f32..0d50bf01ffe 100644 --- a/IPython/core/tests/bad_all.py +++ b/tests/bad_all.py @@ -3,12 +3,16 @@ To test https://github.com/ipython/ipython/issues/9678 """ + def evil(): pass + def puppies(): pass -__all__ = [evil, # Bad - 'puppies', # Good - ] + +__all__ = [ + evil, # Bad + "puppies", # Good +] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000000..6368d342804 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,81 @@ +import builtins +import inspect +import os +import pathlib +import shutil +import sys +import types + +import pytest + +# Must register before it gets imported +pytest.register_assert_rewrite("IPython.testing.tools") + +from IPython.testing import tools + + +def get_ipython(): + from IPython.terminal.interactiveshell import TerminalInteractiveShell + + if TerminalInteractiveShell._instance: + return TerminalInteractiveShell.instance() + + config = tools.default_config() + config.TerminalInteractiveShell.simple_prompt = True + + # Create and initialize our test-friendly IPython instance. + shell = TerminalInteractiveShell.instance(config=config) + return shell + + +@pytest.fixture(scope="session", autouse=True) +def work_path(): + path = pathlib.Path("./tmp-ipython-pytest-profiledir") + os.environ["IPYTHONDIR"] = str(path.absolute()) + if path.exists(): + raise ValueError( + "IPython dir temporary path already exists ! Did previous test run exit successfully ?" + ) + path.mkdir() + yield + shutil.rmtree(str(path.resolve())) + + +def nopage(strng, start=0, screen_lines=0, pager_cmd=None): + if isinstance(strng, dict): + strng = strng.get("text/plain", "") + print(strng) + + +def xsys(self, cmd): + """Replace the default system call with a capturing one for doctest.""" + # We use getoutput, but we need to strip it because pexpect captures + # the trailing newline differently from commands.getoutput + print(self.getoutput(cmd, split=False, depth=1).rstrip(), end="", file=sys.stdout) + sys.stdout.flush() + + +# for things to work correctly we would need this as a session fixture; +# unfortunately this will fail on some test that get executed as _collection_ +# time (before the fixture run), in particular parametrized test that contain +# yields. so for now execute at import time. +# @pytest.fixture(autouse=True, scope='session') +def inject(): + builtins.get_ipython = get_ipython + builtins._ip = get_ipython() + builtins.ip = get_ipython() + builtins.ip.system = types.MethodType(xsys, ip) + builtins.ip.builtin_trap.activate() + from IPython.core import page + + page.pager_page = nopage + + from IPython.core.history import HistoryManager + + # ensure we don't leak History managers + if os.name != "nt": + HistoryManager._max_inst = 1 + # yield + + +inject() diff --git a/IPython/core/tests/daft_extension/daft_extension.py b/tests/fake_ext_dir/daft_extension.py similarity index 90% rename from IPython/core/tests/daft_extension/daft_extension.py rename to tests/fake_ext_dir/daft_extension.py index 37fefe31459..415e136d4e9 100644 --- a/IPython/core/tests/daft_extension/daft_extension.py +++ b/tests/fake_ext_dir/daft_extension.py @@ -2,11 +2,13 @@ """ Useless IPython extension to test installing and loading extensions. """ -some_vars = {'arq': 185} +some_vars = {"arq": 185} + def load_ipython_extension(ip): # set up simplified quantity input ip.push(some_vars) + def unload_ipython_extension(ip): ip.drop_by_id(some_vars) diff --git a/tests/fake_llm.py b/tests/fake_llm.py new file mode 100644 index 00000000000..ceacc5a8ea5 --- /dev/null +++ b/tests/fake_llm.py @@ -0,0 +1,102 @@ +import asyncio +from time import sleep + +try: + from jupyter_ai_magics.providers import BaseProvider + from langchain_community.llms import FakeListLLM +except ImportError: + + class BaseProvider: + pass + + class FakeListLLM: + pass + + +FIBONACCI = """\ +def fib(n): + if n < 2: return n + return fib(n - 1) + fib(n - 2) +""" + + +class FibonacciCompletionProvider(BaseProvider, FakeListLLM): # type: ignore[misc, valid-type] + id = "my_provider" + name = "My Provider" + model_id_key = "model" + models = ["model_a"] + + def __init__(self, **kwargs): + kwargs["responses"] = ["This fake response will not be used for completion"] + kwargs["model_id"] = "model_a" + super().__init__(**kwargs) + + async def generate_inline_completions(self, request): + raise ValueError("IPython only supports streaming models.") + + async def stream_inline_completions(self, request): + from jupyter_ai.completions.models import ( + InlineCompletionList, + InlineCompletionReply, + ) + + assert request.number > 0 + token = f"t{request.number}s0" + last_line = request.prefix.splitlines()[-1] + + if not FIBONACCI.startswith(last_line): + return + + yield InlineCompletionReply( + list=InlineCompletionList( + items=[ + {"insertText": "", "isIncomplete": True, "token": token}, + ] + ), + reply_to=request.number, + ) + + async for reply in self._stream( + FIBONACCI[len(last_line) :], + request.number, + token, + ): + yield reply + + async def _stream(self, sentence, request_number, token, start_with=""): + from jupyter_ai.completions.models import InlineCompletionStreamChunk + + suggestion = start_with + + for fragment in sentence.split(" "): + await asyncio.sleep(0.05) + if suggestion: + suggestion += " " + suggestion += fragment + yield InlineCompletionStreamChunk( + type="stream", + response={"insertText": suggestion, "token": token}, + reply_to=request_number, + done=False, + ) + + # finally, send a message confirming that we are done + yield InlineCompletionStreamChunk( + type="stream", + response={"insertText": suggestion, "token": token}, + reply_to=request_number, + done=True, + ) + + +class SlowStartingCompletionProvider(BaseProvider, FakeListLLM): # type: ignore[misc, valid-type] + id = "slow_provider" + name = "Slow Provider" + model_id_key = "model" + models = ["model_a"] + + def __init__(self, **kwargs): + kwargs["responses"] = ["This fake response will be used for completion"] + kwargs["model_id"] = "model_a" + sleep(10) + super().__init__(**kwargs) diff --git a/tests/hill-alpha.webp b/tests/hill-alpha.webp new file mode 100644 index 00000000000..21b78222374 Binary files /dev/null and b/tests/hill-alpha.webp differ diff --git a/tests/hill-animated.webp b/tests/hill-animated.webp new file mode 100644 index 00000000000..26b44ad9c40 Binary files /dev/null and b/tests/hill-animated.webp differ diff --git a/tests/hill-lossless.webp b/tests/hill-lossless.webp new file mode 100644 index 00000000000..6dbe51780e3 Binary files /dev/null and b/tests/hill-lossless.webp differ diff --git a/tests/hill-lossy.webp b/tests/hill-lossy.webp new file mode 100644 index 00000000000..8482f7d929b Binary files /dev/null and b/tests/hill-lossy.webp differ diff --git a/IPython/core/tests/nonascii.py b/tests/nonascii.py similarity index 91% rename from IPython/core/tests/nonascii.py rename to tests/nonascii.py index 78801dfd093..12738e3adc2 100644 --- a/IPython/core/tests/nonascii.py +++ b/tests/nonascii.py @@ -1,4 +1,4 @@ # coding: iso-8859-5 # (Unlikely to be the default encoding for most testers.) # ±¶ÿàáâãäåæçèéêëìíîï <- Cyrillic characters -u = '®âðÄ' +u = "®âðÄ" diff --git a/IPython/core/tests/nonascii2.py b/tests/nonascii2.py similarity index 50% rename from IPython/core/tests/nonascii2.py rename to tests/nonascii2.py index a1afce4f3ef..b87ceb0a1b8 100644 --- a/IPython/core/tests/nonascii2.py +++ b/tests/nonascii2.py @@ -1,4 +1,4 @@ # coding: iso-8859-5 # (Unlikely to be the default encoding for most testers.) -# БЖџрстуфхцчшщъыьэюя <- Cyrillic characters -'Ўт№Ф' +# ±¶ÿàáâãäåæçèéêëìíîï <- Cyrillic characters +"®âðÄ" diff --git a/IPython/core/tests/print_argv.py b/tests/print_argv.py similarity index 96% rename from IPython/core/tests/print_argv.py rename to tests/print_argv.py index 0e92bddcb06..4ec9e2799ed 100644 --- a/IPython/core/tests/print_argv.py +++ b/tests/print_argv.py @@ -1,2 +1,3 @@ import sys + print(sys.argv[1:]) diff --git a/IPython/core/tests/refbug.py b/tests/refbug.py similarity index 63% rename from IPython/core/tests/refbug.py rename to tests/refbug.py index b8de4c81078..07aa6acdc99 100644 --- a/IPython/core/tests/refbug.py +++ b/tests/refbug.py @@ -13,35 +13,33 @@ test_run.py calls it. """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Module imports -#----------------------------------------------------------------------------- -import sys +# ----------------------------------------------------------------------------- from IPython import get_ipython -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Globals -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # This needs to be here because nose and other test runners will import # this module. Importing this module has potential side effects that we # want to prevent. -if __name__ == '__main__': - +if __name__ == "__main__": ip = get_ipython() - if not '_refbug_cache' in ip.user_ns: - ip.user_ns['_refbug_cache'] = [] + if not "_refbug_cache" in ip.user_ns: + ip.user_ns["_refbug_cache"] = [] + aglobal = "Hello" - aglobal = 'Hello' def f(): return aglobal - cache = ip.user_ns['_refbug_cache'] + cache = ip.user_ns["_refbug_cache"] cache.append(f) def call_f(): for func in cache: - print('lowercased:',func().lower()) + print("lowercased:", func().lower()) diff --git a/IPython/core/tests/simpleerr.py b/tests/simpleerr.py similarity index 66% rename from IPython/core/tests/simpleerr.py rename to tests/simpleerr.py index 34e697029db..28464c329d2 100644 --- a/IPython/core/tests/simpleerr.py +++ b/tests/simpleerr.py @@ -1,32 +1,37 @@ """Error script. DO NOT EDIT FURTHER! It will break exception doctests!!!""" + import sys + def div0(): "foo" x = 1 y = 0 - x/y + x / y + def sysexit(stat, mode): - raise SystemExit(stat, 'Mode = %s' % mode) + raise SystemExit(stat, f"Mode = {mode}") + def bar(mode): "bar" - if mode=='div': + if mode == "div": div0() - elif mode=='exit': + elif mode == "exit": try: stat = int(sys.argv[2]) except: stat = 1 sysexit(stat, mode) else: - raise ValueError('Unknown mode') + raise ValueError("Unknown mode") + -if __name__ == '__main__': +if __name__ == "__main__": try: mode = sys.argv[1] except IndexError: - mode = 'div' + mode = "div" bar(mode) diff --git a/IPython/core/tests/tclass.py b/tests/tclass.py similarity index 78% rename from IPython/core/tests/tclass.py rename to tests/tclass.py index 6bd9ffcd9a0..5160e171b23 100644 --- a/IPython/core/tests/tclass.py +++ b/tests/tclass.py @@ -2,33 +2,34 @@ See test_run for details.""" - import sys + # We want to ensure that while objects remain available for immediate access, # objects from *previous* runs of the same script get collected, to avoid # accumulating massive amounts of old references. class C(object): - def __init__(self,name): + def __init__(self, name): self.name = name self.p = print self.flush_stdout = sys.stdout.flush - + def __del__(self): - self.p('tclass.py: deleting object:',self.name) + self.p("tclass.py: deleting object:", self.name) self.flush_stdout() + try: name = sys.argv[1] except IndexError: pass else: - if name.startswith('C'): + if name.startswith("C"): c = C(name) -#print >> sys.stderr, "ARGV:", sys.argv # dbg +# print("ARGV:", sys.argv, file=sys.stderr) # dbg # This next print statement is NOT debugging, we're making the check on a # completely separate process so we verify by capturing stdout: -print('ARGV 1-:', sys.argv[1:]) +print("ARGV 1-:", sys.argv[1:]) sys.stdout.flush() diff --git a/IPython/lib/tests/test.wav b/tests/test.wav similarity index 100% rename from IPython/lib/tests/test.wav rename to tests/test.wav diff --git a/IPython/core/tests/test_alias.py b/tests/test_alias.py similarity index 54% rename from IPython/core/tests/test_alias.py rename to tests/test_alias.py index 7417e95513c..c92aa839460 100644 --- a/IPython/core/tests/test_alias.py +++ b/tests/test_alias.py @@ -1,62 +1,68 @@ from IPython.utils.capture import capture_output -import nose.tools as nt +import pytest + def test_alias_lifecycle(): - name = 'test_alias1' + name = "test_alias1" cmd = 'echo "Hello"' am = _ip.alias_manager am.clear_aliases() am.define_alias(name, cmd) assert am.is_alias(name) - nt.assert_equal(am.retrieve_alias(name), cmd) - nt.assert_in((name, cmd), am.aliases) - + assert am.retrieve_alias(name) == cmd + assert (name, cmd) in am.aliases + # Test running the alias orig_system = _ip.system result = [] _ip.system = result.append try: - _ip.run_cell('%{}'.format(name)) + _ip.run_cell("%{}".format(name)) result = [c.strip() for c in result] - nt.assert_equal(result, [cmd]) + assert result == [cmd] finally: _ip.system = orig_system - + # Test removing the alias am.undefine_alias(name) assert not am.is_alias(name) - with nt.assert_raises(ValueError): + with pytest.raises(ValueError): am.retrieve_alias(name) - nt.assert_not_in((name, cmd), am.aliases) - + assert (name, cmd) not in am.aliases + def test_alias_args_error(): """Error expanding with wrong number of arguments""" - _ip.alias_manager.define_alias('parts', 'echo first %s second %s') + _ip.alias_manager.define_alias("parts", "echo first %s second %s") # capture stderr: with capture_output() as cap: - _ip.run_cell('parts 1') + _ip.run_cell("parts 1") + + assert cap.stderr.split(":")[0] == "UsageError" + - nt.assert_equal(cap.stderr.split(':')[0], 'UsageError') - def test_alias_args_commented(): """Check that alias correctly ignores 'commented out' args""" - _ip.magic('alias commetarg echo this is %%s a commented out arg') - + _ip.run_line_magic("alias", "commentarg echo this is %%s a commented out arg") + with capture_output() as cap: - _ip.run_cell('commetarg') - - nt.assert_equal(cap.stdout, 'this is %s a commented out arg') + _ip.run_cell("commentarg") + + # strip() is for pytest compat; testing via iptest patch IPython shell + # in testing.globalipapp and replace the system call which messed up the + # \r\n + assert cap.stdout.strip() == "this is %s a commented out arg" + def test_alias_args_commented_nargs(): """Check that alias correctly counts args, excluding those commented out""" am = _ip.alias_manager - alias_name = 'comargcount' - cmd = 'echo this is %%s a commented out arg and this is not %s' - + alias_name = "comargcount" + cmd = "echo this is %%s a commented out arg and this is not %s" + am.define_alias(alias_name, cmd) assert am.is_alias(alias_name) - + thealias = am.get_alias(alias_name) - nt.assert_equal(thealias.nargs, 1) \ No newline at end of file + assert thealias.nargs == 1 diff --git a/tests/test_application.py b/tests/test_application.py new file mode 100644 index 00000000000..04f483122e3 --- /dev/null +++ b/tests/test_application.py @@ -0,0 +1,392 @@ +# coding: utf-8 +"""Tests for IPython.core.application""" + +import os +import sys +import tempfile + +from tempfile import TemporaryDirectory + +import pytest +from traitlets import Unicode +from traitlets.config import Config + +from IPython.core.application import ( + LOGGING_CRITICAL, + LOGGING_DEBUG, + BaseIPythonApplication, + base_flags, +) +from IPython.core.profiledir import ProfileDir +from IPython.testing import decorators as dec + + +def test_logging_level_constants(): + """The inlined logging levels must not drift from the `logging` ones. + + `IPython.core.application` spells these out rather than importing + `logging` at startup; this is the check that keeps the copies honest. + """ + import logging + + assert LOGGING_DEBUG == logging.DEBUG + assert LOGGING_CRITICAL == logging.CRITICAL + assert base_flags["debug"][0]["Application"]["log_level"] == logging.DEBUG + assert base_flags["quiet"][0]["Application"]["log_level"] == logging.CRITICAL + + +@pytest.fixture +def preserve_excepthook(): + """initialize() installs a crash handler; restore sys.excepthook afterwards.""" + orig = sys.excepthook + yield + sys.excepthook = orig + + +def _clear_singletons(app): + """Unregister singleton instances created by the subcommand machinery.""" + from traitlets.config.configurable import SingletonConfigurable + + while app is not None: + for klass in type(app).__mro__: + if klass is SingletonConfigurable: + break + if getattr(klass, "_instance", None) is app: + klass._instance = None + app = getattr(app, "subapp", None) + + +@dec.onlyif_unicode_paths +def test_unicode_cwd(): + """Check that IPython starts with non-ascii characters in the path.""" + wd = tempfile.mkdtemp(suffix="€") + + old_wd = os.getcwd() + os.chdir(wd) + # raise Exception(repr(os.getcwd())) + try: + app = BaseIPythonApplication() + # The lines below are copied from Application.initialize() + app.init_profile_dir() + app.init_config_files() + app.load_config_file(suppress_errors=False) + finally: + os.chdir(old_wd) + + +@dec.onlyif_unicode_paths +def test_unicode_ipdir(): + """Check that IPython starts with non-ascii characters in the IP dir.""" + ipdir = tempfile.mkdtemp(suffix="€") + + # Create the config file, so it tries to load it. + with open(os.path.join(ipdir, "ipython_config.py"), "w", encoding="utf-8") as f: + pass + + old_ipdir1 = os.environ.pop("IPYTHONDIR", None) + old_ipdir2 = os.environ.pop("IPYTHON_DIR", None) + os.environ["IPYTHONDIR"] = ipdir + try: + app = BaseIPythonApplication() + # The lines below are copied from Application.initialize() + app.init_profile_dir() + app.init_config_files() + app.load_config_file(suppress_errors=False) + finally: + if old_ipdir1: + os.environ["IPYTHONDIR"] = old_ipdir1 + if old_ipdir2: + os.environ["IPYTHONDIR"] = old_ipdir2 + + +def test_cli_priority(): + with TemporaryDirectory() as td: + + class TestApp(BaseIPythonApplication): + test = Unicode().tag(config=True) + + # Create the config file, so it tries to load it. + with open(os.path.join(td, "ipython_config.py"), "w", encoding="utf-8") as f: + f.write("c.TestApp.test = 'config file'") + + app = TestApp() + app.initialize(["--profile-dir", td]) + assert app.test == "config file" + app = TestApp() + app.initialize(["--profile-dir", td, "--TestApp.test=cli"]) + assert app.test == "cli" + + +def test_extra_config_file(tmp_path, preserve_excepthook): + """--config loads an extra config file on top of the profile config.""" + + class ConfApp(BaseIPythonApplication): + test = Unicode().tag(config=True) + + profile_dir = tmp_path / "profile" + profile_dir.mkdir() + extra = tmp_path / "extra_config.py" + extra.write_text("c.ConfApp.test = 'extra'", encoding="utf-8") + + app = ConfApp() + app.initialize(["--profile-dir", str(profile_dir), "--config", str(extra)]) + assert app.extra_config_file == str(extra) + assert str(extra) in app.config_files + assert app.test == "extra" + + +def test_extra_config_file_missing(tmp_path, preserve_excepthook): + """A missing --config file is only warned about, not fatal.""" + profile_dir = tmp_path / "profile" + profile_dir.mkdir() + missing = tmp_path / "nonexistent_config.py" + app = BaseIPythonApplication() + app.initialize(["--profile-dir", str(profile_dir), "--config", str(missing)]) + assert app.extra_config_file == str(missing) + + +def test_bad_config_file(tmp_path, preserve_excepthook): + """Errors in config files are suppressed or raised per suppress_errors.""" + from traitlets.config import Application + + profile_dir = tmp_path / "profile" + profile_dir.mkdir() + bad = tmp_path / "bad_config.py" + bad.write_text("1/0", encoding="utf-8") + + app = BaseIPythonApplication() + # by default, errors in config files are logged, not raised + app.initialize(["--profile-dir", str(profile_dir), "--config", str(bad)]) + # make the underlying traitlets loader raise, to exercise IPython's + # suppress_errors handling + app.raise_config_file_errors = True + orig = Application.raise_config_file_errors + try: + # suppressed: only a warning is logged + app.load_config_file(suppress_errors=True) + # not suppressed: the error propagates + with pytest.raises(ZeroDivisionError): + app.load_config_file(suppress_errors=False) + finally: + # load_config_file toggles the class-level attribute and cannot + # restore it when the error propagates + Application.raise_config_file_errors = orig + + +def test_config_file_name_changed(): + app = BaseIPythonApplication() + app.config_file_name = "other_config.py" + assert "other_config.py" in app.config_file_specified + + +def test_profile_dir_autocreate_by_name(tmp_path, monkeypatch): + monkeypatch.setenv("IPYTHONDIR", str(tmp_path)) + app = BaseIPythonApplication(profile="fresh", auto_create=True) + app.init_profile_dir() + assert (tmp_path / "profile_fresh").is_dir() + assert app.profile_dir.location == str(tmp_path / "profile_fresh") + + +def test_profile_not_found_by_name(tmp_path, monkeypatch): + monkeypatch.setenv("IPYTHONDIR", str(tmp_path)) + app = BaseIPythonApplication(profile="nope") + with pytest.raises(SystemExit): + app.init_profile_dir() + + +def test_profile_dir_lazy_default(tmp_path, monkeypatch): + """Accessing profile_dir forces profile dir initialization.""" + monkeypatch.setenv("IPYTHONDIR", str(tmp_path)) + app = BaseIPythonApplication() + assert app.profile_dir.location == str(tmp_path / "profile_default") + # a second call is a no-op + app.init_profile_dir() + assert app.profile_dir.location == str(tmp_path / "profile_default") + + +def test_profile_create_by_name_fails(tmp_path, monkeypatch): + """exit(1) if the profile dir cannot be created.""" + from IPython.core.profiledir import ProfileDirError + + monkeypatch.setenv("IPYTHONDIR", str(tmp_path)) + + def raise_profile_dir_error(*args, **kwargs): + raise ProfileDirError("cannot create") + + monkeypatch.setattr( + ProfileDir, "create_profile_dir_by_name", raise_profile_dir_error + ) + app = BaseIPythonApplication(profile="fresh", auto_create=True) + with pytest.raises(SystemExit): + app.init_profile_dir() + + +def test_profile_create_by_location_fails(tmp_path, monkeypatch): + """exit(1) if the profile dir at an explicit location cannot be created.""" + from IPython.core.profiledir import ProfileDirError + + def raise_profile_dir_error(*args, **kwargs): + raise ProfileDirError("cannot create") + + monkeypatch.setattr(ProfileDir, "create_profile_dir", raise_profile_dir_error) + cfg = Config() + cfg.ProfileDir.location = str(tmp_path / "profile_xyz") + app = BaseIPythonApplication(config=cfg, auto_create=True) + with pytest.raises(SystemExit): + app.init_profile_dir() + + +def test_copy_config_files_from_builtin(tmp_path, monkeypatch): + """copy_config_files stages an existing builtin config file.""" + monkeypatch.setenv("IPYTHONDIR", str(tmp_path / "ip")) + builtin = tmp_path / "builtin" + builtin.mkdir() + (builtin / "ipython_config.py").write_text("# builtin config\n", encoding="utf-8") + + app = BaseIPythonApplication(copy_config_files=True) + app.builtin_profile_dir = str(builtin) + app.init_profile_dir() + app.init_config_files() + staged = tmp_path / "ip" / "profile_default" / "ipython_config.py" + assert staged.read_text(encoding="utf-8") == "# builtin config\n" + + +def test_stage_bundled_config_files(tmp_path, monkeypatch): + """bundled config files are staged even without copy_config_files.""" + monkeypatch.setenv("IPYTHONDIR", str(tmp_path / "ip")) + builtin = tmp_path / "builtin" + builtin.mkdir() + (builtin / "extra_bundled.py").write_text("# bundled\n", encoding="utf-8") + + app = BaseIPythonApplication() + app.builtin_profile_dir = str(builtin) + app.init_profile_dir() + app.init_config_files() + staged = tmp_path / "ip" / "profile_default" / "extra_bundled.py" + assert staged.read_text(encoding="utf-8") == "# bundled\n" + + +def test_profile_dir_autocreate_by_location(tmp_path): + location = tmp_path / "profile_xyz" + cfg = Config() + cfg.ProfileDir.location = str(location) + app = BaseIPythonApplication(config=cfg, auto_create=True) + app.init_profile_dir() + assert location.is_dir() + # profile name is derived from the directory name + assert app.profile == "xyz" + + +def test_profile_dir_location_not_found(tmp_path): + cfg = Config() + cfg.ProfileDir.location = str(tmp_path / "does_not_exist") + app = BaseIPythonApplication(config=cfg) + with pytest.raises(SystemExit): + app.init_profile_dir() + + +def test_add_ipython_dir_to_sys_path(tmp_path, monkeypatch): + monkeypatch.setenv("IPYTHONDIR", str(tmp_path / "default")) + app = BaseIPythonApplication(add_ipython_dir_to_sys_path=True) + ipdir = tmp_path / "ipdir" + try: + app.ipython_dir = str(ipdir) + assert os.path.abspath(str(ipdir)) in sys.path + assert (ipdir / "extensions").is_dir() + assert (ipdir / "nbextensions").is_dir() + + # changing the dir removes the old entry from sys.path + ipdir2 = tmp_path / "ipdir2" + app.ipython_dir = str(ipdir2) + assert os.path.abspath(str(ipdir)) not in sys.path + assert os.path.abspath(str(ipdir2)) in sys.path + finally: + for d in (tmp_path / "default", ipdir, tmp_path / "ipdir2"): + try: + sys.path.remove(os.path.abspath(str(d))) + except ValueError: + pass + + +def test_excepthook_lite(preserve_excepthook, capsys): + """without verbose_crash, the light crash handler prints a traceback.""" + app = BaseIPythonApplication() + app.init_crash_handler() + assert sys.excepthook == app.excepthook + try: + raise ValueError("some error message") + except ValueError: + app.excepthook(*sys.exc_info()) + err = capsys.readouterr().err + assert "some error message" in err + assert "https://github.com/ipython/ipython/issues" in err + + +def test_excepthook_verbose_crash(tmp_path, monkeypatch, preserve_excepthook, capsys): + """with verbose_crash, a crash report is written into the ipython dir.""" + monkeypatch.setattr("builtins.input", lambda *args: "") + monkeypatch.setenv("IPYTHONDIR", str(tmp_path)) + app = BaseIPythonApplication(verbose_crash=True) + app.init_crash_handler() + try: + raise ValueError("crashing for the test") + except ValueError: + app.excepthook(*sys.exc_info()) + report = tmp_path / "Crash_report_ipython.txt" + assert report.exists() + content = report.read_text(encoding="utf-8") + assert "IPython post-mortem report" in content + assert "crashing for the test" in content + + +def test_load_subconfig_profile_aware(tmp_path, monkeypatch, preserve_excepthook): + """load_subconfig(..., profile='name') loads config from another profile.""" + monkeypatch.setenv("IPYTHONDIR", str(tmp_path)) + + class SubCfgApp(BaseIPythonApplication): + test = Unicode().tag(config=True) + + sub = ProfileDir.create_profile_dir_by_name(tmp_path, "sub") + with open( + os.path.join(sub.location, "extra_sub.py"), "w", encoding="utf-8" + ) as f: + f.write("c.SubCfgApp.test = 'from sub profile'") + + main = ProfileDir.create_profile_dir_by_name(tmp_path, "default") + with open( + os.path.join(main.location, "ipython_config.py"), "w", encoding="utf-8" + ) as f: + f.write( + "load_subconfig('extra_sub.py', profile='sub')\n" + # missing profiles are silently ignored + "load_subconfig('extra_sub.py', profile='no_such_profile')\n" + ) + + app = SubCfgApp() + app.initialize([]) + assert app.test == "from sub profile" + + +def test_initialize_stops_at_subapp(tmp_path, monkeypatch, preserve_excepthook): + """initialize() stops early when a subcommand takes over.""" + from IPython.terminal.ipapp import TerminalIPythonApp + + monkeypatch.setenv("IPYTHONDIR", str(tmp_path)) + app = TerminalIPythonApp() + app.initialize(["profile", "list", "--ipython-dir", str(tmp_path)]) + try: + assert app.subapp is not None + # profile dir initialization is skipped when a subapp takes over + assert not (tmp_path / "profile_default").exists() + finally: + _clear_singletons(app) + + +def test_cwd_disappeared(monkeypatch): + """exit(1) if the current working directory no longer exists.""" + def missing_cwd(): + raise OSError("cwd does not exist") + + monkeypatch.setattr(os, "getcwd", missing_cwd) + with pytest.raises(SystemExit): + BaseIPythonApplication() diff --git a/tests/test_async_helpers.py b/tests/test_async_helpers.py new file mode 100644 index 00000000000..d8974c68895 --- /dev/null +++ b/tests/test_async_helpers.py @@ -0,0 +1,470 @@ +""" +Test for async helpers. + +Should only trigger on python 3.5+ or will have syntax errors. +""" + +import contextlib +import sys +from itertools import chain, repeat +from textwrap import dedent, indent +from typing import TYPE_CHECKING + +import pytest + +from IPython.core.async_helpers import _should_be_async +from IPython.testing.decorators import skip_without + +if TYPE_CHECKING: + from IPython import get_ipython + + ip = get_ipython() + + +def iprc(x): + return ip.run_cell(dedent(x)).raise_error() + + +def iprc_nr(x): + return ip.run_cell(dedent(x)) + + +def _finally_return_warning(): + """Context manager for the ``{val}="return"`` + ``"finally"`` test case. + + Compiling a ``return`` that exits a ``finally`` block emits + ``SyntaxWarning: 'return' in a 'finally' block`` starting with Python + 3.14 (PEP 765). That warning does not exist on older Pythons, so only + assert it there; on earlier versions this is a no-op. + """ + if sys.version_info >= (3, 14): + return pytest.warns(SyntaxWarning, match="'return' in a 'finally' block") + return contextlib.nullcontext() + + +@pytest.fixture(autouse=True) +def reset_loop_runner(): + yield + ip.loop_runner = "asyncio" + + +def test_should_be_async(): + assert not _should_be_async("False") + assert _should_be_async("await bar()") + assert _should_be_async("x = await bar()") + assert not _should_be_async( + dedent( + """ + async def awaitable(): + pass +""" + ) + ) + + assert not _should_be_async("return await foo()") + assert not _should_be_async("return bar()") + assert not _should_be_async("some invalid Python code") + + assert not _should_be_async("'\\ud800'") + + if sys.version_info >= (3, 13): + # Note: the next assert assumes the tests run without the `-OO` flag + assert not _should_be_async("'\\ud800'\nawait foo") + + +def _get_top_level_cases(): + # These are test cases that should be valid in a function + # but invalid outside of a function. + test_cases = [] + test_cases.append(("basic", "{val}")) + + # Note, in all conditional cases, I use True instead of + # False so that the peephole optimizer won't optimize away + # the return, so CPython will see this as a syntax error: + # + # while True: + # break + # return + # + # But not this: + # + # while False: + # return + # + # See https://bugs.python.org/issue1875 + + test_cases.append( + ( + "if", + dedent( + """ + if True: + {val} + """ + ), + ) + ) + + test_cases.append( + ( + "while", + dedent( + """ + while True: + {val} + break + """ + ), + ) + ) + + test_cases.append( + ( + "try", + dedent( + """ + try: + {val} + except: + pass + """ + ), + ) + ) + + test_cases.append( + ( + "except", + dedent( + """ + try: + pass + except: + {val} + """ + ), + ) + ) + + test_cases.append( + ( + "finally", + dedent( + """ + try: + pass + except: + pass + finally: + {val} + """ + ), + ) + ) + + test_cases.append( + ( + "for", + dedent( + """ + for _ in range(4): + {val} + """ + ), + ) + ) + + test_cases.append( + ( + "nested", + dedent( + """ + if True: + while True: + {val} + break + """ + ), + ) + ) + + test_cases.append( + ( + "deep-nested", + dedent( + """ + if True: + while True: + break + for x in range(3): + if True: + while True: + for x in range(3): + {val} + """ + ), + ) + ) + + return test_cases + +def _get_ry_syntax_errors(): + # This is a mix of tests that should be a syntax error if + # return or yield whether or not they are in a function + + test_cases = [] + + test_cases.append( + ( + "class", + dedent( + """ + class V: + {val} + """ + ), + ) + ) + + test_cases.append( + ( + "nested-class", + dedent( + """ + class V: + class C: + {val} + """ + ), + ) + ) + + return test_cases + + +def test_top_level_return_error(): + tl_err_test_cases = _get_top_level_cases() + tl_err_test_cases.extend(_get_ry_syntax_errors()) + + vals = ( + "return", + "yield", + "yield from (_ for _ in range(3))", + dedent( + """ + def f(): + pass + return + """ + ), + ) + + with _finally_return_warning(): + for test_name, test_case in tl_err_test_cases: + # This example should work if 'pass' is used as the value + iprc(test_case.format(val="pass")) + + # It should fail with all the values + for val in vals: + with pytest.raises(SyntaxError): + iprc(test_case.format(val=val)) + + +def test_in_func_no_error(): + # Test that the implementation of top-level return/yield + # detection isn't *too* aggressive, and works inside a function + func_contexts = [] + + func_contexts.append( + ( + "func", + False, + dedent( + """ + def f():""" + ), + ) + ) + + func_contexts.append( + ( + "method", + False, + dedent( + """ + class MyClass: + def __init__(self): + """ + ), + ) + ) + + func_contexts.append( + ( + "async-func", + True, + dedent( + """ + async def f():""" + ), + ) + ) + + func_contexts.append( + ( + "async-method", + True, + dedent( + """ + class MyClass: + async def f(self):""" + ), + ) + ) + + func_contexts.append( + ( + "closure", + False, + dedent( + """ + def f(): + def g(): + """ + ), + ) + ) + + def nest_case(context, case): + # Detect indentation + lines = context.strip().splitlines() + prefix_len = 0 + for c in lines[-1]: + if c != " ": + break + prefix_len += 1 + + indented_case = indent(case, " " * (prefix_len + 4)) + return context + "\n" + indented_case + + # yield is allowed in async functions, starting in Python 3.6, + # and yield from is not allowed in any version + vals = ("return", "yield", "yield from (_ for _ in range(3))") + + success_tests = zip(_get_top_level_cases(), repeat(False)) + failure_tests = zip(_get_ry_syntax_errors(), repeat(True)) + + tests = chain(success_tests, failure_tests) + + with _finally_return_warning(): + for context_name, async_func, context in func_contexts: + for (test_name, test_case), should_fail in tests: + nested_case = nest_case(context, test_case) + + for val in vals: + cell = nested_case.format(val=val) + + if should_fail: + with pytest.raises(SyntaxError): + iprc(cell) + else: + iprc(cell) + + +def test_nonlocal(): + # fails if outer scope is not a function scope or if var not defined + with pytest.raises(SyntaxError): + iprc("nonlocal x") + with pytest.raises(SyntaxError): + iprc( + """ + x = 1 + def f(): + nonlocal x + x = 10000 + yield x + """ + ) + with pytest.raises(SyntaxError): + iprc( + """ + def f(): + def g(): + nonlocal x + x = 10000 + yield x + """ + ) + + # works if outer scope is a function scope and var exists + iprc( + """ + def f(): + x = 20 + def g(): + nonlocal x + x = 10000 + yield x + """ + ) + + +def test_execute(): + iprc( + """ + import asyncio + await asyncio.sleep(0.001) + """ + ) + + +def test_autoawait(): + iprc("%autoawait False") + iprc("%autoawait True") + iprc( + """ + from asyncio import sleep + await sleep(0.1) + """ + ) + + +def test_memory_error(): + """The pgen parser in 3.8 or before use to raise MemoryError on too many nested parens.""" + iprc("(" * 200 + ")" * 200) + + +@skip_without("curio") +def test_autoawait_curio(): + iprc("%autoawait curio") + + +@skip_without("trio") +def test_autoawait_trio(): + iprc("%autoawait trio") + + +@skip_without("trio") +def test_autoawait_trio_wrong_sleep(): + iprc("%autoawait trio") + res = iprc_nr( + """ + import asyncio + await asyncio.sleep(0) + """ + ) + with pytest.raises(TypeError): + res.raise_error() + + +@skip_without("trio") +def test_autoawait_asyncio_wrong_sleep(): + iprc("%autoawait asyncio") + res = iprc_nr( + """ + import trio + await trio.sleep(0) + """ + ) + with pytest.raises(RuntimeError): + res.raise_error() diff --git a/tests/test_autocall.py b/tests/test_autocall.py new file mode 100644 index 00000000000..75107822c67 --- /dev/null +++ b/tests/test_autocall.py @@ -0,0 +1,157 @@ +"""These kinds of tests are less than ideal, but at least they run. + +This was an old test that was being run interactively in the top-level tests/ +directory, which we are removing. For now putting this here ensures at least +we do run the test, though ultimately this functionality should all be tested +with better-isolated tests that don't rely on the global instance in iptest. +""" + +import pytest + +from IPython.core.magics.auto import AutoMagics +from IPython.core.splitinput import LineInfo +from IPython.core.prefilter import AutocallChecker + + +def doctest_autocall(): + """ + In [1]: def f1(a,b,c): + ...: return a+b+c + ...: + + In [2]: def f2(a): + ...: return a + a + ...: + + In [3]: def r(x): + ...: return True + ...: + + In [4]: ;f2 a b c + Out[4]: 'a b ca b c' + + In [5]: assert _ == "a b ca b c" + + In [6]: ,f1 a b c + Out[6]: 'abc' + + In [7]: assert _ == 'abc' + + In [8]: print(_) + abc + + In [9]: /f1 1,2,3 + Out[9]: 6 + + In [10]: assert _ == 6 + + In [11]: /f2 4 + Out[11]: 8 + + In [12]: assert _ == 8 + + In [12]: del f1, f2 + + In [13]: ,r a + Out[13]: True + + In [14]: assert _ == True + + In [15]: r'a' + Out[15]: 'a' + + In [16]: assert _ == 'a' + """ + + +def test_autocall_should_ignore_raw_strings(): + line_info = LineInfo("r'a'") + pm = ip.prefilter_manager + ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm, config=pm.config) + assert ac.check(line_info) is None + + +@pytest.fixture +def restore_automagic(): + mman = ip.magics_manager + saved = mman.auto_magic + yield mman + mman.auto_magic = saved + + +@pytest.fixture +def restore_autocall(): + saved = ip.autocall + yield + ip.autocall = saved + + +def test_automagic_on_off_explicit(restore_automagic, capsys): + mman = restore_automagic + for arg in ("on", "1", "true"): + ip.run_line_magic("automagic", arg) + assert mman.auto_magic is True + out = capsys.readouterr().out + assert "Automagic is ON, % prefix IS NOT needed for line magics." in out + for arg in ("off", "0", "false"): + ip.run_line_magic("automagic", arg) + assert mman.auto_magic is False + out = capsys.readouterr().out + assert "Automagic is OFF, % prefix IS needed for line magics." in out + + +def test_automagic_case_insensitive(restore_automagic): + mman = restore_automagic + ip.run_line_magic("automagic", "OFF") + assert mman.auto_magic is False + ip.run_line_magic("automagic", "ON") + assert mman.auto_magic is True + + +def test_automagic_toggle(restore_automagic): + mman = restore_automagic + mman.auto_magic = True + ip.run_line_magic("automagic", "") + assert mman.auto_magic is False + ip.run_line_magic("automagic", "") + assert mman.auto_magic is True + + +def test_autocall_set_modes(restore_autocall, capsys): + for arg, expected, status in (("2", 2, "Full"), ("1", 1, "Smart"), ("0", 0, "Off")): + ip.run_line_magic("autocall", arg) + assert ip.autocall == expected + out = capsys.readouterr().out + assert "Automatic calling is: %s" % status in out + + +def test_autocall_invalid_mode(restore_autocall, caplog): + ip.autocall = 1 + ip.run_line_magic("autocall", "3") + # invalid mode leaves the setting unchanged and logs an error + assert ip.autocall == 1 + assert "Valid modes: 0->Off, 1->Smart, 2->Full" in caplog.text + + +def test_autocall_toggle_remembers_previous_mode(restore_autocall, capsys): + ip.run_line_magic("autocall", "2") + assert ip.autocall == 2 + # toggling turns it off... + ip.run_line_magic("autocall", "") + assert ip.autocall == 0 + out = capsys.readouterr().out + assert "Automatic calling is: Off" in out + # ... and toggling again restores the saved mode + ip.run_line_magic("autocall", "") + assert ip.autocall == 2 + out = capsys.readouterr().out + assert "Automatic calling is: Full" in out + + +def test_autocall_toggle_without_saved_state(restore_autocall): + # A fresh AutoMagics instance has no saved autocall state, so toggling + # from off defaults to smart mode (1). + ip.autocall = 0 + am = AutoMagics(ip) + am.autocall("") + assert ip.autocall == 1 diff --git a/tests/test_backgroundjobs.py b/tests/test_backgroundjobs.py new file mode 100644 index 00000000000..d820426fef3 --- /dev/null +++ b/tests/test_backgroundjobs.py @@ -0,0 +1,281 @@ +"""Tests for pylab tools module.""" + +# ----------------------------------------------------------------------------- +# Copyright (c) 2011, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + +# Stdlib imports +import threading +import time + +# Third-party imports +import pytest + +# Our own imports +from IPython.lib import backgroundjobs as bg + +# ----------------------------------------------------------------------------- +# Globals and constants +# ----------------------------------------------------------------------------- +t_short = 0.0001 # very short interval to wait on jobs + + +# ----------------------------------------------------------------------------- +# Local utilities +# ----------------------------------------------------------------------------- +def sleeper(interval=t_short, *a, **kw): + args = dict(interval=interval, other_args=a, kw_args=kw) + time.sleep(interval) + return args + + +def crasher(interval=t_short, *a, **kw): + time.sleep(interval) + raise Exception("Dead job with interval %s" % interval) + + +# ----------------------------------------------------------------------------- +# Classes and functions +# ----------------------------------------------------------------------------- + + +def test_result(): + """Test job submission and result retrieval""" + jobs = bg.BackgroundJobManager() + j = jobs.new(sleeper) + j.join() + assert j.result["interval"] == t_short + + +def test_flush(): + """Test job control""" + jobs = bg.BackgroundJobManager() + j = jobs.new(sleeper) + j.join() + assert len(jobs.completed) == 1 + assert len(jobs.dead) == 0 + jobs.flush() + assert len(jobs.completed) == 0 + + +def test_dead(): + """Test control of dead jobs""" + jobs = bg.BackgroundJobManager() + j = jobs.new(crasher) + j.join() + assert len(jobs.completed) == 0 + assert len(jobs.dead) == 1 + jobs.flush() + assert len(jobs.dead) == 0 + + +def test_longer(): + """Test control of longer-running jobs""" + jobs = bg.BackgroundJobManager() + # Sleep for long enough for the following two checks to still report the + # job as running, but not so long that it makes the test suite noticeably + # slower. + j = jobs.new(sleeper, 0.1) + assert len(jobs.running) == 1 + assert len(jobs.completed) == 0 + j.join() + assert len(jobs.running) == 0 + assert len(jobs.completed) == 1 + + +def test_expression_job(): + """Jobs can be created from strings evaluated with given namespaces""" + jobs = bg.BackgroundJobManager() + j = jobs.new("x + y", dict(x=1, y=2)) + j.join() + assert j.result == 3 + # separate globals and locals + j = jobs.new("x + y", dict(x=10), dict(y=20)) + j.join() + assert j.result == 30 + + +def test_expression_job_caller_namespace(): + """With no namespace args, the caller's frame is used""" + jobs = bg.BackgroundJobManager() + only_visible_here = 21 + j = jobs.new("only_visible_here * 2") + j.join() + assert j.result == 42 + + +def test_expression_job_too_many_args(): + jobs = bg.BackgroundJobManager() + with pytest.raises(ValueError): + jobs.new("1 + 1", {}, {}, {}) + + +def test_new_invalid_type(): + jobs = bg.BackgroundJobManager() + with pytest.raises(TypeError): + jobs.new(42) + + +def test_base_job_not_instantiable(): + with pytest.raises(NotImplementedError): + bg.BackgroundJobBase() + + +def test_func_job_requires_callable(): + with pytest.raises(TypeError): + bg.BackgroundJobFunc("not a callable") + + +def test_daemon_flag(): + jobs = bg.BackgroundJobManager() + ev = threading.Event() + j = jobs.new(ev.wait, 5, daemon=True) + assert j.daemon + ev.set() + j.join(timeout=5) + assert not j.is_alive() + + +def test_getitem(): + """Jobs can be looked up by number or by the job object itself""" + jobs = bg.BackgroundJobManager() + j = jobs.new(sleeper) + j.join() + assert jobs[j.num] is j + assert jobs[j] is j + + +def test_str_and_repr(): + jobs = bg.BackgroundJobManager() + j = jobs.new("1 + 1") + j.join() + assert str(j) == "1 + 1" + assert repr(j) == "" % j.num + + +def test_status_call(capsys): + """Calling the manager prints a status report of all groups""" + jobs = bg.BackgroundJobManager() + ev = threading.Event() + running = jobs.new(ev.wait, 5) + completed = jobs.new(sleeper) + dead = jobs.new(crasher) + completed.join() + dead.join() + try: + jobs() # alias for jobs.status() + finally: + ev.set() + running.join(timeout=5) + out = capsys.readouterr().out + assert "Running jobs:" in out + assert "Completed jobs:" in out + assert "Dead jobs:" in out + assert "%s : %s" % (completed.num, completed) in out + + +def test_status_new(capsys): + """_status_new only reports jobs finished since the last call""" + jobs = bg.BackgroundJobManager() + j = jobs.new(sleeper) + j.join() + assert jobs._status_new() is True + out = capsys.readouterr().out + assert "Completed jobs:" in out + # state was reset: nothing new to report the second time + assert not jobs._status_new() + + +def test_remove(): + jobs = bg.BackgroundJobManager() + ok = jobs.new(sleeper) + bad = jobs.new(crasher) + ok.join() + bad.join() + jobs.remove(ok.num) + assert len(jobs.completed) == 0 + jobs.remove(bad.num) + assert len(jobs.dead) == 0 + + +def test_remove_running_and_missing(caplog): + jobs = bg.BackgroundJobManager() + ev = threading.Event() + j = jobs.new(ev.wait, 5) + try: + jobs.remove(j.num) + assert "still running" in caplog.text + assert len(jobs.running) == 1 + caplog.clear() + jobs.remove(1234) + assert "not found" in caplog.text + finally: + ev.set() + j.join(timeout=5) + + +def test_flush_empty(capsys): + jobs = bg.BackgroundJobManager() + jobs.flush() + assert "No jobs to flush." in capsys.readouterr().out + + +def test_result_method(caplog): + jobs = bg.BackgroundJobManager() + j = jobs.new("6 * 7") + j.join() + assert jobs.result(j.num) == 42 + jobs.result(1234) + assert "not found" in caplog.text + + +def test_traceback(capsys, caplog): + jobs = bg.BackgroundJobManager() + j = jobs.new(crasher) + j.join() + # traceback of a specific job, by number and by job object + jobs.traceback(j.num) + out = capsys.readouterr().out + assert "Dead job with interval" in out + jobs.traceback(j) + assert "Dead job with interval" in capsys.readouterr().out + # with no argument, print tracebacks for all dead jobs + jobs.traceback() + out = capsys.readouterr().out + assert "Traceback for: %r" % j in out + assert "Dead job with interval" in out + # missing job number logs an error + jobs.traceback(1234) + assert "not found" in caplog.text + + +def test_dead_job_attributes(): + jobs = bg.BackgroundJobManager() + j = jobs.new(crasher) + j.join() + assert j.stat_code == bg.BackgroundJobBase.stat_dead_c + assert j.finished is None + assert "died" in j.result + assert "Dead job with interval" in j._tb + + +def test_traceback_without_ipython(monkeypatch): + """Without an IPython instance, _init falls back to AutoFormattedTB. + + Note: the fallback currently passes the removed ``color_scheme`` + argument to AutoFormattedTB, so job creation raises TypeError. This + test documents the current behaviour; if the fallback is fixed, it + should be updated to assert that the job runs and formats a traceback. + """ + monkeypatch.setattr(bg, "get_ipython", lambda: None) + jobs = bg.BackgroundJobManager() + with pytest.raises(TypeError): + jobs.new(crasher) diff --git a/tests/test_capture.py b/tests/test_capture.py new file mode 100644 index 00000000000..460514ed569 --- /dev/null +++ b/tests/test_capture.py @@ -0,0 +1,180 @@ +# encoding: utf-8 +"""Tests for IPython.utils.capture""" + +# ----------------------------------------------------------------------------- +# Copyright (C) 2013 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + + +import sys + +import pytest + +from IPython.utils import capture + +# ----------------------------------------------------------------------------- +# Globals +# ----------------------------------------------------------------------------- + +_mime_map = dict( + _repr_png_="image/png", + _repr_jpeg_="image/jpeg", + _repr_svg_="image/svg+xml", + _repr_html_="text/html", + _repr_json_="application/json", + _repr_javascript_="application/javascript", +) + +basic_data = { + "image/png": b"binarydata", + "text/html": "bold", +} +basic_metadata = { + "image/png": { + "width": 10, + "height": 20, + }, +} + +full_data = { + "image/png": b"binarydata", + "image/jpeg": b"binarydata", + "image/svg+xml": "", + "text/html": "bold", + "application/javascript": "alert();", + "application/json": "{}", +} +full_metadata = { + "image/png": {"png": "exists"}, + "image/jpeg": {"jpeg": "exists"}, + "image/svg+xml": {"svg": "exists"}, + "text/html": {"html": "exists"}, + "application/javascript": {"js": "exists"}, + "application/json": {"json": "exists"}, +} + +hello_stdout = "hello, stdout" +hello_stderr = "hello, stderr" + + +# ----------------------------------------------------------------------------- +# Test Functions +# ----------------------------------------------------------------------------- +@pytest.mark.parametrize("method_mime", _mime_map.items()) +def test_rich_output_empty(method_mime): + """RichOutput with no args""" + rich = capture.RichOutput() + method, mime = method_mime + assert getattr(rich, method)() is None + + +def test_rich_output(): + """test RichOutput basics""" + data = basic_data + metadata = basic_metadata + rich = capture.RichOutput(data=data, metadata=metadata) + assert rich._repr_html_() == data["text/html"] + assert rich._repr_png_() == (data["image/png"], metadata["image/png"]) + assert rich._repr_latex_() is None + assert rich._repr_javascript_() is None + assert rich._repr_svg_() is None + + +@pytest.mark.parametrize("method_mime", _mime_map.items()) +def test_rich_output_no_metadata(method_mime): + """test RichOutput with no metadata""" + data = full_data + rich = capture.RichOutput(data=data) + method, mime = method_mime + assert getattr(rich, method)() == data[mime] + + +@pytest.mark.parametrize("method_mime", _mime_map.items()) +def test_rich_output_metadata(method_mime): + """test RichOutput with metadata""" + data = full_data + metadata = full_metadata + rich = capture.RichOutput(data=data, metadata=metadata) + method, mime = method_mime + assert getattr(rich, method)() == (data[mime], metadata[mime]) + + +def test_rich_output_display(): + """test RichOutput.display + + This is a bit circular, because we are actually using the capture code we are testing + to test itself. + """ + data = full_data + rich = capture.RichOutput(data=data) + with capture.capture_output() as cap: + rich.display() + assert len(cap.outputs) == 1 + rich2 = cap.outputs[0] + assert rich2.data == rich.data + assert rich2.metadata == rich.metadata + + +def test_capture_output(): + """capture_output works""" + rich = capture.RichOutput(data=full_data) + with capture.capture_output() as cap: + print(hello_stdout, end="") + print(hello_stderr, end="", file=sys.stderr) + rich.display() + assert hello_stdout == cap.stdout + assert hello_stderr == cap.stderr + + +def test_capture_output_no_stdout(): + """test capture_output(stdout=False)""" + rich = capture.RichOutput(data=full_data) + with capture.capture_output(stdout=False) as cap: + print(hello_stdout, end="") + print(hello_stderr, end="", file=sys.stderr) + rich.display() + assert "" == cap.stdout + assert hello_stderr == cap.stderr + assert len(cap.outputs) == 1 + + +def test_capture_output_no_stderr(): + """test capture_output(stderr=False)""" + rich = capture.RichOutput(data=full_data) + # add nested capture_output so stderr doesn't make it to nose output + with capture.capture_output(), capture.capture_output(stderr=False) as cap: + print(hello_stdout, end="") + print(hello_stderr, end="", file=sys.stderr) + rich.display() + assert hello_stdout == cap.stdout + assert "" == cap.stderr + assert len(cap.outputs) == 1 + + +def test_capture_output_no_display(): + """test capture_output(display=False)""" + data = full_data.copy() + del data["image/png"] + rich = capture.RichOutput(data=data) + with capture.capture_output(display=False) as cap: + print(hello_stdout, end="") + print(hello_stderr, end="", file=sys.stderr) + rich.display() + assert hello_stdout == cap.stdout + assert hello_stderr == cap.stderr + assert cap.outputs == [] + + +def test_captured_io_none_streams(): + """CapturedIO accepts None for stdout/stderr and returns empty strings.""" + cap = capture.CapturedIO(None, None) + assert cap.stdout == "" + assert cap.stderr == "" + assert cap.outputs == [] diff --git a/tests/test_clipboard.py b/tests/test_clipboard.py new file mode 100644 index 00000000000..2283f9e7286 --- /dev/null +++ b/tests/test_clipboard.py @@ -0,0 +1,263 @@ +import subprocess +import sys +import types + +import pytest + +from IPython.core.error import TryNext +from IPython.lib import clipboard +from IPython.lib.clipboard import ( + ClipboardEmpty, + osx_clipboard_get, + tkinter_clipboard_get, + wayland_clipboard_get, + win32_clipboard_get, +) +from IPython.testing.decorators import skip_if_no_x11 + + +@skip_if_no_x11 +def test_clipboard_get(): + # Smoketest for clipboard access - we can't easily guarantee that the + # clipboard is accessible and has something on it, but this tries to + # exercise the relevant code anyway. + try: + a = get_ipython().hooks.clipboard_get() + except ClipboardEmpty: + # Nothing in clipboard to get + pass + except TryNext: + # No clipboard access API available + pass + else: + assert isinstance(a, str) + + +class FakePopen: + """Minimal stand-in for subprocess.Popen usable as a context manager.""" + + calls = [] + + def __init__(self, cmd, stdout=None): + type(self).calls.append(cmd) + self.returncode = 0 + + def communicate(self, input=None): + return (b"", b"") + + def wait(self): + return self.returncode + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + return False + + +@pytest.fixture +def popen_recorder(monkeypatch): + """Install a FakePopen subclass in place of subprocess.Popen.""" + + def install(output=b"", returncode=0): + class Recorder(FakePopen): + calls = [] + + def __init__(self, cmd, stdout=None): + super().__init__(cmd, stdout=stdout) + self.returncode = returncode + + def communicate(self, input=None): + return (output, b"") + + monkeypatch.setattr(subprocess, "Popen", Recorder) + return Recorder + + return install + + +# ----------------------------------------------------------------------------- +# OS X (pbpaste) +# ----------------------------------------------------------------------------- + + +def test_osx_clipboard_get(popen_recorder): + recorder = popen_recorder(output=b"old\rmac\rline endings") + text = osx_clipboard_get() + # \r line endings are normalized to \n and bytes decoded to str + assert text == "old\nmac\nline endings" + assert recorder.calls == [["pbpaste", "-Prefer", "ascii"]] + + +# ----------------------------------------------------------------------------- +# Wayland (wl-paste) +# ----------------------------------------------------------------------------- + + +def test_wayland_clipboard_get(monkeypatch, popen_recorder): + monkeypatch.setenv("XDG_SESSION_TYPE", "wayland") + recorder = popen_recorder(output=b"wayland text") + assert wayland_clipboard_get() == "wayland text" + assert recorder.calls == [["wl-paste"]] + + +def test_wayland_clipboard_not_wayland(monkeypatch): + monkeypatch.setenv("XDG_SESSION_TYPE", "x11") + with pytest.raises(TryNext, match="wayland is not detected"): + wayland_clipboard_get() + + +def test_wayland_clipboard_no_wl_paste(monkeypatch): + monkeypatch.setenv("XDG_SESSION_TYPE", "wayland") + + def raise_fnf(*args, **kwargs): + raise FileNotFoundError("wl-paste") + + monkeypatch.setattr(subprocess, "Popen", raise_fnf) + with pytest.raises(TryNext, match="wl-clipboard"): + wayland_clipboard_get() + + +def test_wayland_clipboard_command_failure(monkeypatch, popen_recorder): + monkeypatch.setenv("XDG_SESSION_TYPE", "wayland") + popen_recorder(output=b"whatever", returncode=1) + with pytest.raises(TryNext): + wayland_clipboard_get() + + +def test_wayland_clipboard_empty(monkeypatch, popen_recorder): + monkeypatch.setenv("XDG_SESSION_TYPE", "wayland") + popen_recorder(output=b"") + with pytest.raises(ClipboardEmpty): + wayland_clipboard_get() + + +# ----------------------------------------------------------------------------- +# Windows (pywin32) +# ----------------------------------------------------------------------------- + + +def make_fake_win32clipboard(data): + """Build a fake win32clipboard module. + + *data* maps clipboard formats to either text or an exception to raise. + """ + mod = types.ModuleType("win32clipboard") + + class error(Exception): + pass + + mod.error = error + mod.CF_UNICODETEXT = 13 + mod.CF_TEXT = 1 + mod.opened = False + mod.closed = False + mod.data = dict(data) + + def OpenClipboard(): + mod.opened = True + + def CloseClipboard(): + mod.closed = True + + def GetClipboardData(fmt): + result = mod.data[fmt] + if isinstance(result, Exception): + raise result + return result + + mod.OpenClipboard = OpenClipboard + mod.CloseClipboard = CloseClipboard + mod.GetClipboardData = GetClipboardData + return mod + + +def test_win32_clipboard_get_unicode(monkeypatch): + fake = make_fake_win32clipboard({13: "unicode text"}) + monkeypatch.setitem(sys.modules, "win32clipboard", fake) + assert win32_clipboard_get() == "unicode text" + assert fake.opened + assert fake.closed + + +def test_win32_clipboard_get_fallback_to_cf_text(monkeypatch): + fake = make_fake_win32clipboard({13: TypeError("no unicode"), 1: b"plain text"}) + monkeypatch.setitem(sys.modules, "win32clipboard", fake) + assert win32_clipboard_get() == "plain text" + assert fake.closed + + +def test_win32_clipboard_get_empty(monkeypatch): + fake = make_fake_win32clipboard({}) + fake.data.update({13: fake.error("empty"), 1: fake.error("empty")}) + monkeypatch.setitem(sys.modules, "win32clipboard", fake) + with pytest.raises(ClipboardEmpty): + win32_clipboard_get() + # The clipboard must be closed even on failure. + assert fake.closed + + +def test_win32_clipboard_get_no_pywin32(monkeypatch): + monkeypatch.setitem(sys.modules, "win32clipboard", None) + with pytest.raises(TryNext, match="pywin32"): + win32_clipboard_get() + + +# ----------------------------------------------------------------------------- +# Tkinter +# ----------------------------------------------------------------------------- + + +def make_fake_tkinter(text=None): + mod = types.ModuleType("tkinter") + + class TclError(Exception): + pass + + class Tk: + instances = [] + + def __init__(self): + type(self).instances.append(self) + self.withdrawn = False + self.destroyed = False + + def withdraw(self): + self.withdrawn = True + + def clipboard_get(self): + if text is None: + raise TclError("CLIPBOARD selection doesn't exist") + return text + + def destroy(self): + self.destroyed = True + + mod.TclError = TclError + mod.Tk = Tk + return mod + + +def test_tkinter_clipboard_get(monkeypatch): + fake = make_fake_tkinter(text="tk text") + monkeypatch.setitem(sys.modules, "tkinter", fake) + assert tkinter_clipboard_get() == "tk text" + (root,) = fake.Tk.instances + assert root.withdrawn + assert root.destroyed + + +def test_tkinter_clipboard_get_empty(monkeypatch): + fake = make_fake_tkinter(text=None) + monkeypatch.setitem(sys.modules, "tkinter", fake) + with pytest.raises(ClipboardEmpty): + tkinter_clipboard_get() + # The Tk root window is destroyed even when the clipboard is empty. + (root,) = fake.Tk.instances + assert root.destroyed + + +def test_tkinter_clipboard_get_no_tkinter(monkeypatch): + monkeypatch.setitem(sys.modules, "tkinter", None) + with pytest.raises(TryNext, match="tkinter"): + tkinter_clipboard_get() diff --git a/tests/test_compilerop.py b/tests/test_compilerop.py new file mode 100644 index 00000000000..9542edc888f --- /dev/null +++ b/tests/test_compilerop.py @@ -0,0 +1,65 @@ +# coding: utf-8 +"""Tests for the compilerop module.""" +# ----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +# ----------------------------------------------------------------------------- + +import linecache +import sys + +import pytest + +from IPython.core import compilerop + + +@pytest.mark.parametrize("cell_number,prefix", [ + (0, " ncache + + +def test_proper_default_encoding(): + assert sys.getdefaultencoding() == "utf-8" + + +@pytest.mark.parametrize("src", [ + "a_unique_var_for_test_1 = 1", + "t = 'žćčšđ_unique'", + "y = '日本語_unique'", +]) +def test_cache_source(src): + cp = compilerop.CachingCompiler() + ncache = len(linecache.cache) + cp.cache(src) + assert len(linecache.cache) > ncache + + +def test_compiler_check_cache(): + """Test the compiler properly manages the cache.""" + cp = compilerop.CachingCompiler() + cp.cache("x=1", 99) + linecache.checkcache() + assert any( + k.startswith(" 131808: + size, start, stop, prct = recompute_unicode_ranges() + message = f"""_UNICODE_RANGES likely wrong and need updating. This is + likely due to a new release of Python. We've find that the biggest gap + in unicode characters has reduces in size to be {size} characters + ({prct}), from {start}, to {stop}. In completer.py likely update to + + _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)] + + And update the assertion below to use + + len_exp <= {len_exp} + """ + assert len_exp == len_test, message + + # fail if new unicode symbols have been added. + assert len_exp <= 159801, message + + +@contextmanager +def greedy_completion(): + ip = get_ipython() + greedy_original = ip.Completer.greedy + try: + ip.Completer.greedy = True + yield + finally: + ip.Completer.greedy = greedy_original + + +@contextmanager +def evaluation_policy(evaluation: str, **overrides): + ip = get_ipython() + evaluation_original = ip.Completer.evaluation + overrides_original = ip.Completer.policy_overrides + try: + ip.Completer.evaluation = evaluation + ip.Completer.policy_overrides = overrides + yield + finally: + ip.Completer.evaluation = evaluation_original + ip.Completer.policy_overrides = overrides_original + + +@contextmanager +def custom_matchers(matchers): + ip = get_ipython() + try: + ip.Completer.custom_matchers.extend(matchers) + yield + finally: + ip.Completer.custom_matchers.clear() + + +if sys.platform == "win32": + pairs = [ + ("abc", "abc"), + (" abc", '" abc"'), + ("a bc", '"a bc"'), + ("a bc", '"a bc"'), + (" bc", '" bc"'), + ] +else: + pairs = [ + ("abc", "abc"), + (" abc", r"\ abc"), + ("a bc", r"a\ bc"), + ("a bc", r"a\ \ bc"), + (" bc", r"\ \ bc"), + # On posix, we also protect parens and other special characters. + ("a(bc", r"a\(bc"), + ("a)bc", r"a\)bc"), + ("a( )bc", r"a\(\ \)bc"), + ("a[1]bc", r"a\[1\]bc"), + ("a{1}bc", r"a\{1\}bc"), + ("a#bc", r"a\#bc"), + ("a?bc", r"a\?bc"), + ("a=bc", r"a\=bc"), + ("a\\bc", r"a\\bc"), + ("a|bc", r"a\|bc"), + ("a;bc", r"a\;bc"), + ("a:bc", r"a\:bc"), + ("a'bc", r"a\'bc"), + ("a*bc", r"a\*bc"), + ('a"bc', r"a\"bc"), + ("a^bc", r"a\^bc"), + ("a&bc", r"a\&bc"), + ] + + +@pytest.mark.parametrize("s1,expected", pairs) +def test_protect_filename(s1, expected): + assert completer.protect_filename(s1) == expected + + +def check_line_split(splitter, test_specs): + for part1, part2, split in test_specs: + cursor_pos = len(part1) + line = part1 + part2 + out = splitter.split_line(line, cursor_pos) + assert out == split + + +def test_line_split(): + """Basic line splitter test with default specs.""" + sp = completer.CompletionSplitter() + # The format of the test specs is: part1, part2, expected answer. Parts 1 + # and 2 are joined into the 'line' sent to the splitter, as if the cursor + # was at the end of part1. So an empty part2 represents someone hitting + # tab at the end of the line, the most common case. + t = [ + ("run some/script", "", "some/script"), + ("run scripts/er", "ror.py foo", "scripts/er"), + ("echo $HOM", "", "HOM"), + ("print sys.pa", "", "sys.pa"), + ("print(sys.pa", "", "sys.pa"), + ("execfile('scripts/er", "", "scripts/er"), + ("a[x.", "", "x."), + ("a[x.", "y", "x."), + ('cd "some_file/', "", "some_file/"), + ] + check_line_split(sp, t) + # Ensure splitting works OK with unicode by re-running the tests with + # all inputs turned into unicode + check_line_split(sp, [map(str, p) for p in t]) + + +class NamedInstanceClass: + instances = {} + + def __init__(self, name): + self.instances[name] = self + + @classmethod + def _ipython_key_completions_(cls): + return cls.instances.keys() + + +class KeyCompletable: + def __init__(self, things=()): + self.things = things + + def _ipython_key_completions_(self): + return list(self.things) + + +@pytest.fixture(autouse=True) +def silence_pending_deprecation_warning(): + """ + We want to silence all PendingDeprecationWarning when testing the completer + """ + import warnings + with warnings.catch_warnings(): + warnings.simplefilter("ignore", PendingDeprecationWarning) + yield + + +def test_custom_completion_error(): + """Test that errors from custom attribute completers are silenced.""" + ip = get_ipython() + + class A: + pass + + ip.user_ns["x"] = A() + + @complete_object.register(A) + def complete_A(a, existing_completions): + raise TypeError("this should be silenced") + + ip.complete("x.") + +def test_custom_completion_ordering(): + """Test that errors from custom attribute completers are silenced.""" + ip = get_ipython() + ip.Completer.use_jedi = False + + _, matches = ip.complete("in") + assert matches.index("input") < matches.index("int") + + def complete_example(a): + return ["example2", "example1"] + + ip.Completer.custom_completers.add_re("ex*", complete_example) + _, matches = ip.complete("ex") + assert matches.index("example2") < matches.index("example1") + +def test_unicode_completions(): + ip = get_ipython() + # Some strings that trigger different types of completion. Check them both + # in str and unicode forms + s = ["ru", "%ru", "cd /", "floa", "float(x)/"] + for t in s + list(map(str, s)): + # We don't need to check exact completion values (they may change + # depending on the state of the namespace, but at least no exceptions + # should be thrown and the return value should be a pair of text, list + # values. + text, matches = ip.complete(t) + assert isinstance(text, str) + assert isinstance(matches, list) + +def test_latex_completions(): + ip = get_ipython() + # Test some random unicode symbols + keys = random.sample(sorted(latex_symbols), 10) + for k in keys: + text, matches = ip.complete(k) + assert text == k + assert matches == [latex_symbols[k]] + # Test a more complex line + text, matches = ip.complete("print(\\alpha") + assert text == "\\alpha" + assert matches[0] == latex_symbols["\\alpha"] + # Test multiple matching latex symbols + text, matches = ip.complete("\\al") + assert "\\alpha" in matches + assert "\\aleph" in matches + +def test_latex_no_results(): + """ + forward latex should really return nothing in either field if nothing is found. + """ + ip = get_ipython() + text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing") + assert text == "" + assert matches == () + +def test_back_latex_completion(): + ip = get_ipython() + + # do not return more than 1 matches for \beta, only the latex one. + name, matches = ip.complete("\\β") + assert matches == ["\\beta"] + +def test_back_unicode_completion(): + ip = get_ipython() + + name, matches = ip.complete("\\Ⅴ") + assert matches == ["\\ROMAN NUMERAL FIVE"] + +def test_forward_unicode_completion(): + ip = get_ipython() + + name, matches = ip.complete("\\ROMAN NUMERAL FIVE") + assert matches == ["Ⅴ"] # This is not a V + assert matches == ["\u2164"] # same as above but explicit. + +def test_delim_setting(): + sp = completer.CompletionSplitter() + sp.delims = " " + assert sp.delims == " " + assert sp._delim_expr == r"[\ ]" + +def test_spaces(): + """Test with only spaces as split chars.""" + sp = completer.CompletionSplitter() + sp.delims = " " + t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")] + check_line_split(sp, t) + +@pytest.mark.parametrize("s,expected", [ + ("'", "'"), + ("'''", "'"), + ("'hi' '", "'"), + ('"', '"'), + ('"""', '"'), + ('"hi" "', '"'), + ("''", False), + ("''' '''", False), + ("'hi' 'ipython'", False), + ('""', False), + ('""" """', False), + ('"hi" "ipython"', False), +]) +def test_has_open_quotes(s, expected): + result = completer.has_open_quotes(s) + if expected is False: + assert not result + else: + assert result == expected + +@pytest.mark.xfail( + sys.platform == "win32", reason="abspath completions fail on Windows" +) +def test_abspath_file_completions(): + ip = get_ipython() + with TemporaryDirectory() as tmpdir: + prefix = os.path.join(tmpdir, "foo") + suffixes = ["1", "2"] + names = [prefix + s for s in suffixes] + for n in names: + open(n, "w", encoding="utf-8").close() + + # Check simple completion + c = ip.complete(prefix)[1] + assert c == names + + # Now check with a function call + cmd = 'a = f("%s' % prefix + c = ip.complete(prefix, cmd)[1] + comp = [prefix + s for s in suffixes] + assert c == comp + +def test_local_file_completions(): + ip = get_ipython() + with TemporaryWorkingDirectory(): + prefix = "./foo" + suffixes = ["1", "2"] + names = [prefix + s for s in suffixes] + for n in names: + open(n, "w", encoding="utf-8").close() + + # Check simple completion + c = ip.complete(prefix)[1] + assert c == names + + test_cases = { + "function call": 'a = f("', + "shell bang": "!ls ", + "ls magic": r"%ls ", + "alias ls": "ls ", + } + for name, code in test_cases.items(): + cmd = f"{code}{prefix}" + c = ip.complete(prefix, cmd)[1] + comp = {prefix + s for s in suffixes} + assert comp.issubset(set(c)), f"completes in {name}" + +def test_file_completions_only_in_path_contexts(): + ip = get_ipython() + + def path_matches(text): + with provisionalcompleter(): + return [ + completion.text + for completion in ip.Completer.completions(text, len(text)) + if completion.type == "path" + ] + + with TemporaryWorkingDirectory(): + open("alpha_path_marker", "w", encoding="utf-8").close() + open("foo_path_marker", "w", encoding="utf-8").close() + + assert path_matches("f(x=") == [] + assert path_matches("# alpha") == [] + assert path_matches("foo != alpha") == [] + assert "./foo_path_marker" in path_matches("./foo") + assert "alpha_path_marker" in path_matches('f("alpha') + assert "alpha_path_marker" in path_matches("!cat alpha") + assert "alpha_path_marker" in path_matches("%ls alpha") + assert "alpha_path_marker" in path_matches("files = ! cat alpha") + +def test_quoted_file_completions(): + ip = get_ipython() + + def _(text): + return ip.Completer._complete( + cursor_line=0, cursor_pos=len(text), full_text=text + )["IPCompleter.file_matcher"]["completions"] + + with TemporaryWorkingDirectory(): + name = "foo'bar" + open(name, "w", encoding="utf-8").close() + + # Don't escape Windows + escaped = name if sys.platform == "win32" else "foo\\'bar" + + # Single quote matches embedded single quote + c = _("open('foo")[0] + assert c.text == escaped + + # Double quote requires no escape + c = _('open("foo')[0] + assert c.text == name + + # No quote requires an escape + c = _("%ls foo")[0] + assert c.text == escaped + +@pytest.mark.xfail( + sys.version_info.releaselevel in ("alpha",), + reason="Parso does not yet parse 3.13", +) +@pytest.mark.parametrize("jedi_status", [True, False]) +def test_all_completions_dups(jedi_status): + """ + Make sure the output of `IPCompleter.all_completions` does not have + duplicated prefixes. + """ + ip = get_ipython() + c = ip.Completer + ip.ex("class TestClass():\n\ta=1\n\ta1=2") + with provisionalcompleter(): + ip.Completer.use_jedi = jedi_status + matches = c.all_completions("TestCl") + assert matches == ["TestClass"], (jedi_status, matches) + matches = c.all_completions("TestClass.") + assert len(matches) > 2, (jedi_status, matches) + matches = c.all_completions("TestClass.a") + if jedi_status: + assert matches == ["TestClass.a", "TestClass.a1"], jedi_status + else: + assert matches == [".a", ".a1"], jedi_status + +@pytest.mark.xfail( + sys.version_info.releaselevel in ("alpha",), + reason="Parso does not yet parse 3.13", +) +def test_jedi(): + """ + A couple of issue we had with Jedi + """ + ip = get_ipython() + + def _test_complete(reason, s, comp, start=None, end=None): + l = len(s) + start = start if start is not None else l + end = end if end is not None else l + with provisionalcompleter(): + ip.Completer.use_jedi = True + completions = set(ip.Completer.completions(s, l)) + ip.Completer.use_jedi = False + assert Completion(start, end, comp) in completions, reason + + def _test_not_complete(reason, s, comp): + l = len(s) + with provisionalcompleter(): + ip.Completer.use_jedi = True + completions = set(ip.Completer.completions(s, l)) + ip.Completer.use_jedi = False + assert Completion(l, l, comp) not in completions, reason + + import jedi + + jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3]) + if jedi_version > (0, 10): + _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real") + _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real") + _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize") + _test_complete("cover duplicate completions", "im", "import", 0, 2) + + _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize") + +@pytest.mark.xfail( + sys.version_info.releaselevel in ("alpha",), + reason="Parso does not yet parse 3.13", +) +def test_completion_have_signature(): + """ + Lets make sure jedi is capable of pulling out the signature of the function we are completing. + """ + ip = get_ipython() + with provisionalcompleter(): + ip.Completer.use_jedi = True + completions = ip.Completer.completions("ope", 3) + c = next(completions) # should be `open` + ip.Completer.use_jedi = False + assert "file" in c.signature, "Signature of function was not found by completer" + assert ( + "encoding" in c.signature + ), "Signature of function was not found by completer" + +@pytest.mark.xfail( + sys.version_info.releaselevel in ("alpha",), + reason="Parso does not yet parse 3.13", +) +def test_completions_have_type(): + """ + Lets make sure matchers provide completion type. + """ + ip = get_ipython() + with provisionalcompleter(): + ip.Completer.use_jedi = False + completions = ip.Completer.completions("%tim", 3) + c = next(completions) # should be `%time` or similar + assert c.type == "magic", "Type of magic was not assigned by completer" + +@pytest.mark.xfail( + parse(version("jedi")) <= parse("0.18.0"), + reason="Known failure on jedi<=0.18.0", + strict=True, +) +def test_deduplicate_completions(): + """ + Test that completions are correctly deduplicated (even if ranges are not the same) + """ + ip = get_ipython() + ip.ex( + textwrap.dedent( + """ + class Z: + zoo = 1 + """ + ) + ) + with provisionalcompleter(): + ip.Completer.use_jedi = True + l = list( + _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3)) + ) + ip.Completer.use_jedi = False + + assert len(l) == 1, "Completions (Z.z) correctly deduplicate: %s " % l + assert l[0].text == "zoo" # and not `it.accumulate` + +@pytest.mark.xfail( + sys.version_info.releaselevel in ("alpha",), + reason="Parso does not yet parse 3.13", +) +def test_greedy_completions(): + """ + Test the capability of the Greedy completer. + + Most of the test here does not really show off the greedy completer, for proof + each of the text below now pass with Jedi. The greedy completer is capable of more. + + See the :any:`test_dict_key_completion_contexts` + + """ + ip = get_ipython() + ip.ex("a=list(range(5))") + ip.ex("b,c = 1, 1.2") + ip.ex("d = {'a b': str}") + ip.ex("x=y='a'") + _, c = ip.complete(".", line="a[0].") + assert not ".real" in c, "Shouldn't have completed on a[0]: %s" % c + + def _(line, cursor_pos, expect, message, completion): + with greedy_completion(), provisionalcompleter(): + ip.Completer.use_jedi = False + _, c = ip.complete(".", line=line, cursor_pos=cursor_pos) + assert expect in c, message % c + + ip.Completer.use_jedi = True + with provisionalcompleter(): + completions = ip.Completer.completions(line, cursor_pos) + assert completion in list(completions) + + with provisionalcompleter(): + _( + "a[0].", + 5, + ".real", + "Should have completed on a[0].: %s", + Completion(5, 5, "real"), + ) + _( + "a[0].r", + 6, + ".real", + "Should have completed on a[0].r: %s", + Completion(5, 6, "real"), + ) + _( + "a[0].from_", + 10, + ".from_bytes", + "Should have completed on a[0].from_: %s", + Completion(5, 10, "from_bytes"), + ) + _( + "assert str.star", + 14, + ".startswith", + "Should have completed on `assert str.star`: %s", + Completion(11, 14, "startswith"), + ) + _( + "d['a b'].str", + 12, + ".strip", + "Should have completed on `d['a b'].str`: %s", + Completion(9, 12, "strip"), + ) + _( + "a.app", + 4, + ".append", + "Should have completed on `a.app`: %s", + Completion(2, 4, "append"), + ) + _( + "x.upper() == y.", + 15, + ".upper", + "Should have completed on `x.upper() == y.`: %s", + Completion(15, 15, "upper"), + ) + _( + "(x.upper() == y.", + 16, + ".upper", + "Should have completed on `(x.upper() == y.`: %s", + Completion(16, 16, "upper"), + ) + _( + "(x.upper() == y).", + 17, + ".bit_length", + "Should have completed on `(x.upper() == y).`: %s", + Completion(17, 17, "bit_length"), + ) + _( + "{'==', 'abc'}.", + 14, + ".add", + "Should have completed on `{'==', 'abc'}.`: %s", + Completion(14, 14, "add"), + ) + _( + "b + c.", + 6, + ".hex", + "Should have completed on `b + c.`: %s", + Completion(6, 6, "hex"), + ) + +def test_omit__names(): + # also happens to test IPCompleter as a configurable + ip = get_ipython() + ip._hidden_attr = 1 + ip._x = {} + c = ip.Completer + ip.ex("ip=get_ipython()") + cfg = Config() + cfg.IPCompleter.omit__names = 0 + c.update_config(cfg) + with provisionalcompleter(): + c.use_jedi = False + s, matches = c.complete("ip.") + assert ".__str__" in matches + assert "._hidden_attr" in matches + + # c.use_jedi = True + # completions = set(c.completions('ip.', 3)) + # assert Completion(3 in 3, '__str__', completions) + # assert Completion(3 in 3, "_hidden_attr", completions) + + cfg = Config() + cfg.IPCompleter.omit__names = 1 + c.update_config(cfg) + with provisionalcompleter(): + c.use_jedi = False + s, matches = c.complete("ip.") + assert ".__str__" not in matches + # assert 'ip._hidden_attr' in matches + + # c.use_jedi = True + # completions = set(c.completions('ip.', 3)) + # assert Completion(3 not in 3,'__str__', completions) + # assert Completion(3 in 3, "_hidden_attr", completions) + + cfg = Config() + cfg.IPCompleter.omit__names = 2 + c.update_config(cfg) + with provisionalcompleter(): + c.use_jedi = False + s, matches = c.complete("ip.") + assert ".__str__" not in matches + assert "._hidden_attr" not in matches + + # c.use_jedi = True + # completions = set(c.completions('ip.', 3)) + # assert Completion(3 not in 3,'__str__', completions) + # assert Completion(3 not in 3, "_hidden_attr", completions) + + with provisionalcompleter(): + c.use_jedi = False + s, matches = c.complete("ip._x.") + assert ".keys" in matches + + # c.use_jedi = True + # completions = set(c.completions('ip._x.', 6)) + # assert Completion(6 in 6, "keys", completions) + + del ip._hidden_attr + del ip._x + +def test_limit_to__all__False_ok(): + """ + Limit to all is deprecated, once we remove it this test can go away. + + ``limit_to__all__`` has since been removed from ``IPCompleter``, so + setting it now triggers traitlets' "not recognized" warning; that is + expected here since the point of the test is to make sure the (now + unknown) option doesn't break completion. + """ + ip = get_ipython() + c = ip.Completer + c.use_jedi = False + ip.ex("class D: x=24") + ip.ex("d=D()") + cfg = Config() + cfg.IPCompleter.limit_to__all__ = False + with pytest.warns(UserWarning, match="limit_to__all__.*not recognized"): + c.update_config(cfg) + s, matches = c.complete("d.") + assert ".x" in matches + +def test_get__all__entries_ok(): + class A: + __all__ = ["x", 1] + + words = completer.get__all__entries(A()) + assert words == ["x"] + +def test_get__all__entries_no__all__ok(): + class A: + pass + + words = completer.get__all__entries(A()) + assert words == [] + +def test_completes_globals_as_args_of_methods(): + ip = get_ipython() + c = ip.Completer + c.use_jedi = False + ip.ex("long_variable_name = 1") + ip.ex("a = []") + s, matches = c.complete(None, "a.sort(lo") + assert "long_variable_name" in matches + +def test_completes_attributes_in_fstring_expressions(): + ip = get_ipython() + c = ip.Completer + c.use_jedi = False + + class CustomClass: + def method_one(self): + pass + + ip.user_ns["custom_obj"] = CustomClass() + + # Test completion inside f-string expressions + s, matches = c.complete(None, "f'{custom_obj.meth") + assert ".method_one" in matches + +def test_completes_in_dict_expressions(): + ip = get_ipython() + c = ip.Completer + c.use_jedi = False + ip.ex("class Test: pass") + ip.ex("test_obj = Test()") + ip.ex("test_obj.attribute = 'value'") + + # Test completion in dictionary expressions + s, matches = c.complete(None, "d = {'key': test_obj.attr") + assert ".attribute" in matches + + # Test global completion in dictionary expressions with dots + s, matches = c.complete(None, "d = {'k.e.y': Te") + assert "Test" in matches + +def test_func_kw_completions(): + ip = get_ipython() + c = ip.Completer + c.use_jedi = False + ip.ex("def myfunc(a=1,b=2): return a+b") + s, matches = c.complete(None, "myfunc(1,b") + assert "b=" in matches + # Simulate completing with cursor right after b (pos==10): + s, matches = c.complete(None, "myfunc(1,b)", 10) + assert "b=" in matches + s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b') + assert "b=" in matches + # builtin function + s, matches = c.complete(None, "min(k, k") + assert "key=" in matches + +def test_default_arguments_from_docstring(): + ip = get_ipython() + c = ip.Completer + kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value") + assert kwd == ["key"] + # with cython type etc + kwd = c._default_arguments_from_docstring( + "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n" + ) + assert kwd == ["ncall", "resume", "nsplit"] + # white spaces + kwd = c._default_arguments_from_docstring( + "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n" + ) + assert kwd == ["ncall", "resume", "nsplit"] + +def test_line_magics(): + ip = get_ipython() + c = ip.Completer + s, matches = c.complete(None, "lsmag") + assert "%lsmagic" in matches + s, matches = c.complete(None, "%lsmag") + assert "%lsmagic" in matches + +def test_cell_magics(): + from IPython.core.magic import register_cell_magic + + @register_cell_magic + def _foo_cellm(line, cell): + pass + + ip = get_ipython() + c = ip.Completer + + s, matches = c.complete(None, "_foo_ce") + assert "%%_foo_cellm" in matches + s, matches = c.complete(None, "%%_foo_ce") + assert "%%_foo_cellm" in matches + +def test_line_cell_magics(): + from IPython.core.magic import register_line_cell_magic + + @register_line_cell_magic + def _bar_cellm(line, cell): + pass + + ip = get_ipython() + c = ip.Completer + + # The policy here is trickier, see comments in completion code. The + # returned values depend on whether the user passes %% or not explicitly, + # and this will show a difference if the same name is both a line and cell + # magic. + s, matches = c.complete(None, "_bar_ce") + assert "%_bar_cellm" in matches + assert "%%_bar_cellm" in matches + s, matches = c.complete(None, "%_bar_ce") + assert "%_bar_cellm" in matches + assert "%%_bar_cellm" in matches + s, matches = c.complete(None, "%%_bar_ce") + assert "%_bar_cellm" not in matches + assert "%%_bar_cellm" in matches + +def test_line_magics_with_code_argument(): + ip = get_ipython() + c = ip.Completer + c.use_jedi = False + + # attribute completion + text, matches = c.complete("%timeit -n 2 -r 1 float.as_integer") + assert matches == [".as_integer_ratio"] + + text, matches = c.complete("%debug --breakpoint test float.as_integer") + assert matches == [".as_integer_ratio"] + + text, matches = c.complete("%time --no-raise-error float.as_integer") + assert matches == [".as_integer_ratio"] + + text, matches = c.complete("%prun -l 0.5 -r float.as_integer") + assert matches == [".as_integer_ratio"] + + # implicit magics + text, matches = c.complete("timeit -n 2 -r 1 float.as_integer") + assert matches == [".as_integer_ratio"] + + # built-ins completion + text, matches = c.complete("%timeit -n 2 -r 1 flo") + assert matches == ["float"] + + # dict completion + text, matches = c.complete("%timeit -n 2 -r 1 {'my_key': 1}['my") + assert matches == ["my_key"] + + # invalid arguments - should not throw + text, matches = c.complete("%timeit -n 2 -r 1 -invalid float.as_integer") + assert matches == [] + + text, matches = c.complete("%debug --invalid float.as_integer") + assert matches == [] + +def test_line_magics_with_code_argument_shadowing(): + ip = get_ipython() + c = ip.Completer + c.use_jedi = False + + # shadow + ip.run_cell("timeit = 1") + + # should not suggest on implict magic when shadowed + text, matches = c.complete("timeit -n 2 -r 1 flo") + assert matches == [] + + # should suggest on explicit magic + text, matches = c.complete("%timeit -n 2 -r 1 flo") + assert matches == ["float"] + + # remove shadow + del ip.user_ns["timeit"] + + # should suggest on implicit magic after shadow removal + text, matches = c.complete("timeit -n 2 -r 1 flo") + assert matches == ["float"] + +def test_magic_completion_order(): + ip = get_ipython() + c = ip.Completer + + # Test ordering of line and cell magics. + text, matches = c.complete("timeit") + assert matches == ["%timeit", "%%timeit"] + +def test_magic_completion_shadowing(): + ip = get_ipython() + c = ip.Completer + c.use_jedi = False + + # Before importing matplotlib, %matplotlib magic should be the only option. + text, matches = c.complete("mat") + assert matches == ["%matplotlib"] + + # The newly introduced name should shadow the magic. + ip.run_cell("matplotlib = 1") + text, matches = c.complete("mat") + assert matches == ["matplotlib"] + + # After removing matplotlib from namespace, the magic should again be + # the only option. + del ip.user_ns["matplotlib"] + text, matches = c.complete("mat") + assert matches == ["%matplotlib"] + +def test_magic_completion_shadowing_explicit(): + """ + If the user try to complete a shadowed magic, and explicit % start should + still return the completions. + """ + ip = get_ipython() + c = ip.Completer + + # Before importing matplotlib, %matplotlib magic should be the only option. + text, matches = c.complete("%mat") + assert matches == ["%matplotlib"] + + ip.run_cell("matplotlib = 1") + + # After removing matplotlib from namespace, the magic should still be + # the only option. + text, matches = c.complete("%mat") + assert matches == ["%matplotlib"] + +def test_magic_config(): + ip = get_ipython() + c = ip.Completer + + s, matches = c.complete(None, "conf") + assert "%config" in matches + s, matches = c.complete(None, "conf") + assert "AliasManager" not in matches + s, matches = c.complete(None, "config ") + assert "AliasManager" in matches + s, matches = c.complete(None, "%config ") + assert "AliasManager" in matches + s, matches = c.complete(None, "config Ali") + assert ["AliasManager"] == matches + s, matches = c.complete(None, "%config Ali") + assert ["AliasManager"] == matches + s, matches = c.complete(None, "config AliasManager") + assert ["AliasManager"] == matches + s, matches = c.complete(None, "%config AliasManager") + assert ["AliasManager"] == matches + s, matches = c.complete(None, "config AliasManager.") + assert "AliasManager.default_aliases" in matches + s, matches = c.complete(None, "%config AliasManager.") + assert "AliasManager.default_aliases" in matches + s, matches = c.complete(None, "config AliasManager.de") + assert ["AliasManager.default_aliases"] == matches + s, matches = c.complete(None, "config AliasManager.de") + assert ["AliasManager.default_aliases"] == matches + +def test_magic_color(): + ip = get_ipython() + c = ip.Completer + + s, matches = c.complete(None, "colo") + assert "%colors" in matches + s, matches = c.complete(None, "colo") + assert "nocolor" not in matches + s, matches = c.complete(None, "%colors") # No trailing space + assert "nocolor" not in matches + s, matches = c.complete(None, "colors ") + assert "nocolor" in matches + s, matches = c.complete(None, "%colors ") + assert "nocolor" in matches + s, matches = c.complete(None, "colors noco") + assert ["nocolor"] == matches + s, matches = c.complete(None, "%colors noco") + assert ["nocolor"] == matches + +def test_match_dict_keys(): + """ + Test that match_dict_keys works on a couple of use case does return what + expected, and does not crash + """ + delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?" + + def match(*args, **kwargs): + quote, offset, matches = match_dict_keys(*args, delims=delims, **kwargs) + return quote, offset, list(matches) + + keys = ["foo", b"far"] + assert match(keys, "b'") == ("'", 2, ["far"]) + assert match(keys, "b'f") == ("'", 2, ["far"]) + assert match(keys, 'b"') == ('"', 2, ["far"]) + assert match(keys, 'b"f') == ('"', 2, ["far"]) + + assert match(keys, "'") == ("'", 1, ["foo"]) + assert match(keys, "'f") == ("'", 1, ["foo"]) + assert match(keys, '"') == ('"', 1, ["foo"]) + assert match(keys, '"f') == ('"', 1, ["foo"]) + + # Completion on first item of tuple + keys = [("foo", 1111), ("foo", 2222), (3333, "bar"), (3333, "test")] + assert match(keys, "'f") == ("'", 1, ["foo"]) + assert match(keys, "33") == ("", 0, ["3333"]) + + # Completion on numbers + keys = [ + 0xDEADBEEF, + 1111, + 1234, + "1999", + 0b10101, + 22, + ] # 0xDEADBEEF = 3735928559; 0b10101 = 21 + assert match(keys, "0xdead") == ("", 0, ["0xdeadbeef"]) + assert match(keys, "1") == ("", 0, ["1111", "1234"]) + assert match(keys, "2") == ("", 0, ["21", "22"]) + assert match(keys, "0b101") == ("", 0, ["0b10101", "0b10110"]) + + # Should yield on variables + assert match(keys, "a_variable") == ("", 0, []) + + # Should pass over invalid literals + assert match(keys, "'' ''") == ("", 0, []) + +def test_match_dict_keys_tuple(): + """ + Test that match_dict_keys called with extra prefix works on a couple of use case, + does return what expected, and does not crash. + """ + delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?" + + keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ("other", "test")] + + def match(*args, extra=None, **kwargs): + quote, offset, matches = match_dict_keys( + *args, delims=delims, extra_prefix=extra, **kwargs + ) + return quote, offset, list(matches) + + # Completion on first key == "foo" + assert match(keys, "'", extra=("foo",)) == ("'", 1, ["bar", "oof"]) + assert match(keys, '"', extra=("foo",)) == ('"', 1, ["bar", "oof"]) + assert match(keys, "'o", extra=("foo",)) == ("'", 1, ["oof"]) + assert match(keys, '"o', extra=("foo",)) == ('"', 1, ["oof"]) + assert match(keys, "b'", extra=("foo",)) == ("'", 2, ["bar"]) + assert match(keys, 'b"', extra=("foo",)) == ('"', 2, ["bar"]) + assert match(keys, "b'b", extra=("foo",)) == ("'", 2, ["bar"]) + assert match(keys, 'b"b', extra=("foo",)) == ('"', 2, ["bar"]) + + # No Completion + assert match(keys, "'", extra=("no_foo",)) == ("'", 1, []) + assert match(keys, "'", extra=("fo",)) == ("'", 1, []) + + keys = [("foo1", "foo2", "foo3", "foo4"), ("foo1", "foo2", "bar", "foo4")] + assert match(keys, "'foo", extra=("foo1",)) == ("'", 1, ["foo2"]) + assert match(keys, "'foo", extra=("foo1", "foo2")) == ("'", 1, ["foo3"]) + assert match(keys, "'foo", extra=("foo1", "foo2", "foo3")) == ("'", 1, ["foo4"]) + assert match(keys, "'foo", extra=("foo1", "foo2", "foo3", "foo4")) == ( + "'", + 1, + [], + ) + + keys = [("foo", 1111), ("foo", "2222"), (3333, "bar"), (3333, 4444)] + assert match(keys, "'", extra=("foo",)) == ("'", 1, ["2222"]) + assert match(keys, "", extra=("foo",)) == ("", 0, ["1111", "'2222'"]) + assert match(keys, "'", extra=(3333,)) == ("'", 1, ["bar"]) + assert match(keys, "", extra=(3333,)) == ("", 0, ["'bar'", "4444"]) + assert match(keys, "'", extra=("3333",)) == ("'", 1, []) + assert match(keys, "33") == ("", 0, ["3333"]) + +def test_dict_key_completion_closures(): + ip = get_ipython() + complete = ip.Completer.complete + ip.Completer.auto_close_dict_keys = True + + ip.user_ns["d"] = { + # tuple only + ("aa", 11): None, + # tuple and non-tuple + ("bb", 22): None, + "bb": None, + # non-tuple only + "cc": None, + # numeric tuple only + (77, "x"): None, + # numeric tuple and non-tuple + (88, "y"): None, + 88: None, + # numeric non-tuple only + 99: None, + } + + _, matches = complete(line_buffer="d[") + # should append `, ` if matches a tuple only + assert "'aa' in ", matches + # should not append anything if matches a tuple and an item + assert "'bb'" in matches + # should append `]` if matches and item only + assert "'cc']" in matches + + # should append `, ` if matches a tuple only + assert "77 in ", matches + # should not append anything if matches a tuple and an item + assert "88" in matches + # should append `]` if matches and item only + assert "99]" in matches + + _, matches = complete(line_buffer="d['aa', ") + # should restrict matches to those matching tuple prefix + assert "11]" in matches + assert "'bb'" not in matches + assert "'bb' not in ", matches + assert "'bb']" not in matches + assert "'cc'" not in matches + assert "'cc' not in ", matches + assert "'cc']" not in matches + ip.Completer.auto_close_dict_keys = False + +def test_dict_key_completion_string(): + """Test dictionary key completion for string keys""" + ip = get_ipython() + complete = ip.Completer.complete + + ip.user_ns["d"] = {"abc": None} + + # check completion at different stages + _, matches = complete(line_buffer="d[") + assert "'abc'" in matches + assert "'abc']" not in matches + + _, matches = complete(line_buffer="d['") + assert "abc" in matches + assert "abc']" not in matches + + _, matches = complete(line_buffer="d['a") + assert "abc" in matches + assert "abc']" not in matches + + # check use of different quoting + _, matches = complete(line_buffer='d["') + assert "abc" in matches + assert 'abc"]' not in matches + + _, matches = complete(line_buffer='d["a') + assert "abc" in matches + assert 'abc"]' not in matches + + # check sensitivity to following context + _, matches = complete(line_buffer="d[]", cursor_pos=2) + assert "'abc'" in matches + + _, matches = complete(line_buffer="d['']", cursor_pos=3) + assert "abc" in matches + assert "abc'" not in matches + assert "abc']" not in matches + + # check multiple solutions are correctly returned and that noise is not + ip.user_ns["d"] = { + "abc": None, + "abd": None, + "bad": None, + object(): None, + 5: None, + ("abe", None): None, + (None, "abf"): None, + } + + _, matches = complete(line_buffer="d['a") + assert "abc" in matches + assert "abd" in matches + assert "bad" not in matches + assert "abe" not in matches + assert "abf" not in matches + assert not any(m.endswith(("]", '"', "'")) for m in matches), matches + + # check escaping and whitespace + ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None} + _, matches = complete(line_buffer="d['a") + assert "a\\nb" in matches + assert "a\\'b" in matches + assert 'a"b' in matches + assert "a word" in matches + assert not any(m.endswith(("]", '"', "'")) for m in matches), matches + + # - can complete on non-initial word of the string + _, matches = complete(line_buffer="d['a w") + assert "word" in matches + + # - understands quote escaping + _, matches = complete(line_buffer="d['a\\'") + assert "b" in matches + + # - default quoting should work like repr + _, matches = complete(line_buffer="d[") + assert '"a\'b"' in matches + + # - when opening quote with ", possible to match with unescaped apostrophe + _, matches = complete(line_buffer="d[\"a'") + assert "b" in matches + + # need to not split at delims that readline won't split at + if "-" not in ip.Completer.splitter.delims: + ip.user_ns["d"] = {"before-after": None} + _, matches = complete(line_buffer="d['before-af") + assert "before-after" in matches + + # check completion on tuple-of-string keys at different stage - on first key + ip.user_ns["d"] = {("foo", "bar"): None} + _, matches = complete(line_buffer="d[") + assert "'foo'" in matches + assert "'foo']" not in matches + assert "'bar'" not in matches + assert "foo" not in matches + assert "bar" not in matches + + # - match the prefix + _, matches = complete(line_buffer="d['f") + assert "foo" in matches + assert "foo']" not in matches + assert 'foo"]' not in matches + _, matches = complete(line_buffer="d['foo") + assert "foo" in matches + + # - can complete on second key + _, matches = complete(line_buffer="d['foo', ") + assert "'bar'" in matches + _, matches = complete(line_buffer="d['foo', 'b") + assert "bar" in matches + assert "foo" not in matches + + # - does not propose missing keys + _, matches = complete(line_buffer="d['foo', 'f") + assert "bar" not in matches + assert "foo" not in matches + + # check sensitivity to following context + _, matches = complete(line_buffer="d['foo',]", cursor_pos=8) + assert "'bar'" in matches + assert "bar" not in matches + assert "'foo'" not in matches + assert "foo" not in matches + + _, matches = complete(line_buffer="d['']", cursor_pos=3) + assert "foo" in matches + assert not any(m.endswith(("]", '"', "'")) for m in matches), matches + + _, matches = complete(line_buffer='d[""]', cursor_pos=3) + assert "foo" in matches + assert not any(m.endswith(("]", '"', "'")) for m in matches), matches + + _, matches = complete(line_buffer='d["foo","]', cursor_pos=9) + assert "bar" in matches + assert not any(m.endswith(("]", '"', "'")) for m in matches), matches + + _, matches = complete(line_buffer='d["foo",]', cursor_pos=8) + assert "'bar'" in matches + assert "bar" not in matches + + # Can complete with longer tuple keys + ip.user_ns["d"] = {("foo", "bar", "foobar"): None} + + # - can complete second key + _, matches = complete(line_buffer="d['foo', 'b") + assert "bar" in matches + assert "foo" not in matches + assert "foobar" not in matches + + # - can complete third key + _, matches = complete(line_buffer="d['foo', 'bar', 'fo") + assert "foobar" in matches + assert "foo" not in matches + assert "bar" not in matches + +def test_dict_key_completion_numbers(): + ip = get_ipython() + complete = ip.Completer.complete + + ip.user_ns["d"] = { + 0xDEADBEEF: None, # 3735928559 + 1111: None, + 1234: None, + "1999": None, + 0b10101: None, # 21 + 22: None, + } + _, matches = complete(line_buffer="d[1") + assert "1111" in matches + assert "1234" in matches + assert "1999" not in matches + assert "'1999'" not in matches + + _, matches = complete(line_buffer="d[0xdead") + assert "0xdeadbeef" in matches + + _, matches = complete(line_buffer="d[2") + assert "21" in matches + assert "22" in matches + + _, matches = complete(line_buffer="d[0b101") + assert "0b10101" in matches + assert "0b10110" in matches + +def test_dict_key_completion_contexts(): + """Test expression contexts in which dict key completion occurs""" + ip = get_ipython() + complete = ip.Completer.complete + d = {"abc": None} + ip.user_ns["d"] = d + + class C: + data = d + + ip.user_ns["C"] = C + ip.user_ns["get"] = lambda: d + ip.user_ns["nested"] = {"x": d} + + def assert_no_completion(**kwargs): + _, matches = complete(**kwargs) + assert "abc" not in matches + assert "abc'" not in matches + assert "abc']" not in matches + assert "'abc'" not in matches + assert "'abc']" not in matches + + def assert_completion(**kwargs): + _, matches = complete(**kwargs) + assert "'abc'" in matches + assert "'abc']" not in matches + + # no completion after string closed, even if reopened + assert_no_completion(line_buffer="d['a'") + assert_no_completion(line_buffer='d["a"') + assert_no_completion(line_buffer="d['a' + ") + assert_no_completion(line_buffer="d['a' + '") + + # completion in non-trivial expressions + assert_completion(line_buffer="+ d[") + assert_completion(line_buffer="(d[") + assert_completion(line_buffer="C.data[") + + # nested dict completion + assert_completion(line_buffer="nested['x'][") + + with evaluation_policy("minimal"): + with pytest.raises(AssertionError): + assert_completion(line_buffer="nested['x'][") + + # greedy flag + def assert_completion(**kwargs): + _, matches = complete(**kwargs) + assert "get()['abc']" in matches + + assert_no_completion(line_buffer="get()[") + with greedy_completion(): + assert_completion(line_buffer="get()[") + assert_completion(line_buffer="get()['") + assert_completion(line_buffer="get()['a") + assert_completion(line_buffer="get()['ab") + assert_completion(line_buffer="get()['abc") + +def test_func_kw_matches_respects_evaluation_policy(): + ip = get_ipython() + complete = ip.Completer.complete + evaluated = [] + + class Proxy: + @property + def handler(self): + evaluated.append("handler") + return lambda timeout=1: None + + def plain(alpha=1): + pass + + ip.user_ns["proxy"] = Proxy() + ip.user_ns["plain"] = plain + + with jedi_status(False): + with evaluation_policy("limited"): + _, matches = complete(line_buffer="proxy.handler(") + assert "timeout=" not in matches + assert evaluated == [] + + # plain callables are unaffected + _, matches = complete(line_buffer="plain(") + assert "alpha=" in matches + + with evaluation_policy("unsafe"): + _, matches = complete(line_buffer="proxy.handler(") + assert "timeout=" in matches + assert evaluated == ["handler"] + +@pytest.mark.parametrize( + "line", + [ + # the cases from https://github.com/ipython/ipython/issues/15392 + 'd["comp', + 'print(d["comp', + 'd["bar"]; d["comp', + 'x = d["comp', + 'd["k"] = d["comp', + 'for k in d["comp', + 'lambda: d["comp', + 'd["bar"] * d["comp', + # the same shapes with other keywords and operators + 'not d["comp', + 'x = 1 if y else d["comp', + '[k for k in d["comp', + 'f(a=d["comp', + 'x: dict = d["comp', + 'await d["comp', + ], +) +def test_dict_key_completion_after_statement_or_operator(line): + """Dict key completion when the subscript follows an assignment, keyword or operator.""" + ip = get_ipython() + ip.user_ns["d"] = {"complete_me": 1, "bar": 2} + with jedi_status(False): + _, matches = ip.Completer.complete(line_buffer=line) + assert "complete_me" in matches + + +@pytest.mark.parametrize( + "line", + [ + 'x = df["Ye', + 'df["k"] = df["Ye', + 'df["b"] * df["Ye', + 'x = df.loc[:, "Ye', + ], +) +def test_dataframe_key_completion_after_statement_or_operator(line): + """Same as above for a DataFrame, as reported in issue #15392.""" + pandas = pytest.importorskip("pandas") + ip = get_ipython() + ip.user_ns["df"] = pandas.DataFrame({"Year": [1], "Yield": [2], "b": [3]}) + with jedi_status(False): + _, matches = ip.Completer.complete(line_buffer=line) + assert "Year" in matches + + +@pytest.mark.parametrize( + "line", + [ + '%timeit d["comp', + '%timeit -n 2 -r 1 d["comp', + '%time d["comp', + '%prun d["comp', + '%%timeit d["comp', + # implicit magic, with flags + 'timeit -n 2 -r 1 d["comp', + ], +) +def test_dict_key_completion_in_magic_argument(line): + """Dict key completion in the code argument of a line or cell magic.""" + ip = get_ipython() + ip.user_ns["d"] = {"complete_me": 1, "bar": 2} + with jedi_status(False): + _, matches = ip.Completer.complete(line_buffer=line) + assert "complete_me" in matches + + +def test_completion_autoimport(): + ip = get_ipython() + complete = ip.Completer.complete + with ( + evaluation_policy("limited", allow_auto_import=True), + jedi_status(False), + ): + _, matches = complete(line_buffer="math.") + assert ".pi" in matches + +def test_completion_no_autoimport(): + ip = get_ipython() + complete = ip.Completer.complete + with ( + evaluation_policy("limited", allow_auto_import=False), + jedi_status(False), + ): + _, matches = complete(line_buffer="math.") + assert ".pi" not in matches + +def test_completion_allow_custom_getattr_per_module(): + factory_code = textwrap.dedent( + """ + class ListFactory: + def __getattr__(self, attr): + return [] + """ + ) + + safe_lib = types.ModuleType("my.safe.lib") + sys.modules["my.safe.lib"] = safe_lib + exec(factory_code, safe_lib.__dict__) + + unsafe_lib = types.ModuleType("my.unsafe.lib") + sys.modules["my.unsafe.lib"] = unsafe_lib + exec(factory_code, unsafe_lib.__dict__) + + fake_safe_lib = types.ModuleType("my_fake_lib") + sys.modules["my_fake_lib"] = fake_safe_lib + exec(factory_code, fake_safe_lib.__dict__) + + ip = get_ipython() + ip.user_ns["safe_list_factory"] = safe_lib.ListFactory() + ip.user_ns["unsafe_list_factory"] = unsafe_lib.ListFactory() + ip.user_ns["fake_safe_factory"] = fake_safe_lib.ListFactory() + complete = ip.Completer.complete + with ( + evaluation_policy("limited", allowed_getattr_external={"my.safe.lib"}), + jedi_status(False), + ): + _, matches = complete(line_buffer="safe_list_factory.example.") + assert ".append" in matches + # this also checks against https://github.com/ipython/ipython/issues/14916 + # because removing "un" would cause this test to incorrectly pass + _, matches = complete(line_buffer="unsafe_list_factory.example.") + assert ".append" not in matches + + sys.modules["my"] = types.ModuleType("my") + + with ( + evaluation_policy("limited", allowed_getattr_external={"my"}), + jedi_status(False), + ): + _, matches = complete(line_buffer="safe_list_factory.example.") + assert ".append" in matches + _, matches = complete(line_buffer="unsafe_list_factory.example.") + assert ".append" in matches + _, matches = complete(line_buffer="fake_safe_factory.example.") + assert ".append" not in matches + + with ( + evaluation_policy("limited"), + jedi_status(False), + ): + _, matches = complete(line_buffer="safe_list_factory.example.") + assert ".append" not in matches + _, matches = complete(line_buffer="unsafe_list_factory.example.") + assert ".append" not in matches + +def test_completion_allow_subclass_of_trusted_module(): + factory_code = textwrap.dedent( + """ + class ListFactory: + def __getattr__(self, attr): + return [] + """ + ) + trusted_lib = types.ModuleType("my.trusted.lib") + sys.modules["my.trusted.lib"] = trusted_lib + exec(factory_code, trusted_lib.__dict__) + + ip = get_ipython() + # Create a subclass in __main__ (untrusted namespace) + subclass_code = textwrap.dedent( + """ + class SubclassFactory(trusted_lib.ListFactory): + pass + """ + ) + ip.user_ns["trusted_lib"] = trusted_lib + exec(subclass_code, ip.user_ns) + ip.user_ns["subclass_factory"] = ip.user_ns["SubclassFactory"]() + complete = ip.Completer.complete + with ( + evaluation_policy("limited", allowed_getattr_external={"my.trusted.lib"}), + jedi_status(False), + ): + _, matches = complete(line_buffer="subclass_factory.example.") + assert ".append" in matches + + # Test that overriding __getattr__ in subclass in untrusted namespace prevents completion + overriding_subclass_code = textwrap.dedent( + """ + class OverridingSubclass(trusted_lib.ListFactory): + def __getattr__(self, attr): + return {} + """ + ) + exec(overriding_subclass_code, ip.user_ns) + ip.user_ns["overriding_factory"] = ip.user_ns["OverridingSubclass"]() + + with ( + evaluation_policy("limited", allowed_getattr_external={"my.trusted.lib"}), + jedi_status(False), + ): + _, matches = complete(line_buffer="overriding_factory.example.") + assert ".append" not in matches + assert ".keys" not in matches + +def test_completion_fallback_to_annotation_for_attribute(): + code = textwrap.dedent( + """ + class StringMethods: + def a(): + pass + + class Test: + str: StringMethods + def __init__(self): + self.str = StringMethods() + def __getattr__(self, name): + raise AttributeError(f"{name} not found") + """ + ) + + repro = types.ModuleType("repro") + sys.modules["repro"] = repro + exec(code, repro.__dict__) + + ip = get_ipython() + ip.user_ns["repro"] = repro + exec("r = repro.Test()", ip.user_ns) + + complete = ip.Completer.complete + try: + with evaluation_policy("limited"), jedi_status(False): + _, matches = complete(line_buffer="r.str.") + assert ".a" in matches + finally: + sys.modules.pop("repro", None) + ip.user_ns.pop("r", None) + +def test_policy_warnings(): + with pytest.warns(UserWarning): + with evaluation_policy("unsafe", allowed_getattr_external=[]): + pass + + with pytest.warns(UserWarning): + with evaluation_policy("limited", test=[]): + pass + +def test_dict_key_completion_bytes(): + """Test handling of bytes in dict key completion""" + ip = get_ipython() + complete = ip.Completer.complete + + ip.user_ns["d"] = {"abc": None, b"abd": None} + + _, matches = complete(line_buffer="d[") + assert "'abc'" in matches + assert "b'abd'" in matches + + if False: # not currently implemented + _, matches = complete(line_buffer="d[b") + assert "b'abd'" in matches + assert "b'abc'" not in matches + + _, matches = complete(line_buffer="d[b'") + assert "abd" in matches + assert "abc" not in matches + + _, matches = complete(line_buffer="d[B'") + assert "abd" in matches + assert "abc" not in matches + + _, matches = complete(line_buffer="d['") + assert "abc" in matches + assert "abd" not in matches + +def test_dict_key_completion_unicode_py3(): + """Test handling of unicode in dict key completion""" + ip = get_ipython() + complete = ip.Completer.complete + + ip.user_ns["d"] = {"a\u05d0": None} + + # query using escape + if sys.platform != "win32": + # Known failure on Windows + _, matches = complete(line_buffer="d['a\\u05d0") + assert "u05d0" in matches # tokenized after \\ + + # query using character + _, matches = complete(line_buffer="d['a\u05d0") + assert "a\u05d0" in matches + + with greedy_completion(): + # query using escape + _, matches = complete(line_buffer="d['a\\u05d0") + assert "d['a\\u05d0']" in matches # tokenized after \\ + + # query using character + _, matches = complete(line_buffer="d['a\u05d0") + assert "d['a\u05d0']" in matches + +@dec.skip_without("numpy") +def test_struct_array_key_completion(): + """Test dict key completion applies to numpy struct arrays""" + import numpy + + ip = get_ipython() + complete = ip.Completer.complete + ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")]) + _, matches = complete(line_buffer="d['") + assert "hello" in matches + assert "world" in matches + # complete on the numpy struct itself + dt = numpy.dtype( + [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)] + ) + x = numpy.zeros(2, dtype=dt) + ip.user_ns["d"] = x[1] + _, matches = complete(line_buffer="d['") + assert "my_head" in matches + assert "my_data" in matches + + def completes_on_nested(): + ip.user_ns["d"] = numpy.zeros(2, dtype=dt) + _, matches = complete(line_buffer="d[1]['my_head']['") + assert any(["my_dt" in m for m in matches]) + assert any(["my_df" in m for m in matches]) + + # complete on a nested level + with greedy_completion(): + completes_on_nested() + + with evaluation_policy("limited"): + completes_on_nested() + + with evaluation_policy("minimal"): + with pytest.raises(AssertionError): + completes_on_nested() + +@dec.skip_without("pandas") +def test_dataframe_key_completion(): + """Test dict key completion applies to pandas DataFrames""" + import pandas + + ip = get_ipython() + complete = ip.Completer.complete + ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]}) + _, matches = complete(line_buffer="d['") + assert "hello" in matches + assert "world" in matches + _, matches = complete(line_buffer="d.loc[:, '") + assert "hello" in matches + assert "world" in matches + _, matches = complete(line_buffer="d.loc[1:, '") + assert "hello" in matches + _, matches = complete(line_buffer="d.loc[1:1, '") + assert "hello" in matches + _, matches = complete(line_buffer="d.loc[1:1:-1, '") + assert "hello" in matches + _, matches = complete(line_buffer="d.loc[::, '") + assert "hello" in matches + +def test_dict_key_completion_invalids(): + """Smoke test cases dict key completion can't handle""" + ip = get_ipython() + complete = ip.Completer.complete + + ip.user_ns["no_getitem"] = None + ip.user_ns["no_keys"] = [] + ip.user_ns["cant_call_keys"] = dict + ip.user_ns["empty"] = {} + ip.user_ns["d"] = {"abc": 5} + + _, matches = complete(line_buffer="no_getitem['") + _, matches = complete(line_buffer="no_keys['") + _, matches = complete(line_buffer="cant_call_keys['") + _, matches = complete(line_buffer="empty['") + _, matches = complete(line_buffer="name_error['") + _, matches = complete(line_buffer="d['\\") # incomplete escape + +def test_object_key_completion(): + ip = get_ipython() + ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"]) + + _, matches = ip.Completer.complete(line_buffer="key_completable['qw") + assert "qwerty" in matches + assert "qwick" in matches + +def test_class_key_completion(): + ip = get_ipython() + NamedInstanceClass("qwerty") + NamedInstanceClass("qwick") + ip.user_ns["named_instance_class"] = NamedInstanceClass + + _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw") + assert "qwerty" in matches + assert "qwick" in matches + +def test_tryimport(): + """ + Test that try-import don't crash on trailing dot, and import modules before + """ + from IPython.core.completerlib import try_import + + assert try_import("IPython.") + +def test_aimport_module_completer(): + ip = get_ipython() + _, matches = ip.complete("i", "%aimport i") + assert "io" in matches + assert "int" not in matches + +def test_nested_import_module_completer(): + ip = get_ipython() + _, matches = ip.complete(None, "import IPython.co", 17) + assert "IPython.core" in matches + assert "import IPython.core" not in matches + assert "IPython.display" not in matches + +def test_import_module_completer(): + ip = get_ipython() + _, matches = ip.complete("i", "import i") + assert "io" in matches + assert "int" not in matches + +def test_from_module_completer(): + ip = get_ipython() + _, matches = ip.complete("B", "from io import B", 16) + assert "BytesIO" in matches + assert "BaseException" not in matches + +def test_snake_case_completion(): + ip = get_ipython() + ip.Completer.use_jedi = False + ip.user_ns["some_three"] = 3 + ip.user_ns["some_four"] = 4 + _, matches = ip.complete("s_", "print(s_f") + assert "some_three" in matches + assert "some_four" in matches + +def test_mix_terms(): + ip = get_ipython() + from textwrap import dedent + + ip.Completer.use_jedi = False + ip.ex( + dedent( + """ + class Test: + def meth(self, meth_arg1): + print("meth") + + def meth_1(self, meth1_arg1, meth1_arg2): + print("meth1") + + def meth_2(self, meth2_arg1, meth2_arg2): + print("meth2") + test = Test() + """ + ) + ) + _, matches = ip.complete(None, "test.meth(") + assert "meth_arg1=" in matches + assert "meth2_arg1=" not in matches + +def test_percent_symbol_restrict_to_magic_completions(): + ip = get_ipython() + completer = ip.Completer + text = "%a" + + with provisionalcompleter(): + completer.use_jedi = True + completions = completer.completions(text, len(text)) + for c in completions: + assert c.text[0] == "%" + +def test_fwd_unicode_restricts(): + ip = get_ipython() + completer = ip.Completer + text = "\\ROMAN NUMERAL FIVE" + + with provisionalcompleter(): + completer.use_jedi = True + completions = [ + completion.text for completion in completer.completions(text, len(text)) + ] + assert completions == ["\u2164"] + +def test_dict_key_restrict_to_dicts(): + """Test that dict key suppresses non-dict completion items""" + ip = get_ipython() + c = ip.Completer + d = {"abc": None} + ip.user_ns["d"] = d + + text = 'd["a' + + def _(): + with provisionalcompleter(): + c.use_jedi = True + return [ + completion.text for completion in c.completions(text, len(text)) + ] + + completions = _() + assert completions == ["abc"] + + # check that it can be disabled in granular manner: + cfg = Config() + cfg.IPCompleter.suppress_competing_matchers = { + "IPCompleter.dict_key_matcher": False + } + c.update_config(cfg) + + completions = _() + assert "abc" in completions + assert len(completions) > 1 + +def test_matcher_suppression(): + @completion_matcher(identifier="a_matcher") + def a_matcher(text): + return ["completion_a"] + + @completion_matcher(identifier="b_matcher", api_version=2) + def b_matcher(context: CompletionContext): + text = context.token + result = {"completions": [SimpleCompletion("completion_b")]} + + if text == "suppress c": + result["suppress"] = {"c_matcher"} + + if text.startswith("suppress all"): + result["suppress"] = True + if text == "suppress all but c": + result["do_not_suppress"] = {"c_matcher"} + if text == "suppress all but a": + result["do_not_suppress"] = {"a_matcher"} + + return result + + @completion_matcher(identifier="c_matcher") + def c_matcher(text): + return ["completion_c"] + + with custom_matchers([a_matcher, b_matcher, c_matcher]): + ip = get_ipython() + c = ip.Completer + + def _(text, expected): + c.use_jedi = False + s, matches = c.complete(text) + assert expected == matches + + _("do not suppress", ["completion_a", "completion_b", "completion_c"]) + _("suppress all", ["completion_b"]) + _("suppress all but a", ["completion_a", "completion_b"]) + _("suppress all but c", ["completion_b", "completion_c"]) + + def configure(suppression_config): + cfg = Config() + cfg.IPCompleter.suppress_competing_matchers = suppression_config + c.update_config(cfg) + + # test that configuration takes priority over the run-time decisions + + configure(False) + _("suppress all", ["completion_a", "completion_b", "completion_c"]) + + configure({"b_matcher": False}) + _("suppress all", ["completion_a", "completion_b", "completion_c"]) + + configure({"a_matcher": False}) + _("suppress all", ["completion_b"]) + + configure({"b_matcher": True}) + _("do not suppress", ["completion_b"]) + + configure(True) + _("do not suppress", ["completion_a"]) + +def test_matcher_suppression_with_iterator(): + @completion_matcher(identifier="matcher_returning_iterator") + def matcher_returning_iterator(text): + return iter(["completion_iter"]) + + @completion_matcher(identifier="matcher_returning_list") + def matcher_returning_list(text): + return ["completion_list"] + + with custom_matchers([matcher_returning_iterator, matcher_returning_list]): + ip = get_ipython() + c = ip.Completer + + def _(text, expected): + c.use_jedi = False + s, matches = c.complete(text) + assert expected == matches + + def configure(suppression_config): + cfg = Config() + cfg.IPCompleter.suppress_competing_matchers = suppression_config + c.update_config(cfg) + + configure(False) + _("---", ["completion_iter", "completion_list"]) + + configure(True) + _("---", ["completion_iter"]) + + configure(None) + _("--", ["completion_iter", "completion_list"]) + +@pytest.mark.xfail( + sys.version_info.releaselevel in ("alpha",), + reason="Parso does not yet parse 3.13", +) +def test_matcher_suppression_with_jedi(): + ip = get_ipython() + c = ip.Completer + c.use_jedi = True + + def configure(suppression_config): + cfg = Config() + cfg.IPCompleter.suppress_competing_matchers = suppression_config + c.update_config(cfg) + + def _(): + with provisionalcompleter(): + matches = [completion.text for completion in c.completions("dict.", 5)] + assert "keys" in matches + + configure(False) + _() + + configure(True) + _() + + configure(None) + _() + +def test_matcher_disabling(): + @completion_matcher(identifier="a_matcher") + def a_matcher(text): + return ["completion_a"] + + @completion_matcher(identifier="b_matcher") + def b_matcher(text): + return ["completion_b"] + + def _(expected): + s, matches = c.complete("completion_") + assert expected == matches + + with custom_matchers([a_matcher, b_matcher]): + ip = get_ipython() + c = ip.Completer + + _(["completion_a", "completion_b"]) + + cfg = Config() + cfg.IPCompleter.disable_matchers = ["b_matcher"] + c.update_config(cfg) + + _(["completion_a"]) + + cfg.IPCompleter.disable_matchers = [] + c.update_config(cfg) + +def test_matcher_priority(): + @completion_matcher(identifier="a_matcher", priority=0, api_version=2) + def a_matcher(text): + return {"completions": [SimpleCompletion("completion_a")], "suppress": True} + + @completion_matcher(identifier="b_matcher", priority=2, api_version=2) + def b_matcher(text): + return {"completions": [SimpleCompletion("completion_b")], "suppress": True} + + def _(expected): + s, matches = c.complete("completion_") + assert expected == matches + + with custom_matchers([a_matcher, b_matcher]): + ip = get_ipython() + c = ip.Completer + + _(["completion_b"]) + a_matcher.matcher_priority = 3 + _(["completion_a"]) + +def test_private_attr_completions(): + ip = get_ipython() + ip.user_ns["Test"] = type("Test", (), {"_test1": 1, "__test2": 2}) + ip.user_ns["t"] = ip.user_ns["Test"]() + ip.Completer.use_jedi = False + + try: + with provisionalcompleter(): + completions = list(ip.Completer.completions(text="t._", offset=3)) + completion_texts = [c.text for c in completions] + + assert ( + "._test1" in completion_texts + ), f"_test1 not found in {completion_texts}" + assert ( + ".__test2" in completion_texts + ), f"__test2 not found in {completion_texts}" + finally: + del ip.user_ns["Test"] + del ip.user_ns["t"] + + +@pytest.mark.parametrize( + "use_jedi,evaluation", + [ + [True, "minimal"], + [False, "limited"], + ], +) +@pytest.mark.parametrize( + "code,insert_text", + [ + [ + "\n".join( + [ + "class NotYetDefined:", + " def my_method(self) -> str:", + " return 1", + "my_instance = NotYetDefined()", + "my_insta", + ] + ), + "my_instance", + ], + [ + "\n".join( + [ + "class NotYetDefined:", + " def my_method(self) -> str:", + " return 1", + "instance = NotYetDefined()", + "instance.", + ] + ), + "my_method", + ], + [ + "\n".join( + [ + "class NotYetDefined:", + " def my_method(self) -> str:", + " return 1", + "my_instance = NotYetDefined()", + "my_instance.my_method().", + ] + ), + "capitalize", + ], + [ + "\n".join( + [ + "class NotYetDefined:", + " def my_method(self):", + " return []", + "my_instance = NotYetDefined()", + "my_instance.my_method().", + ] + ), + "append", + ], + [ + "\n".join( + [ + "class NotYetDefined:", + " @property", + " def my_property(self):", + " return 1.1", + "my_instance = NotYetDefined()", + "my_instance.my_property.", + ] + ), + "as_integer_ratio", + ], + [ + "\n".join( + [ + "my_instance = 1.1", + "assert my_instance.", + ] + ), + "as_integer_ratio", + ], + [ + "\n".join( + [ + "def my_test() -> float:", + " pass", + "my_test().", + ] + ), + "as_integer_ratio", + ], + [ + "\n".join( + [ + "def my_test():", + " return {}", + "my_test().", + ] + ), + "keys", + ], + [ + "\n".join( + [ + "l = []", + "def my_test():", + " return l", + "my_test().", + ] + ), + "append", + ], + [ + "\n".join( + [ + "num = {1: 'one'}", + "num[2] = 'two'", + "num.", + ] + ), + "keys", + ], + [ + "\n".join( + [ + "num = {1: 'one'}", + "num[2] = ['two']", + "num[2].", + ] + ), + "append", + ], + [ + "\n".join( + [ + "l = []", + "class NotYetDefined:", + " def my_method(self):", + " return l", + "my_instance = NotYetDefined()", + "my_instance.my_method().", + ] + ), + "append", + ], + [ + "\n".join( + [ + "def string_or_int(flag):", + " if flag:", + " return 'test'", + " return 1", + "string_or_int().", + ] + ), + ["capitalize", "as_integer_ratio"], + ], + [ + "\n".join( + [ + "def foo():", + " l = []", + " return l", + "foo().", + ] + ), + "append", + ], + [ + "\n".join( + [ + "class NotYetDefined:", + " def __init__(self):", + " self.test = []", + "instance = NotYetDefined()", + "instance.", + ] + ), + "test", + ], + [ + "\n".join( + [ + "class NotYetDefined:", + " def __init__(instance):", + " instance.test = []", + "instance = NotYetDefined()", + "instance.test.", + ] + ), + "append", + ], + [ + "\n".join( + [ + "class NotYetDefined:", + " def __init__(this):", + " this.test:str = []", + "instance = NotYetDefined()", + "instance.test.", + ] + ), + "capitalize", + ], + [ + "\n".join( + [ + "l = []", + "class NotYetDefined:", + " def __init__(me):", + " me.test = l", + "instance = NotYetDefined()", + "instance.test.", + ] + ), + "append", + ], + [ + "\n".join( + [ + "class NotYetDefined:", + " def test(self):", + " self.l = []", + " return self.l", + "instance = NotYetDefined()", + "instance.test().", + ] + ), + "append", + ], + [ + "\n".join( + [ + "class NotYetDefined:", + " def test():", + " return []", + "instance = NotYetDefined()", + "instance.test().", + ] + ), + "append", + ], + [ + "\n".join( + [ + "def foo():", + " if some_condition:", + " return {'top':{'mid':{'leaf': 2}}}", + " return {'top': {'mid':[]}}", + "foo()['top']['mid'].", + ] + ), + ["keys", "append"], + ], + [ + "\n".join( + [ + "def foo():", + " if some_condition:", + " return {'top':{'mid':{'leaf': 2}}}", + " return {'top': {'mid':[]}}", + "foo()['top']['mid']['leaf'].", + ] + ), + "as_integer_ratio", + ], + [ + "\n".join( + [ + "async def async_func():", + " return []", + "async_func().", + ] + ), + "cr_await", + ], + [ + "\n".join( + [ + "async def async_func():", + " return []", + "(await async_func()).", + ] + ), + "append", + ], + [ + "\n".join(["t = []", "if some_condition:", " t."]), + "append", + ], + [ + "\n".join( + [ + "t = []", + "if some_condition:", + " t = 'string'", + "t.", + ] + ), + ["append", "capitalize"], + ], + [ + "\n".join( + [ + "t = []", + "if some_condition:", + " t = 'string'", + "else:", + " t.", + ] + ), + "append", + ], + [ + "\n".join( + [ + "t = []", + "if some_condition:", + " t = 'string'", + "else:", + " t = 1", + "t.", + ] + ), + ["append", "capitalize", "as_integer_ratio"], + ], + [ + "\n".join( + [ + "t = []", + "if condition_1:", + " t = 'string'", + "elif condition_2:", + " t = 1", + "elif condition_3:", + " t.", + ] + ), + "append", + ], + [ + "\n".join( + [ + "t = []", + "if condition_1:", + " t = 'string'", + "elif condition_2:", + " t = 1", + "elif condition_3:", + " t = {}", + "t.", + ] + ), + ["append", "capitalize", "as_integer_ratio", "keys"], + ], + [ + "\n".join( + [ + "t = []", + "if condition_1:", + " if condition_2:", + " t = 'nested'", + "t.", + ] + ), + ["append", "capitalize"], + ], + [ + "\n".join( + [ + "a = []", + "while condition:", + " a.", + ] + ), + "append", + ], + [ + "\n".join( + [ + "t = []", + "while condition:", + " t = 'str'", + "t.", + ] + ), + ["append", "capitalize"], + ], + [ + "\n".join( + [ + "t = []", + "while condition_1:", + " while condition_2:", + " t = 'str'", + "t.", + ] + ), + ["append", "capitalize"], + ], + [ + "\n".join( + [ + "for i in range(10):", + " i.", + ] + ), + "bit_length", + ], + [ + "\n".join( + [ + "for i in range(10):", + " if i % 2 == 0:", + " i.", + ] + ), + "bit_length", + ], + [ + "\n".join( + [ + "for item in ['a', 'b', 'c']:", + " item.", + ] + ), + "capitalize", + ], + [ + "\n".join( + [ + "for key, value in {'a': 1, 'b': 2}.items():", + " key.", + ] + ), + "capitalize", + ], + [ + "\n".join( + [ + "for key, value in {'a': 1, 'b': 2}.items():", + " value.", + ] + ), + "bit_length", + ], + [ + "\n".join( + [ + "for sublist in [[1, 2], [3, 4]]:", + " sublist.", + ] + ), + "append", + ], + [ + "\n".join( + [ + "for sublist in [[1, 2], [3, 4]]:", + " for item in sublist:", + " item.", + ] + ), + "bit_length", + ], + [ + "\n".join( + [ + "t: list[str]", + "t[0].", + ] + ), + ["capitalize"], + ], + ], +) +def test_undefined_variables(use_jedi, evaluation, code, insert_text): + offset = len(code) + ip.Completer.use_jedi = use_jedi + ip.Completer.evaluation = evaluation + + with provisionalcompleter(): + completions = list(ip.Completer.completions(text=code, offset=offset)) + insert_texts = insert_text if isinstance(insert_text, list) else [insert_text] + for text in insert_texts: + match = [c for c in completions if c.text.lstrip(".") == text] + message_on_fail = f"{text} not found among {[c.text for c in completions]}" + assert len(match) == 1, message_on_fail + + +def test_attribute_completion_uses_current_buffer_redefinition(): + code = "\n".join(["a = []", "a."]) + ip = get_ipython() + ip.user_ns["a"] = 1 + + try: + with provisionalcompleter(), jedi_status(False), evaluation_policy("limited"): + completions = list(ip.Completer.completions(text=code, offset=len(code))) + finally: + del ip.user_ns["a"] + + completion_texts = {completion.text.lstrip(".") for completion in completions} + assert "append" in completion_texts + assert "as_integer_ratio" not in completion_texts + + +@pytest.mark.parametrize( + "code,insert_text", + [ + [ + "\n".join( + [ + "t: dict = {'a': []}", + "t['a'].", + ] + ), + ["append"], + ], + [ + "\n".join( + [ + "t: int | dict = {'a': []}", + "t.", + ] + ), + ["keys", "bit_length"], + ], + [ + "\n".join( + [ + "t: int | dict = {'a': []}", + "t['a'].", + ] + ), + "append", + ], + # Test union types + [ + "\n".join( + [ + "t: int | str", + "t.", + ] + ), + ["bit_length", "capitalize"], + ], + [ + "\n".join( + [ + "def func() -> int | str: pass", + "func().", + ] + ), + ["bit_length", "capitalize"], + ], + [ + "\n".join( + [ + "t: list = ['test']", + "t[0].", + ] + ), + ["capitalize"], + ], + [ + "\n".join( + [ + "class T:", + " @property", + " def p(self) -> int | str: pass", + "t = T()", + "t.p.", + ] + ), + ["bit_length", "capitalize"], + ], + ], +) +def test_undefined_variables_without_jedi(code, insert_text): + offset = len(code) + ip.Completer.use_jedi = False + ip.Completer.evaluation = "limited" + + with provisionalcompleter(): + completions = list(ip.Completer.completions(text=code, offset=offset)) + insert_texts = insert_text if isinstance(insert_text, list) else [insert_text] + for text in insert_texts: + match = [c for c in completions if c.text.lstrip(".") == text] + message_on_fail = f"{text} not found among {[c.text for c in completions]}" + assert len(match) == 1, message_on_fail + + +@pytest.mark.parametrize( + "code", + [ + "\n".join( + [ + "def my_test() -> float:", + " return 1.1", + "my_test().", + ] + ), + "\n".join( + [ + "class MyClass():", + " b: list[str]", + "x = MyClass()", + "x.b[0].", + ] + ), + "\n".join( + [ + "class MyClass():", + " b: list[str]", + "x = MyClass()", + "x.fake_attr().", + ] + ), + ], +) +def test_no_file_completions_in_attr_access(code): + """Test that files are not suggested during attribute/method completion.""" + with TemporaryWorkingDirectory(): + open(".hidden", "w", encoding="utf-8").close() + offset = len(code) + for use_jedi in (True, False): + with provisionalcompleter(), jedi_status(use_jedi): + completions = list(ip.Completer.completions(text=code, offset=offset)) + matches = [c for c in completions if c.text.lstrip(".") == "hidden"] + assert ( + len(matches) == 0 + ), f"File '.hidden' should not appear in attribute completion" + + +@pytest.mark.parametrize( + "line,expected", + [ + # Basic test cases + ("np.", "attribute"), + ("np.ran", "attribute"), + ("np.random.rand(np.random.ran", "attribute"), + ("np.random.rand(n", "global"), + ("d['k.e.y.'](ran", "global"), + ("d[0].k", "attribute"), + ("a = { 'a': np.ran", "attribute"), + ("n", "global"), + ("", "global"), + # Dots in string literals + ('some_var = "this is a string with a dot.', "global"), + ("text = 'another string with a dot.", "global"), + ('f"greeting {user.na', "attribute"), # Cursor in f-string expression + ('t"welcome {guest.na', "attribute"), # Cursor in t-string expression + ('f"hello {name} worl', "global"), # Cursor in f-string outside expression + ('f"hello {{a.', "global"), + ('f"hello {{{a.', "attribute"), + # Backslash escapes in strings + ('var = "string with \\"escaped quote and a dot.', "global"), + ("escaped = 'single \\'quote\\' with a dot.", "global"), + # Multi-line strings + ('multi = """This is line one\nwith a dot.', "global"), + ("multi_single = '''Another\nmulti-line\nwith a dot.", "global"), + # Inline comments + ("x = 5 # This is a comment", "global"), + ("y = obj.method() # Comment after dot.method", "global"), + # Hash symbol within string literals should not be treated as comments + ("d['#'] = np.", "attribute"), + # Nested parentheses with dots + ("complex_expr = (func((obj.method(param.attr", "attribute"), + ("multiple_nesting = {key: [value.attr", "attribute"), + # Numbers + ("3.", "global"), + ("3.14", "global"), + ("-42.14", "global"), + ("x = func(3.14", "global"), + ("x = func(a3.", "attribute"), + ("x = func(a3.12", "global"), + ("3.1.", "attribute"), + ("-3.1.", "attribute"), + ("(3).", "attribute"), + # Additional cases + ("", "global"), + ('str_with_code = "x.attr', "global"), + ('f"formatted {obj.attr', "attribute"), + ('f"formatted {obj.attr}', "global"), + ("dict_with_dots = {'key.with.dots': value.attr", "attribute"), + ("d[f'{a}']['{a.", "global"), + ("ls .", "global"), + ("t._", "attribute"), + ("t.__a", "attribute"), + ], +) +def test_completion_context(line, expected): + """Test completion context""" + ip = get_ipython() + get_context = ip.Completer._determine_completion_context + result = get_context(line) + assert result.value == expected, f"Failed on input: '{line}'" + + +@pytest.mark.parametrize( + "line,expected,expected_after_assignment", + [ + ("test_alias file", True, False), # overshadowed by variable + ("test_alias .", True, False), + ("test_alias file.", True, False), + ("%test_alias .file.ext", True, True), # magic, not affected by variable + ("!test_alias file.", True, True), # bang, not affected by variable + ], +) +def test_completion_in_cli_context(line, expected, expected_after_assignment): + """Test completion context with and without variable overshadowing""" + ip = get_ipython() + ip.run_cell("alias test_alias echo test_alias") + get_context = ip.Completer._is_completing_in_cli_context + + # Normal case + result = get_context(line) + assert result == expected, f"Failed on input: '{line}'" + + # Test with alias assigned as a variable + try: + ip.user_ns["test_alias"] = "some_value" + result_after_assignment = get_context(line) + assert ( + result_after_assignment == expected_after_assignment + ), f"Failed after assigning 'ls' as a variable for input: '{line}'" + finally: + ip.user_ns.pop("test_alias", None) + + +@pytest.mark.parametrize( + "line, expected", + [ + ("f'{f'a.", "global"), # Nested f-string + ("3a.", "global"), # names starting with numbers or other symbols + ("$).", "global"), # random things with dot at end + ], +) +def test_unsupported_completion_context(line, expected): + """Test unsupported completion context""" + ip = get_ipython() + get_context = ip.Completer._determine_completion_context + result = get_context(line) + assert result.value == expected, f"Failed on input: '{line}'" + + +@pytest.mark.parametrize( + "setup,code,expected,not_expected", + [ + ('a="str"; b=1', "(a, b.", [".bit_count", ".conjugate"], [".count"]), + ('a="str"; b=1', "(a, b).", [".count"], [".bit_count", ".capitalize"]), + ('x="str"; y=1', "x = {1, y.", [".bit_count"], [".count"]), + ('x="str"; y=1', "x = [1, y.", [".bit_count"], [".count"]), + ('x="str"; y=1; fun=lambda x:x', "x = fun(1, y.", [".bit_count"], [".count"]), + ], +) +def test_misc_no_jedi_completions(setup, code, expected, not_expected): + ip = get_ipython() + c = ip.Completer + ip.ex(setup) + with provisionalcompleter(), jedi_status(False): + matches = c.all_completions(code) + assert set(expected) - set(matches) == set(), set(matches) + assert set(matches).intersection(set(not_expected)) == set() + + +@pytest.mark.parametrize( + "code,expected", + [ + (" (a, b", "b"), + ("(a, b", "b"), + ("(a, b)", ""), # trim always start by trimming + (" (a, b)", "(a, b)"), + (" [a, b]", "[a, b]"), + (" a, b", "b"), + ("x = {1, y", "y"), + ("x = [1, y", "y"), + ("x = fun(1, y", "y"), + (" assert a", "a"), + ], +) +def test_trim_expr(code, expected): + c = get_ipython().Completer + assert c._trim_expr(code) == expected + + +@pytest.mark.parametrize( + "input, expected", + [ + ["1.234", "1.234"], + # should match signed numbers + ["+1", "+1"], + ["-1", "-1"], + ["-1.0", "-1.0"], + ["-1.", "-1."], + ["+1.", "+1."], + [".1", ".1"], + # should not match non-numbers + ["1..", None], + ["..", None], + [".1.", None], + # should match after comma + [",1", "1"], + [", 1", "1"], + [", .1", ".1"], + [", +.1", "+.1"], + # should not match after trailing spaces + [".1 ", None], + # some complex cases + ["0b_0011_1111_0100_1110", "0b_0011_1111_0100_1110"], + ["0xdeadbeef", "0xdeadbeef"], + ["0b_1110_0101", "0b_1110_0101"], + # should not match if in an operation + ["1 + 1", None], + [", 1 + 1", None], + ], +) +def test_match_numeric_literal_for_dict_key(input, expected): + assert _match_number_in_dict_key_prefix(input) == expected diff --git a/tests/test_completerlib.py b/tests/test_completerlib.py new file mode 100644 index 00000000000..de3f8abf970 --- /dev/null +++ b/tests/test_completerlib.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +"""Tests for completerlib.""" + +import os +import sys +from os.path import join +from tempfile import TemporaryDirectory + +import pytest + +from IPython.core.completerlib import magic_run_completer, module_completion, try_import +from IPython.testing.decorators import onlyif_unicode_paths + + +class MockEvent(object): + def __init__(self, line): + self.line = line + + +@pytest.fixture +def run_completer_dir(tmp_path, monkeypatch): + files = ["aao.py", "a.py", "b.py", "aao.txt"] + dirs = ["adir/", "bdir/"] + for fil in files: + (tmp_path / fil).write_text("pass\n", encoding="utf-8") + for d in dirs: + (tmp_path / d).mkdir() + monkeypatch.chdir(tmp_path) + return tmp_path, files, dirs + + +@pytest.mark.parametrize("line,expected_match", [ + ("%run a", {"a.py", "aao.py", "adir/"}), + ("%run aa", {"aao.py"}), + ('%run "a', {"a.py", "aao.py", "adir/"}), +]) +def test_magic_run_completer(run_completer_dir, line, expected_match): + match = set(magic_run_completer(None, MockEvent(line))) + assert match == expected_match + + +def test_magic_run_completer_more_args(run_completer_dir): + tmp_path, files, dirs = run_completer_dir + match = set(magic_run_completer(None, MockEvent("%run a.py "))) + assert match == set(files + dirs) + + +def test_magic_run_completer_in_dir(run_completer_dir): + # Github issue #3459 + tmp_path, files, dirs = run_completer_dir + event = MockEvent("%run a.py {}".format(join(str(tmp_path), "a"))) + match = set(magic_run_completer(None, event)) + assert match == { + join(str(tmp_path), f).replace("\\", "/") + for f in ("a.py", "aao.py", "aao.txt", "adir/") + } + + +@pytest.fixture +def run_completer_dir_nonascii(tmp_path, monkeypatch): + for fil in ["aaø.py", "a.py", "b.py"]: + (tmp_path / fil).write_text("pass\n", encoding="utf-8") + monkeypatch.chdir(tmp_path) + return tmp_path + + +@onlyif_unicode_paths +@pytest.mark.parametrize("line,expected_match", [ + ("%run a", {"a.py", "aaø.py"}), + ("%run aa", {"aaø.py"}), + ('%run "a', {"a.py", "aaø.py"}), +]) +def test_magic_run_completer_nonascii(run_completer_dir_nonascii, line, expected_match): + match = set(magic_run_completer(None, MockEvent(line))) + assert match == expected_match + + +def test_import_invalid_module(): + """Testing of issue https://github.com/ipython/ipython/issues/1107""" + invalid_module_names = {"foo-bar", "foo:bar", "10foo"} + valid_module_names = {"foobar"} + with TemporaryDirectory() as tmpdir: + sys.path.insert(0, tmpdir) + for name in invalid_module_names | valid_module_names: + filename = os.path.join(tmpdir, name + ".py") + open(filename, "w", encoding="utf-8").close() + + s = set(module_completion("import foo")) + intersection = s.intersection(invalid_module_names) + assert intersection == set() + + assert valid_module_names.issubset(s), valid_module_names.intersection(s) + + +def test_bad_module_all(): + """Test module with invalid __all__ + + https://github.com/ipython/ipython/issues/9678 + """ + testsdir = os.path.dirname(__file__) + sys.path.insert(0, testsdir) + try: + results = module_completion("from bad_all import ") + assert "puppies" in results + for r in results: + assert isinstance(r, str) + + results = module_completion("import bad_all.") + assert results == [] + finally: + sys.path.remove(testsdir) + + +def test_module_without_init(): + """ + Test module without __init__.py. + + https://github.com/ipython/ipython/issues/11226 + """ + fake_module_name = "foo_xder_134" + with TemporaryDirectory() as tmpdir: + sys.path.insert(0, tmpdir) + try: + os.makedirs(os.path.join(tmpdir, fake_module_name)) + s = try_import(mod=fake_module_name) + assert s == [], f"for module {fake_module_name}" + finally: + sys.path.remove(tmpdir) + + +def test_valid_exported_submodules(): + """ + Test checking exported (__all__) objects are submodules + """ + results = module_completion("import os.pa") + assert "os.path" in results + assert "os.pathconf" not in results diff --git a/tests/test_contexts.py b/tests/test_contexts.py new file mode 100644 index 00000000000..5afbd335ee9 --- /dev/null +++ b/tests/test_contexts.py @@ -0,0 +1,72 @@ +"""Tests for IPython.utils.contexts.""" + +import pytest + +from IPython.utils.contexts import preserve_keys + + +def test_preserve_keys_restores_existing_key(): + d = {"a": 1, "b": 2} + with preserve_keys(d, "a"): + d["a"] = 99 + assert d["a"] == 1 + + +def test_preserve_keys_deletes_new_key(): + d = {"a": 1} + with preserve_keys(d, "new_key"): + d["new_key"] = 42 + assert "new_key" not in d + + +def test_preserve_keys_preserves_unchanged_keys(): + d = {"a": 1, "b": 2, "c": 3} + with preserve_keys(d, "a"): + d["a"] = 99 + assert d["b"] == 2 + assert d["c"] == 3 + + +def test_preserve_keys_missing_key_deleted_on_exit(): + d = {"a": 1} + with preserve_keys(d, "missing"): + assert "missing" not in d + d["missing"] = "added" + assert "missing" not in d + + +def test_preserve_keys_multiple_keys(): + d = {"x": 10, "y": 20} + with preserve_keys(d, "x", "y", "z"): + d["x"] = 100 + del d["y"] + d["z"] = 30 + assert d["x"] == 10 + assert d["y"] == 20 + assert "z" not in d + + +def test_preserve_keys_restores_after_exception(): + d = {"a": 1} + try: + with preserve_keys(d, "a"): + d["a"] = 99 + raise RuntimeError("test") + except RuntimeError: + pass + assert d["a"] == 1 + + +@pytest.mark.parametrize("original_value", [None, 0, False, "", [], {}]) +def test_preserve_keys_restores_falsy_values(original_value): + d = {"k": original_value} + with preserve_keys(d, "k"): + d["k"] = "replaced" + assert d["k"] == original_value + + +def test_preserve_keys_no_keys_is_noop(): + d = {"a": 1} + with preserve_keys(d): + d["a"] = 99 + assert d["a"] == 99 diff --git a/tests/test_crashhandler.py b/tests/test_crashhandler.py new file mode 100644 index 00000000000..08265fae182 --- /dev/null +++ b/tests/test_crashhandler.py @@ -0,0 +1,213 @@ +"""Tests for IPython.core.crashhandler""" + +import sys +from unittest.mock import MagicMock + +import pytest + +from IPython.core import crashhandler +from IPython.core.crashhandler import CrashHandler, crash_handler_lite +from IPython.core.interactiveshell import InteractiveShell + + +class FakeApp: + """Minimal stand-in for an IPython Application.""" + + name = "testapp" + + def __init__(self, ipython_dir=None): + if ipython_dir is not None: + self.ipython_dir = ipython_dir + self.config = {"FakeApp": {"answer": 42}} + + +def _exc_info(): + try: + raise ValueError("crash-handler-test") + except ValueError: + return sys.exc_info() + + +@pytest.fixture +def input_prompts(monkeypatch): + """Replace builtins.input, recording the prompts it was called with.""" + prompts = [] + + def fake_input(prompt=""): + prompts.append(prompt) + return "" + + monkeypatch.setattr("builtins.input", fake_input) + return prompts + + +@pytest.fixture(autouse=True) +def restore_excepthook(monkeypatch): + # CrashHandler.__call__ resets sys.excepthook; make sure this does not + # leak out of the tests. + monkeypatch.setattr(sys, "excepthook", sys.excepthook) + + +def test_crash_handler_init(): + app = FakeApp() + ch = CrashHandler( + app, + contact_name="Jane Dev", + contact_email="jane@example.com", + bug_tracker="https://example.com/bugs", + ) + assert ch.crash_report_fname == "Crash_report_testapp.txt" + assert ch.app is app + assert ch.call_pdb is False + assert ch.show_crash_traceback is True + assert ch.info["app_name"] == "testapp" + assert ch.info["contact_name"] == "Jane Dev" + assert ch.info["contact_email"] == "jane@example.com" + assert ch.info["bug_tracker"] == "https://example.com/bugs" + assert ch.info["crash_report_fname"] == "Crash_report_testapp.txt" + + +def test_crash_handler_writes_report(tmp_path, capsys, input_prompts): + app = FakeApp(ipython_dir=str(tmp_path)) + ch = CrashHandler( + app, + contact_name="Jane Dev", + contact_email="jane@example.com", + bug_tracker="https://example.com/bugs", + ) + etype, evalue, etb = _exc_info() + ch(etype, evalue, etb) + + report_file = tmp_path / "Crash_report_testapp.txt" + assert report_file.is_file() + text = report_file.read_text(encoding="utf-8") + assert "IPython post-mortem report" in text + assert "Application name: testapp" in text + assert "Crash traceback:" in text + assert "ValueError" in text + assert "crash-handler-test" in text + + err = capsys.readouterr().err + # user-facing message + assert "Oops, testapp crashed." in err + assert "jane@example.com" in err + assert str(report_file) in err + # traceback shown on stderr by default + assert "ValueError" in err + + # the filename was expanded to the full path + assert ch.crash_report_fname == str(report_file) + assert ch.info["crash_report_fname"] == str(report_file) + # the crash handler uninstalls itself + assert sys.excepthook is sys.__excepthook__ + # and waits for the user to acknowledge + assert len(input_prompts) == 1 + + +def test_crash_handler_hidden_traceback(tmp_path, capsys, input_prompts): + app = FakeApp(ipython_dir=str(tmp_path)) + ch = CrashHandler(app, show_crash_traceback=False) + etype, evalue, etb = _exc_info() + ch(etype, evalue, etb) + + err = capsys.readouterr().err + # message is still printed, but not the traceback + assert "Oops, testapp crashed." in err + assert "ValueError" not in err + # the report on disk still has the traceback + text = (tmp_path / "Crash_report_testapp.txt").read_text(encoding="utf-8") + assert "ValueError" in text + + +def test_crash_handler_no_ipython_dir(tmp_path, monkeypatch, capsys, input_prompts): + # app without an ipython_dir attribute: report goes to cwd + monkeypatch.chdir(tmp_path) + ch = CrashHandler(FakeApp()) + etype, evalue, etb = _exc_info() + ch(etype, evalue, etb) + assert (tmp_path / "Crash_report_testapp.txt").is_file() + capsys.readouterr() + + +def test_crash_handler_missing_ipython_dir(tmp_path, monkeypatch, capsys, input_prompts): + # nonexistent ipython_dir: falls back to cwd + monkeypatch.chdir(tmp_path) + ch = CrashHandler(FakeApp(ipython_dir=str(tmp_path / "does-not-exist"))) + etype, evalue, etb = _exc_info() + ch(etype, evalue, etb) + assert (tmp_path / "Crash_report_testapp.txt").is_file() + capsys.readouterr() + + +def test_crash_handler_unwritable_report(tmp_path, capsys, input_prompts): + # make open() fail by occupying the report path with a directory + (tmp_path / "Crash_report_testapp.txt").mkdir() + ch = CrashHandler(FakeApp(ipython_dir=str(tmp_path))) + etype, evalue, etb = _exc_info() + ch(etype, evalue, etb) + + err = capsys.readouterr().err + assert "Could not create crash report on disk." in err + # bails out before the user message and the input() prompt + assert "Oops, testapp crashed." not in err + assert input_prompts == [] + + +def test_crash_handler_call_pdb(tmp_path, monkeypatch, input_prompts): + tb_instance = MagicMock() + verbose_tb = MagicMock(return_value=tb_instance) + monkeypatch.setattr(crashhandler.ultratb, "VerboseTB", verbose_tb) + + ch = CrashHandler(FakeApp(ipython_dir=str(tmp_path)), call_pdb=True) + etype, evalue, etb = _exc_info() + ch(etype, evalue, etb) + + verbose_tb.assert_called_once_with( + theme_name="nocolor", long_header=True, call_pdb=True + ) + tb_instance.assert_called_once_with(etype, evalue, etb) + # with call_pdb, no report is written and input() is not called + assert not (tmp_path / "Crash_report_testapp.txt").exists() + assert input_prompts == [] + + +def test_make_report_contains_config(): + ch = CrashHandler(FakeApp()) + report = ch.make_report("FAKE TRACEBACK") + assert "IPython post-mortem report" in report + assert "Application name: testapp" in report + assert "Current user configuration structure:" in report + assert "'answer': 42" in report + assert report.endswith("Crash traceback:\n\nFAKE TRACEBACK") + + +def test_make_report_without_config(): + app = FakeApp() + del app.config + ch = CrashHandler(app) + report = ch.make_report("FAKE TRACEBACK") + # the config section is skipped, the rest is still there + assert "IPython post-mortem report" in report + assert "Application name" not in report + assert "FAKE TRACEBACK" in report + + +def test_crash_handler_lite_no_shell(capsys, monkeypatch): + monkeypatch.setattr(InteractiveShell, "initialized", lambda: False) + etype, evalue, etb = _exc_info() + crash_handler_lite(etype, evalue, etb) + err = capsys.readouterr().err + assert "ValueError: crash-handler-test" in err + assert "If you suspect this is an IPython" in err + # outside of a shell, generic config syntax is suggested + assert "c.Application.verbose_crash=True" in err + + +def test_crash_handler_lite_in_shell(capsys, monkeypatch): + monkeypatch.setattr(InteractiveShell, "initialized", lambda: True) + etype, evalue, etb = _exc_info() + crash_handler_lite(etype, evalue, etb) + err = capsys.readouterr().err + assert "ValueError: crash-handler-test" in err + # inside a shell, the %config magic is suggested + assert "%config Application.verbose_crash=True" in err diff --git a/tests/test_cve.py b/tests/test_cve.py new file mode 100644 index 00000000000..fab11b4bd89 --- /dev/null +++ b/tests/test_cve.py @@ -0,0 +1,66 @@ +""" +Test that CVEs stay fixed. +""" + +from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory +from pathlib import Path +import random +import sys +import os +import string +import subprocess + + +def test_cve_2022_21699(): + """ + Here we test CVE-2022-21699. + + We create a temporary directory, cd into it. + Make a profile file that should not be executed and start IPython in a subprocess, + checking for the value. + + + + """ + + dangerous_profile_dir = Path("profile_default") + + dangerous_startup_dir = dangerous_profile_dir / "startup" + dangerous_expected = "CVE-2022-21699-" + "".join( + [random.choice(string.ascii_letters) for i in range(10)] + ) + + with TemporaryWorkingDirectory() as t: + dangerous_startup_dir.mkdir(parents=True) + (dangerous_startup_dir / "foo.py").write_text( + f'print("{dangerous_expected}")', encoding="utf-8" + ) + # 1 sec to make sure FS is flushed. + # time.sleep(1) + cmd = [sys.executable, "-m", "IPython"] + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + + # First we fake old behavior, making sure the profile is/was actually dangerous + p_dangerous = subprocess.Popen( + cmd + [f"--profile-dir={dangerous_profile_dir}"], + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r") + assert dangerous_expected in out_dangerous.decode() + + # Now that we know it _would_ have been dangerous, we test it's not loaded + p = subprocess.Popen( + cmd, + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + out, err = p.communicate(b"exit\r") + assert b"IPython" in out + assert dangerous_expected not in out.decode() + assert err == b"" diff --git a/tests/test_data.py b/tests/test_data.py new file mode 100644 index 00000000000..bcf135d22c3 --- /dev/null +++ b/tests/test_data.py @@ -0,0 +1,76 @@ +"""Tests for IPython.utils.data.""" + +import warnings + +import pytest + +from IPython.utils.data import chop, uniq_stable + + +# --------------------------------------------------------------------------- +# chop +# --------------------------------------------------------------------------- + + +def test_chop_even_division(): + assert chop([1, 2, 3, 4], 2) == [[1, 2], [3, 4]] + + +def test_chop_uneven_division(): + result = chop([1, 2, 3, 4, 5], 2) + assert result == [[1, 2], [3, 4], [5]] + + +def test_chop_size_larger_than_seq(): + assert chop([1, 2], 10) == [[1, 2]] + + +def test_chop_size_1(): + assert chop([1, 2, 3], 1) == [[1], [2], [3]] + + +def test_chop_empty_sequence(): + assert chop([], 2) == [] + + +def test_chop_string(): + result = chop("abcdef", 2) + assert result == ["ab", "cd", "ef"] + + +@pytest.mark.parametrize("seq,size,expected", [ + ([1, 2, 3, 4, 5, 6], 3, [[1, 2, 3], [4, 5, 6]]), + ([1, 2, 3, 4, 5, 6], 2, [[1, 2], [3, 4], [5, 6]]), + ([1, 2, 3, 4, 5, 6], 6, [[1, 2, 3, 4, 5, 6]]), +]) +def test_chop_parametrized(seq, size, expected): + assert chop(seq, size) == expected + + +# --------------------------------------------------------------------------- +# uniq_stable (deprecated) +# --------------------------------------------------------------------------- + + +def test_uniq_stable_raises_deprecation_warning(): + with pytest.warns(DeprecationWarning, match="uniq_stable is deprecated"): + uniq_stable([1, 2, 1]) + + +def test_uniq_stable_preserves_order(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + result = uniq_stable([3, 1, 2, 1, 3]) + assert result == [3, 1, 2] + + +def test_uniq_stable_empty(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + assert uniq_stable([]) == [] + + +def test_uniq_stable_all_unique(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + assert uniq_stable([1, 2, 3]) == [1, 2, 3] diff --git a/tests/test_debug_magic.py b/tests/test_debug_magic.py new file mode 100644 index 00000000000..425ecb5b5f2 --- /dev/null +++ b/tests/test_debug_magic.py @@ -0,0 +1,87 @@ +"""Test embedding of IPython""" + +# ----------------------------------------------------------------------------- +# Copyright (C) 2013 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + +import os +import sys +from IPython.testing.decorators import skip_win32 +from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE + +# ----------------------------------------------------------------------------- +# Tests +# ----------------------------------------------------------------------------- + + +@skip_win32 +def test_debug_magic_passes_through_generators(): + """ + This test that we can correctly pass through frames of a generator post-mortem. + """ + import pexpect + import re + + in_prompt = re.compile(rb"In ?\[\d+\]:") + ipdb_prompt = "ipdb>" + env = os.environ.copy() + child = pexpect.spawn( + sys.executable, + ["-m", "IPython", "--colors=nocolor", "--simple-prompt"], + env=env, + ) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + + child.expect(in_prompt) + + child.timeout = 2 * IPYTHON_TESTING_TIMEOUT_SCALE + + child.sendline("def f(x):") + child.sendline(" raise Exception") + child.sendline("") + + child.expect(in_prompt) + child.sendline("gen = (f(x) for x in [0])") + child.sendline("") + + child.expect(in_prompt) + child.sendline("for x in gen:") + child.sendline(" pass") + child.sendline("") + + child.timeout = 10 * IPYTHON_TESTING_TIMEOUT_SCALE + + child.expect("Exception:") + + child.expect(in_prompt) + child.sendline(r"%debug") + child.expect("----> 2 raise Exception") + + child.expect(ipdb_prompt) + child.sendline("u") + child.expect_exact(r"----> 1 gen = (f(x) for x in [0])") + + child.expect(ipdb_prompt) + child.sendline("u") + child.expect_exact("----> 1 for x in gen:") + + child.expect(ipdb_prompt) + child.sendline("u") + child.expect_exact( + "*** all frames above skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules." + ) + + child.expect(ipdb_prompt) + child.sendline("exit") + + child.expect(in_prompt) + child.sendline("exit") + + child.close() diff --git a/tests/test_debugger.py b/tests/test_debugger.py new file mode 100644 index 00000000000..6803ffabd77 --- /dev/null +++ b/tests/test_debugger.py @@ -0,0 +1,2001 @@ +"""Tests for debugging machinery.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import builtins +import io +import os +import re as re_module +import sys +import platform +from pathlib import Path + +from tempfile import NamedTemporaryFile, TemporaryDirectory +from textwrap import dedent +from unittest.mock import patch + +from IPython.core import debugger +from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE +from IPython.testing.decorators import skip_win32 +from IPython.utils import PyColorize +import pytest + +# Helper for executing files with proper debugger frame marking +def execfile(fname, glob, loc=None, compiler=None): + __tracebackhide__ = "__ipython_bottom__" + loc = loc if (loc is not None) else glob + with open(fname, "rb") as f: + compiler_fn = compiler or compile + exec(compiler_fn(f.read(), fname, "exec"), glob, loc) + + +# Helper classes, from CPython's Pdb test suite +# + + +class _FakeInput(object): + """ + A fake input stream for pdb's interactive debugger. Whenever a + line is read, print it (to simulate the user typing it), and then + return it. The set of lines to return is specified in the + constructor; they should not have trailing newlines. + """ + + def __init__(self, lines): + self.lines = iter(lines) + + def readline(self): + line = next(self.lines) + print(line) + return line + "\n" + + +class PdbTestInput(object): + """Context manager that makes testing Pdb in doctests easier.""" + + def __init__(self, input): + self.input = input + + def __enter__(self): + self.real_stdin = sys.stdin + sys.stdin = _FakeInput(self.input) + + def __exit__(self, *exc): + sys.stdin = self.real_stdin + + +# ----------------------------------------------------------------------------- +# Tests +# ----------------------------------------------------------------------------- + + +def test_ipdb_magics(): + '''Test calling some IPython magics from ipdb. + + First, set up some test functions and classes which we can inspect. + + In [1]: class ExampleClass(object): + ...: """Docstring for ExampleClass.""" + ...: def __init__(self): + ...: """Docstring for ExampleClass.__init__""" + ...: pass + ...: def __str__(self): + ...: return "ExampleClass()" + + In [2]: def example_function(x, y, z="hello"): + ...: """Docstring for example_function.""" + ...: pass + + In [3]: old_trace = sys.gettrace() + + Create a function which triggers ipdb. + + In [4]: def trigger_ipdb(): + ...: a = ExampleClass() + ...: debugger.Pdb().set_trace() + + Run ipdb with faked input & check output. Because of a difference between + Python 3.13 & older versions, the first bit of the output is inconsistent. + We need to use ... to accommodate that, so the examples have to use IPython + prompts so that ... is distinct from the Python PS2 prompt. + + In [5]: with PdbTestInput([ + ...: 'pdef example_function', + ...: 'pdoc ExampleClass', + ...: 'up', + ...: 'down', + ...: 'list', + ...: 'pinfo a', + ...: 'll', + ...: 'continue', + ...: ]): + ...: trigger_ipdb() + ...> (3)trigger_ipdb() + 1 def trigger_ipdb(): + 2 a = ExampleClass() + ----> 3 debugger.Pdb().set_trace() + + ipdb> pdef example_function + example_function(x, y, z='hello') + ipdb> pdoc ExampleClass + Class docstring: + Docstring for ExampleClass. + Init docstring: + Docstring for ExampleClass.__init__ + ipdb> up + > (11)() + 7 'pinfo a', + 8 'll', + 9 'continue', + 10 ]): + ---> 11 trigger_ipdb() + + ipdb> down... + > (3)trigger_ipdb() + 1 def trigger_ipdb(): + 2 a = ExampleClass() + ----> 3 debugger.Pdb().set_trace() + + ipdb> list + 1 def trigger_ipdb(): + 2 a = ExampleClass() + ----> 3 debugger.Pdb().set_trace() + + ipdb> pinfo a + Type: ExampleClass + String form: ExampleClass() + Namespace: Local... + Docstring: Docstring for ExampleClass. + Init docstring: Docstring for ExampleClass.__init__ + ipdb> ll + 1 def trigger_ipdb(): + 2 a = ExampleClass() + ----> 3 debugger.Pdb().set_trace() + + ipdb> continue + + Restore previous trace function, e.g. for coverage.py + + In [6]: sys.settrace(old_trace) + ''' + + +def test_ipdb_closure(): + """Test evaluation of expressions which depend on closure. + + In [1]: old_trace = sys.gettrace() + + Create a function which triggers ipdb. + + In [2]: def trigger_ipdb(): + ...: debugger.Pdb().set_trace() + + In [3]: with PdbTestInput([ + ...: 'x = 1; sum(x * i for i in range(5))', + ...: 'continue', + ...: ]): + ...: trigger_ipdb() + ...> (2)trigger_ipdb() + 1 def trigger_ipdb(): + ----> 2 debugger.Pdb().set_trace() + + ipdb> x = 1; sum(x * i for i in range(5)) + ipdb> continue + + Restore previous trace function, e.g. for coverage.py + + In [4]: sys.settrace(old_trace) + """ + + +def test_ipdb_magics2(): + """Test ipdb with a very short function. + + >>> old_trace = sys.gettrace() + + >>> def bar(): + ... pass + + Run ipdb. + + >>> with PdbTestInput([ + ... 'continue', + ... ]): + ... debugger.Pdb().runcall(bar) + > (2)bar() + 1 def bar(): + ----> 2 pass + + ipdb> continue + + Restore previous trace function, e.g. for coverage.py + + >>> sys.settrace(old_trace) + """ + + +def can_quit(): + """Test that quit work in ipydb + + >>> old_trace = sys.gettrace() + + >>> def bar(): + ... pass + + >>> with PdbTestInput([ + ... 'quit', + ... ]): + ... debugger.Pdb().runcall(bar) + > (2)bar() + 1 def bar(): + ----> 2 pass + + ipdb> quit + + Restore previous trace function, e.g. for coverage.py + + >>> sys.settrace(old_trace) + """ + + +def can_exit(): + """Test that quit work in ipydb + + >>> old_trace = sys.gettrace() + + >>> def bar(): + ... pass + + >>> with PdbTestInput([ + ... 'exit', + ... ]): + ... debugger.Pdb().runcall(bar) + > (2)bar() + 1 def bar(): + ----> 2 pass + + ipdb> exit + + Restore previous trace function, e.g. for coverage.py + + >>> sys.settrace(old_trace) + """ + + +def test_interruptible_core_debugger(): + """The debugger can be interrupted. + + The presumption is there is some mechanism that causes a KeyboardInterrupt + (this is implemented in ipykernel). We want to ensure the + KeyboardInterrupt cause debugging to cease. + """ + + def raising_input(msg="", called=[0]): + called[0] += 1 + assert called[0] == 1, "input() should only be called once!" + raise KeyboardInterrupt() + + tracer_orig = sys.gettrace() + try: + with patch.object(builtins, "input", raising_input): + debugger.InterruptiblePdb().set_trace() + # The way this test will fail is by set_trace() never exiting, + # resulting in a timeout by the test runner. The alternative + # implementation would involve a subprocess, but that adds issues + # with interrupting subprocesses that are rather complex, so it's + # simpler just to do it this way. + finally: + # restore the original trace function + sys.settrace(tracer_orig) + + +@pytest.mark.skipif( + not debugger.CHAIN_EXCEPTIONS, + reason="chained exception navigation is not available", +) +def test_exception_command_aliases_exceptions(): + ipdb = debugger.Pdb() + + with patch.object(ipdb, "do_exceptions", return_value=True) as do_exceptions: + assert ipdb.onecmd("exception 0") is True + + do_exceptions.assert_called_once_with("0") + + +def test_execfile_marks_debugger_internal_frames_hidden(): + with TemporaryDirectory() as td: + script = Path(td) / "fails.py" + script.write_text( + "def user_frame():\n" + " raise RuntimeError('boom')\n" + "user_frame()\n", + encoding="utf-8", + ) + + with pytest.raises(RuntimeError) as exc_info: + execfile(script, {}) + + ipdb = debugger.Pdb() + stack, _ = ipdb.get_stack(None, exc_info.value.__traceback__) + hidden_frames = ipdb.hidden_frames(stack) + frame_names = [frame.f_code.co_name for frame, _ in stack] + execfile_index = frame_names.index("execfile") + + assert ( + stack[execfile_index][0].f_locals["__tracebackhide__"] + == "__ipython_bottom__" + ) + assert hidden_frames[execfile_index] is True + assert all(hidden_frames[:execfile_index]) + assert hidden_frames[execfile_index + 1 :] == [False, False] + + +@skip_win32 +def test_xmode_skip(): + """that xmode skip frames + + Not as a doctest as pytest does not run doctests. + """ + import pexpect + + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + + child = pexpect.spawn( + sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env + ) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + + child.expect("IPython") + child.expect("\n") + child.expect_exact("In [1]") + + block = dedent( + """ + def f(): + __tracebackhide__ = True + g() + + def g(): + raise ValueError + + f() + """ + ) + + for line in block.splitlines(): + child.sendline(line) + child.expect_exact(line) + child.expect_exact("skipping") + + block = dedent( + """ + def f(): + __tracebackhide__ = True + g() + + def g(): + from IPython.core.debugger import set_trace + set_trace() + + f() + """ + ) + + for line in block.splitlines(): + child.sendline(line) + child.expect_exact(line) + + child.expect("ipdb>") + child.sendline("w") + child.expect("hidden") + child.expect("ipdb>") + child.sendline("skip_hidden false") + child.sendline("w") + child.expect("__traceba") + child.expect("ipdb>") + + child.close() + + +skip_decorators_blocks = ( + """ + def helpers_helper(): + pass # should not stop here except breakpoint + """, + """ + def helper_1(): + helpers_helper() # should not stop here + """, + """ + def helper_2(): + pass # should not stop here + """, + """ + def pdb_skipped_decorator2(function): + def wrapped_fn(*args, **kwargs): + __debuggerskip__ = True + helper_2() + __debuggerskip__ = False + result = function(*args, **kwargs) + __debuggerskip__ = True + helper_2() + return result + return wrapped_fn + """, + """ + def pdb_skipped_decorator(function): + def wrapped_fn(*args, **kwargs): + __debuggerskip__ = True + helper_1() + __debuggerskip__ = False + result = function(*args, **kwargs) + __debuggerskip__ = True + helper_2() + return result + return wrapped_fn + """, + """ + @pdb_skipped_decorator + @pdb_skipped_decorator2 + def bar(x, y): + return x * y + """, + """import IPython.terminal.debugger as ipdb""", + """ + def f(): + ipdb.set_trace() + bar(3, 4) + """, + """ + f() + """, +) + + +# From 3.13, ``set_trace()``/``breakpoint()`` stop on the line where they are +# called instead of on the next one, so one extra ``step`` is needed to reach +# the line following the ``set_trace()`` call. +SET_TRACE_STOPS_ON_CALLING_LINE = sys.version_info >= (3, 13) + + +def _decorator_skip_setup(): + import pexpect + + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + env["PROMPT_TOOLKIT_NO_CPR"] = "1" + + child = pexpect.spawn( + sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env + ) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + + child.expect("IPython") + child.expect("\n") + + child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE + child.str_last_chars = 500 + + dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks] + in_prompt_number = 1 + for cblock in dedented_blocks: + child.expect_exact(f"In [{in_prompt_number}]:") + in_prompt_number += 1 + for line in cblock.splitlines(): + child.sendline(line) + child.expect_exact(line) + child.sendline("") + return child + + +@skip_win32 +def test_decorator_skip(): + """test that decorator frames can be skipped.""" + + child = _decorator_skip_setup() + + child.expect_exact("ipython-input-8") + child.expect_exact("3 bar(3, 4)") + child.expect("ipdb>") + + child.expect("ipdb>") + if SET_TRACE_STOPS_ON_CALLING_LINE: + child.sendline("step") + child.expect_exact("step") + child.expect_exact("----> 3 bar(3, 4)") + child.expect("ipdb>") + child.sendline("step") + child.expect_exact("step") + child.expect_exact("--Call--") + child.expect_exact("ipython-input-6") + + child.expect_exact("1 @pdb_skipped_decorator") + + child.sendline("s") + child.expect_exact("return x * y") + + child.close() + + +@pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy") +@skip_win32 +def test_decorator_skip_disabled(): + """test that decorator frame skipping can be disabled""" + + child = _decorator_skip_setup() + + child.expect_exact("3 bar(3, 4)") + + if SET_TRACE_STOPS_ON_CALLING_LINE: + extra_step = [("step", "----> 3 bar(3, 4)")] + else: + extra_step = [] + + for input_, expected in extra_step + [ + ("skip_predicates debuggerskip False", ""), + ("skip_predicates", "debuggerskip : False"), + ("step", "---> 2 def wrapped_fn"), + ("step", "----> 3 __debuggerskip__"), + ("step", "----> 4 helper_1()"), + ("step", "---> 1 def helper_1():"), + ("next", "----> 2 helpers_helper()"), + ("next", "--Return--"), + ("next", "----> 5 __debuggerskip__ = False"), + ]: + child.expect("ipdb>") + child.sendline(input_) + child.expect_exact(input_) + child.expect_exact(expected) + + child.close() + + +@pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy") +@skip_win32 +def test_decorator_skip_with_breakpoint(): + """test that decorator frame skipping can be disabled""" + + import pexpect + + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + env["PROMPT_TOOLKIT_NO_CPR"] = "1" + + child = pexpect.spawn( + sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env + ) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + child.str_last_chars = 500 + + child.expect("IPython") + child.expect("\n") + + child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE + + ### we need a filename, so we need to exec the full block with a filename + with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf: + name = tf.name[:-3].split("/")[-1] + tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode()) + tf.flush() + codeblock = f"from {name} import f" + + dedented_blocks = [ + codeblock, + "f()", + ] + + in_prompt_number = 1 + for cblock in dedented_blocks: + child.expect_exact(f"In [{in_prompt_number}]:") + in_prompt_number += 1 + for line in cblock.splitlines(): + child.sendline(line) + child.expect_exact(line) + child.sendline("") + + if SET_TRACE_STOPS_ON_CALLING_LINE: + child.expect_exact("--> 46 ipdb.set_trace()") + extra_step = [("step", "--> 47 bar(3, 4)")] + else: + child.expect_exact("--> 47 bar(3, 4)") + extra_step = [] + + for input_, expected in ( + [ + (f"b {name}.py:3", ""), + ] + + extra_step + + [ + ("step", "1---> 3 pass # should not stop here except"), + ("step", "---> 38 @pdb_skipped_decorator"), + ("continue", ""), + ] + ): + child.expect("ipdb>") + child.sendline(input_) + child.expect_exact(input_) + child.expect_exact(expected) + + child.close() + + +@skip_win32 +def test_where_erase_value(): + """Test that `where` does not access f_locals and erase values.""" + import pexpect + + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + + child = pexpect.spawn( + sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env + ) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + + child.expect("IPython") + child.expect("\n") + child.expect_exact("In [1]") + + block = dedent( + """ + def simple_f(): + myvar = 1 + print(myvar) + 1/0 + print(myvar) + simple_f() """ + ) + + for line in block.splitlines(): + child.sendline(line) + child.expect_exact(line) + child.expect_exact("ZeroDivisionError") + child.expect_exact("In [2]:") + + child.sendline("%debug") + + ## + child.expect("ipdb>") + + child.sendline("myvar") + child.expect("1") + + ## + child.expect("ipdb>") + + child.sendline("myvar = 2") + + ## + child.expect_exact("ipdb>") + + child.sendline("myvar") + + child.expect_exact("2") + + ## + child.expect("ipdb>") + child.sendline("where") + + ## + child.expect("ipdb>") + child.sendline("myvar") + + child.expect_exact("2") + child.expect("ipdb>") + + child.close() + + +@skip_win32 +def test_ignore_module_basic_functionality(): + """Test basic ignore/unignore functionality and error handling.""" + import pexpect + + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + + with TemporaryDirectory() as temp_dir: + main_path = create_test_modules(temp_dir) + + child = pexpect.spawn(sys.executable, [main_path], env=env, cwd=temp_dir) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + child.expect("ipdb>") + + # Test listing modules when none are ignored + child.sendline("ignore_module") + child.expect_exact("No modules are currently ignored.") + child.expect("ipdb>") + + # Test ignoring a module + child.sendline("ignore_module level2_module") + child.expect("ipdb>") + + # Test listing ignored modules + child.sendline("ignore_module") + child.expect_exact("Currently ignored modules: ['level2_module']") + child.expect("ipdb>") + + # Test wildcard pattern + child.sendline("ignore_module testpkg.*") + child.expect("ipdb>") + + child.sendline("ignore_module") + child.expect_exact("Currently ignored modules: ['level2_module', 'testpkg.*']") + child.expect("ipdb>") + + # Test error handling - removing non-existent module + child.sendline("unignore_module nonexistent") + child.expect_exact("Module nonexistent is not currently ignored") + child.expect("ipdb>") + + # Test successful removal + child.sendline("unignore_module level2_module") + child.expect("ipdb>") + + child.sendline("ignore_module") + child.expect_exact("Currently ignored modules: ['testpkg.*']") + child.expect("ipdb>") + + # Test removing already removed module + child.sendline("unignore_module level2_module") + child.expect_exact("Module level2_module is not currently ignored") + child.expect("ipdb>") + + # Remove wildcard pattern + child.sendline("unignore_module testpkg.*") + child.expect("ipdb>") + + child.sendline("ignore_module") + child.expect_exact("No modules are currently ignored.") + child.expect("ipdb>") + + child.sendline("continue") + child.close() + + +# Helper function for creating temporary modules +def create_test_modules(temp_dir): + """Create a comprehensive module hierarchy for testing all debugger commands.""" + + temp_path = Path(temp_dir) + + # Create package structure for wildcard testing + package_dir = temp_path / "testpkg" + package_dir.mkdir() + + # Package __init__.py + (package_dir / "__init__.py").write_text("# Test package") + + # testpkg/submod1.py + (package_dir / "submod1.py").write_text( + dedent( + """ + def submod1_func(): + x = 1 + y = 2 + return x + y + """ + ) + ) + + # testpkg/submod2.py + (package_dir / "submod2.py").write_text( + dedent( + """ + def submod2_func(): + z = 10 + return z * 2 + """ + ) + ) + + # Level 1 (top level module) + (temp_path / "level1_module.py").write_text( + dedent( + """ + from level2_module import level2_func + + def level1_func(): + return level2_func() + """ + ) + ) + + # Level 2 (middle level module) + (temp_path / "level2_module.py").write_text( + dedent( + """ + from level3_module import level3_func + from testpkg.submod1 import submod1_func + from testpkg.submod2 import submod2_func + + def level2_func(): + # Call package functions for step/next testing + result1 = submod1_func() + result2 = submod2_func() + return level3_func() + result1 + result2 + """ + ) + ) + + # Level 3 (bottom level with debugger) + (temp_path / "level3_module.py").write_text( + dedent( + """ + from level4_module import level4_func + + from IPython.core.debugger import set_trace + + def level3_func(): + set_trace() + pass + result = level4_func() + return result + """ + ) + ) + + # Level 4 (bottom level with debugger) + (temp_path / "level4_module.py").write_text( + dedent( + """ + def level4_func(): + a = 70 + b = 30 + return a + b + """ + ) + ) + + # Main runner + main_path = temp_path / "main_runner.py" + main_path.write_text( + dedent( + """ + import sys + sys.path.insert(0, '.') + from level1_module import level1_func + + if __name__ == "__main__": + result = level1_func() + print(f"Final result: {result}") + """ + ) + ) + + return str(main_path) + + +@skip_win32 +def test_ignore_module_all_commands(): + """Comprehensive test for all debugger commands (up/down/step/next) with ignore functionality.""" + import pexpect + + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + + with TemporaryDirectory() as temp_dir: + main_path = create_test_modules(temp_dir) + + # Test UP and DOWN commands + child = pexpect.spawn(sys.executable, [main_path], env=env, cwd=temp_dir) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + child.expect("ipdb>") + + # Test up without ignores (baseline) + child.sendline("up") + child.expect("ipdb>") + child.sendline("__name__") + child.expect_exact("level2_module") + child.expect("ipdb>") + + # Reset position + child.sendline("down") + child.expect("ipdb>") + + # Test up with single module ignore + child.sendline("ignore_module level2_module") + child.expect("ipdb>") + child.sendline("up") + child.expect_exact( + "[... skipped 1 frame(s): 0 hidden frames + 1 ignored modules]" + ) + child.expect("ipdb>") + child.sendline("__name__") + child.expect_exact("level1_module") + child.expect("ipdb>") + + # Test up with wildcard ignore + child.sendline("down") + child.expect_exact( + "[... skipped 1 frame(s): 0 hidden frames + 1 ignored modules]" + ) + child.expect("ipdb>") + child.sendline("unignore_module level2_module") + child.expect("ipdb>") + child.sendline("ignore_module level*") + child.expect("ipdb>") + child.sendline("up") + child.expect_exact( + "[... skipped 2 frame(s): 0 hidden frames + 2 ignored modules]" + ) + child.expect("ipdb>") + child.sendline("__name__") + child.expect_exact("__main__") + child.expect("ipdb>") + + child.sendline("continue") + child.close() + + # Test STEP command + child = pexpect.spawn(sys.executable, [main_path], env=env, cwd=temp_dir) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + child.expect("ipdb>") + + # Test step without ignores (should step into module) + child.sendline("until 9") + child.expect("ipdb>") + child.sendline("step") + child.expect("ipdb>") + child.sendline("__name__") + child.expect_exact("level4_module") + child.expect("ipdb>") + + child.sendline("continue") + child.close() + + # Test step with single module ignore + child = pexpect.spawn(sys.executable, [main_path], env=env, cwd=temp_dir) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + child.expect("ipdb>") + + child.sendline("ignore_module level4_module") + child.expect("ipdb>") + child.sendline("until 9") + child.expect("ipdb>") + child.sendline("step") + child.expect_exact("[... skipped 1 ignored module(s)]") + child.expect("ipdb>") + child.sendline("__name__") + child.expect_exact("level3_module") + child.expect("ipdb>") + + child.sendline("continue") + child.close() + + # Test NEXT command + child = pexpect.spawn(sys.executable, [main_path], env=env, cwd=temp_dir) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + child.expect("ipdb>") + + # Test next without ignores + child.sendline("until 9") + child.expect("ipdb>") + child.sendline("next") + child.expect("ipdb>") + child.sendline("__name__") + child.expect_exact("level3_module") + child.expect("ipdb>") + + child.sendline("continue") + child.close() + + # Test next with module ignore + child = pexpect.spawn(sys.executable, [main_path], env=env, cwd=temp_dir) + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE + child.expect("ipdb>") + + child.sendline("ignore_module level2_module") + child.expect("ipdb>") + child.sendline("return") + child.expect("ipdb>") + child.sendline("next") + child.expect_exact("[... skipped 1 ignored module(s)]") + child.expect("ipdb>") + + child.sendline("continue") + child.close() + + +# ----------------------------------------------------------------------------- +# Signature compatibility with stdlib's ``pdb.Pdb`` +# ----------------------------------------------------------------------------- + +import inspect +import pdb as _stdlib_pdb + +from IPython.terminal.debugger import TerminalPdb + +_PDB_SUBCLASSES = [debugger.Pdb, debugger.InterruptiblePdb, TerminalPdb] + +# TerminalPdb instantiates a prompt_toolkit session, which needs a real +# console and cannot be created on Windows CI (NoConsoleScreenBufferError), +# so we skip instantiating it there. +_PDB_SUBCLASSES_INSTANTIABLE = [ + debugger.Pdb, + debugger.InterruptiblePdb, + pytest.param(TerminalPdb, marks=skip_win32), +] + + +@pytest.mark.parametrize( + "cls", _PDB_SUBCLASSES, ids=lambda c: c.__name__ +) +def test_pdb_subclass_signature_compatible(cls): + """IPython debuggers derived from ``pdb.Pdb`` should be drop-in + replacements, i.e. accept every keyword argument that the stdlib + ``pdb.Pdb`` accepts (directly or via ``**kwargs``).""" + assert issubclass(cls, _stdlib_pdb.Pdb) + + stdlib_params = inspect.signature(_stdlib_pdb.Pdb.__init__).parameters + ip_params = inspect.signature(cls.__init__).parameters + accepts_var_keyword = any( + p.kind is inspect.Parameter.VAR_KEYWORD for p in ip_params.values() + ) + + for name, param in stdlib_params.items(): + if name == "self": + continue + if param.kind in ( + inspect.Parameter.VAR_POSITIONAL, + inspect.Parameter.VAR_KEYWORD, + ): + continue + assert accepts_var_keyword or name in ip_params, ( + f"{cls.__name__} does not accept the {name!r} argument " + "supported by stdlib pdb.Pdb" + ) + + +@pytest.mark.parametrize( + "cls", _PDB_SUBCLASSES_INSTANTIABLE, ids=lambda c: c.__name__ +) +@pytest.mark.parametrize("mode", [None, "inline", "cli"]) +def test_pdb_subclass_accepts_mode(cls, mode): + """The ``mode`` argument (added to ``pdb.Pdb`` in Python 3.14) should be + accepted on every supported Python version and stored on the instance.""" + inst = cls(mode=mode) + try: + assert inst.mode == mode + finally: + if isinstance(inst, TerminalPdb): + _cleanup_terminal_pdb(inst) + + +# ----------------------------------------------------------------------------- +# In-process Pdb tests. +# +# These drive the debugger post-mortem style: the Pdb instance is set up on a +# traceback and commands are dispatched with ``onecmd`` (or a full +# ``interaction`` with a fake stdin). No trace function is installed for most +# tests, which keeps them fast and deterministic. Sessions that end with +# quit/continue call ``sys.settrace(None)``, so those tests save and restore +# the original trace function (e.g. for coverage.py). +# ----------------------------------------------------------------------------- + +_ANSI_ESCAPE_RE = re_module.compile(r"\x1b\[[0-9;]*m") + + +def _uncolor(text): + """Remove ANSI color escapes from ``text``.""" + return _ANSI_ESCAPE_RE.sub("", text) + + +def _make_pdb(**kwargs): + """Create a Pdb instance writing to a StringIO, with colors disabled.""" + kwargs.setdefault("readrc", False) + p = debugger.Pdb(stdout=io.StringIO(), **kwargs) + p.set_theme_name("nocolor") + return p + + +def _post_mortem_pdb(tb_or_exc, cls=debugger.Pdb, **kwargs): + """Create a Pdb instance set up post-mortem on a traceback/exception.""" + kwargs.setdefault("readrc", False) + p = cls(stdout=io.StringIO(), **kwargs) + p.set_theme_name("nocolor") + p.reset() + tb = ( + tb_or_exc.__traceback__ + if isinstance(tb_or_exc, BaseException) + else tb_or_exc + ) + p.setup(None, tb) + return p + + +def _run_pdb_session(commands, tb_or_exc, cls=debugger.Pdb, **kwargs): + """Run a full interactive post-mortem session, feeding ``commands``. + + Quitting the debugger calls ``sys.settrace(None)``, so the original trace + function is restored afterwards (e.g. for coverage.py). + """ + kwargs.setdefault("readrc", False) + stdout = io.StringIO() + p = cls(stdin=_FakeInput(list(commands)), stdout=stdout, **kwargs) + p.set_theme_name("nocolor") + p.reset() + old_trace = sys.gettrace() + try: + p.interaction(None, tb_or_exc) + finally: + sys.settrace(old_trace) + return p, stdout.getvalue() + + +def _simple_exc(): + """Return a ValueError whose traceback contains a frame with locals.""" + + def fail(): + myvar = 42 + raise ValueError("simple-boom %s" % myvar) + + try: + fail() + except ValueError as exc: + return exc + + +def _mixed_hidden_exc(): + """Exception with both hidden and visible frames in its traceback.""" + + def raiser(): + __tracebackhide__ = True + raise ValueError("mixed-boom") + + def hidden_mid(): + __tracebackhide__ = True + return raiser() + + def visible_top(): + return hidden_mid() + + try: + visible_top() + except ValueError as exc: + return exc + + +def _chained_exc(): + try: + try: + 1 / 0 + except ZeroDivisionError as inner: + raise ValueError("chained-boom") from inner + except ValueError as exc: + return exc + + +_FAKE_MODULE_SOURCE = '''\ +"""Fake module for debugger tests.""" + + +class ExampleClass: + """Docstring for ExampleClass.""" + + +def example_function(x, y=3): + """Docstring for example_function.""" + return x + y + + +def fail(): + value = example_function(1) + raise ValueError("failing-module %s" % value) + + +fail() +''' + + +def _exec_failing_module(tmp_path, name="pdb_fake_mod"): + """Execute a small failing module from a real file, return (exc, path).""" + path = tmp_path / (name + ".py") + path.write_text(_FAKE_MODULE_SOURCE, encoding="utf-8") + ns = {"__name__": name, "__file__": str(path)} + try: + exec(compile(_FAKE_MODULE_SOURCE, str(path), "exec"), ns) + except ValueError as exc: + return exc, str(path) + raise AssertionError("fake module should have raised") + + +@pytest.fixture +def restore_pdb_predicates(): + """``Pdb._predicates`` aliases the class-level ``default_predicates`` + dict, so tests toggling predicates must restore its contents.""" + saved = dict(debugger.Pdb.default_predicates) + yield + debugger.Pdb.default_predicates.clear() + debugger.Pdb.default_predicates.update(saved) + + +def test_bdbquit_excepthook_is_deprecated(): + with pytest.raises(ValueError, match="deprecated"): + debugger.BdbQuit_excepthook(None, None, None) + + +def test_strip_indentation_and_doc_decoration(): + assert debugger.strip_indentation("hello\n world") == "hello\nworld" + # do_quit is decorated with the stripped stdlib docstring + assert debugger.Pdb.do_quit.__doc__.startswith("q(uit)") + assert debugger.Pdb.do_q.__doc__ == debugger.Pdb.do_quit.__doc__ + + +def test_pdb_context_argument(): + assert _make_pdb(context=None).context == 5 + assert _make_pdb(context="7").context == 7 + p = _make_pdb(context=3) + assert p.context == 3 + p.context = "9" + assert p.context == 9 + with pytest.raises(ValueError): + _make_pdb(context="not-a-number") + with pytest.raises(AssertionError): + p.context = -1 + + +def test_do_context_command(): + p = _post_mortem_pdb(_simple_exc()) + p.onecmd("context 11") + assert p.context == 11 + p.onecmd("context 0") + assert "positive integer" in p.stdout.getvalue() + assert p.context == 11 + p.onecmd("context banana") + assert p.context == 11 + + +def test_set_colors_is_deprecated(): + p = _make_pdb() + with pytest.warns(DeprecationWarning, match="set_theme_name"): + p.set_colors("nocolor") + assert p._theme_name == "nocolor" + assert p.theme is PyColorize.theme_table["nocolor"] + + +def test_get_stack_hides_bdb_internal_frames(): + src = ( + "def runcall():\n" + " def inner():\n" + " raise ValueError('fake bdb error')\n" + " try:\n" + " inner()\n" + " except ValueError as exc:\n" + " holder.append(exc)\n" + "runcall()\n" + ) + holder = [] + exec(compile(src, "bdb.py", "exec"), {"holder": holder}) + exc = holder[0] + tb = exc.__traceback__ + + p = _make_pdb() + # 'runcall' in a file named bdb.py is considered internal ... + assert p._is_internal_frame(tb.tb_frame) is True + # ... but other functions in it are not, nor are frames of other files + assert p._is_internal_frame(tb.tb_next.tb_frame) is False + assert p._is_internal_frame(sys._getframe()) is False + + stack, pos = p.get_stack(None, tb) + assert [f.f_code.co_name for f, _ in stack] == ["inner"] + assert pos == 0 + + +def test_hidden_predicate_readonly_files(restore_pdb_predicates): + p = _make_pdb() + frame = sys._getframe() + # no __tracebackhide__ local, not read-only: not hidden + assert p._hidden_predicate(frame) is False + p._predicates["readonly"] = True + with patch("IPython.core.debugger.os.access", return_value=False): + assert p._hidden_predicate(frame) is True + p._predicates["readonly"] = False + # with the tbhide predicate disabled nothing is ever hidden + p._predicates["tbhide"] = False + assert p._hidden_predicate(_hidden_frame_capture()) is False + + +def test_do_skip_predicates_command(restore_pdb_predicates, capsys): + p = _make_pdb() + + p.do_skip_predicates("") + out = capsys.readouterr().out + assert "current predicates:" in out + assert "tbhide" in out + + p.do_skip_predicates("tbhide") # wrong number of arguments + assert "Usage: skip_predicates" in capsys.readouterr().out + + p.do_skip_predicates("nonsense true") + assert "not in" in capsys.readouterr().out + + p.do_skip_predicates("tbhide banana") + assert "is invalid" in capsys.readouterr().out + + p.do_skip_predicates("tbhide false") + assert p._predicates["tbhide"] is False + + for key in list(p._predicates): + p.do_skip_predicates(f"{key} false") + assert "may not have any effects" in capsys.readouterr().out + + +def test_do_skip_hidden_command(restore_pdb_predicates, capsys): + p = _make_pdb() + p.do_skip_hidden("") + assert "skip_hidden = True" in capsys.readouterr().out + p.do_skip_hidden("false") + assert p.skip_hidden is False + p.do_skip_hidden("yes") + assert p.skip_hidden is True + for key in list(p._predicates): + p._predicates[key] = False + p.do_skip_hidden("true") + assert "may not have any effects" in capsys.readouterr().out + + +def test_where_and_navigation_with_hidden_frames(capsys): + p = _post_mortem_pdb(_mixed_hidden_exc()) + names = [f.f_code.co_name for f, _ in p.stack] + assert names[-3:] == ["visible_top", "hidden_mid", "raiser"] + assert p.curindex == len(p.stack) - 1 + + # hidden_mid is skipped; raiser is the current frame so it is shown + p.onecmd("where") + out = _uncolor(p.stdout.getvalue()) + assert "[... skipping 1 hidden frame(s)]" in out + assert "raiser" in out + + p.onecmd("up") + assert p.curframe.f_code.co_name == "visible_top" + assert "1 hidden frames + 0 ignored modules" in _uncolor( + capsys.readouterr().out + ) + + # now both hidden_mid and raiser are hidden, at the end of the stack + p.onecmd("where") + assert "[... skipping 2 hidden frame(s)]" in _uncolor(p.stdout.getvalue()) + + # every frame below the current one is hidden + p.onecmd("down") + assert "all frames below skipped" in p.stdout.getvalue() + + p.onecmd("skip_hidden false") + p.onecmd("down") + assert p.curframe.f_code.co_name == "hidden_mid" + # not at the newest frame: an unparsable count is reported + p.onecmd("down banana") + assert "Invalid frame count" in p.stdout.getvalue() + p.onecmd("down") + assert p.curframe.f_code.co_name == "raiser" + p.onecmd("down") + assert "Newest frame" in p.stdout.getvalue() + + p.onecmd("up -1") + assert p.curindex == 0 + p.onecmd("up") + assert "Oldest frame" in p.stdout.getvalue() + p.onecmd("down -1") + assert p.curindex == len(p.stack) - 1 + + p.onecmd("up banana") + assert "Invalid frame count" in p.stdout.getvalue() + + p.onecmd("up 99") + assert "all frames above skipped" in p.stdout.getvalue() + + p.onecmd("w 1") + p.onecmd("where banana") + assert "***" in p.stdout.getvalue() + + +def test_navigation_with_ignored_modules(tmp_path, capsys): + exc, path = _exec_failing_module(tmp_path, name="pdb_ignored_mod") + p = _post_mortem_pdb(exc) + assert p.curframe.f_code.co_name == "fail" + + p.onecmd("ignore_module") + assert "No modules are currently ignored." in capsys.readouterr().out + + p.onecmd("ignore_module pdb_ignored_mod") + p.onecmd("ignore_module") + assert "pdb_ignored_mod" in capsys.readouterr().out + + # the module-level frame of the fake module is skipped when going up + p.onecmd("up") + assert p.curframe.f_code.co_name == "_exec_failing_module" + assert "0 hidden frames + 1 ignored modules" in _uncolor( + capsys.readouterr().out + ) + + # going down is impossible: every frame below is in the ignored module + p.onecmd("down") + assert "all frames below skipped" in p.stdout.getvalue() + + p.onecmd("unignore_module nonexistent") + assert "Module nonexistent is not currently ignored" in capsys.readouterr().out + + p.onecmd("unignore_module pdb_ignored_mod") + p.onecmd("ignore_module") + assert "No modules are currently ignored." in capsys.readouterr().out + + p.onecmd("down") + assert p.curframe.f_code.co_name == "" + + +def test_down_reports_skipped_hidden_frames(capsys): + def raiser(): + raise ValueError("down-boom") + + def hidden_mid(): + __tracebackhide__ = True + return raiser() + + try: + hidden_mid() + except ValueError as e: + exc = e + + p = _post_mortem_pdb(exc) + p.onecmd("up") # skips hidden_mid on the way up + capsys.readouterr() + p.onecmd("down") # ... and again on the way down + assert p.curframe.f_code.co_name == "raiser" + assert "1 hidden frames + 0 ignored modules" in _uncolor( + capsys.readouterr().out + ) + + +def test_unignore_module_with_no_ignore_list(capsys): + p = _make_pdb() + assert p.skip is None + p.onecmd("unignore_module") + assert "No modules are currently ignored." in capsys.readouterr().out + assert p.skip == set() + + +def test_do_list_and_longlist(tmp_path): + exc, path = _exec_failing_module(tmp_path) + p = _post_mortem_pdb(exc) + + p.onecmd("list") + out = _uncolor(p.stdout.getvalue()) + assert "raise ValueError" in out + p.onecmd("list") # continue from the last listed position + p.onecmd("list .") # jump back to the current frame + p.onecmd("list 1,5") + assert "Fake module for debugger tests" in _uncolor(p.stdout.getvalue()) + p.onecmd("list 8,2") # last < first is treated as a count + p.onecmd("list 4") + p.onecmd("l 1,3") + # evaluates but cannot be converted to an int + p.onecmd("list 'banana'") + assert "*** Error in argument:" in p.stdout.getvalue() + + p.onecmd("longlist") + assert "def fail():" in _uncolor(p.stdout.getvalue()) + + # 'll' on a module-level frame prints the whole module + p.onecmd("up") + assert p.curframe.f_code.co_name == "" + p.onecmd("ll") + assert "class ExampleClass" in _uncolor(p.stdout.getvalue()) + + +def test_longlist_error_and_exec_filename(tmp_path): + src = "def fail():\n raise ValueError('nofile-boom')\n\nfail()\n" + try: + exec(compile(src, "", "exec"), {}) + except ValueError as e: + exc = e + + p = _post_mortem_pdb(exc) + # the source of a '' frame is not available + p.onecmd("longlist") + assert "***" in p.stdout.getvalue() + + # ... unless _exec_filename points at a real file + path = tmp_path / "exec_source.py" + path.write_text(src, encoding="utf-8") + p._exec_filename = str(path) + p.onecmd("list 1,4") + assert "nofile-boom" in _uncolor(p.stdout.getvalue()) + + +def test_list_shows_breakpoint_markers(tmp_path): + exc, path = _exec_failing_module(tmp_path) + p = _post_mortem_pdb(exc) + try: + p.onecmd(f"break {path}:10") + breaks = p.get_breaks(path, 10) + assert len(breaks) == 1 + bp = breaks[-1] + + p.onecmd("list 8,11") + assert str(bp.number) in _uncolor(p.stdout.getvalue()) + + # disabled breakpoints are rendered with a different token + p.onecmd(f"disable {bp.number}") + p.onecmd("list 8,11") + p.onecmd(f"enable {bp.number}") + p.onecmd("longlist") + finally: + p.clear_all_breaks() + + +def test_format_stack_entry_return_and_args(tmp_path): + exc, path = _exec_failing_module(tmp_path) + p = _post_mortem_pdb(exc) + p._curframe_locals["__return__"] = "some-return-value" + p._curframe_locals["__args__"] = (1, 2, 3) + out = _uncolor(p.format_stack_entry(p.stack[p.curindex])) + assert "some-return-value" in out + assert "(1, 2, 3)" in out + assert "fail" in out + + # formatting of a frame which is not the current one + out2 = _uncolor(p.format_stack_entry(p.stack[p.curindex - 1])) + assert "" in out2 + + # a context of 0 is reported as invalid + p.context = 0 + p.format_stack_entry(p.stack[p.curindex]) + assert "Context must be a positive integer" in p.stdout.getvalue() + + +@pytest.mark.skipif( + not debugger.CHAIN_EXCEPTIONS, + reason="chained exception navigation is not available", +) +def test_do_exceptions_navigation(): + exc = _chained_exc() + p = _make_pdb() + p.reset() + exceptions, tb = p._get_tb_and_exceptions(exc) + assert [type(e) for e in exceptions] == [ZeroDivisionError, ValueError] + p.setup(None, tb) + with p._hold_exceptions(exceptions): + p.onecmd("exceptions") + out = p.stdout.getvalue() + assert "ZeroDivisionError" in out + assert "chained-boom" in out + + p.onecmd("exceptions 0") + assert p.curframe.f_code.co_name == "_chained_exc" + assert "1 / 0" in _uncolor(p.stdout.getvalue()) + + p.onecmd("exceptions not-a-number") + assert "Argument must be an integer" in p.stdout.getvalue() + + p.onecmd("exceptions 99") + assert "No exception with that number" in p.stdout.getvalue() + + p.onecmd("exception 1") # alias, switch back to the ValueError + assert "raise ValueError" in _uncolor(p.stdout.getvalue()) + + # _hold_exceptions cleans up the exception references + assert p._chained_exceptions == tuple() + assert p._chained_exception_index == 0 + + +@pytest.mark.skipif( + not debugger.CHAIN_EXCEPTIONS, + reason="chained exception navigation is not available", +) +def test_do_exceptions_without_traceback(): + try: + raise ValueError("outer-boom") from KeyError("never-raised") + except ValueError as e: + exc = e + + p = _make_pdb() + p.reset() + exceptions, tb = p._get_tb_and_exceptions(exc) + p.setup(None, tb) + with p._hold_exceptions(exceptions): + p.onecmd("exceptions") + assert "never-raised" in p.stdout.getvalue() + # the cause was never raised, so it cannot be selected + p.onecmd("exceptions 0") + assert "does not have a traceback" in p.stdout.getvalue() + + +@pytest.mark.skipif( + not debugger.CHAIN_EXCEPTIONS, + reason="chained exception navigation is not available", +) +def test_do_exceptions_without_chain(): + p = _post_mortem_pdb(_simple_exc().__traceback__) + p.onecmd("exceptions") + assert "Did not find chained exceptions" in p.stdout.getvalue() + + +@pytest.mark.skipif( + not debugger.CHAIN_EXCEPTIONS, + reason="chained exception navigation is not available", +) +def test_do_exceptions_truncates_long_reprs(): + try: + raise ValueError("x" * 200) + except ValueError as e: + exc = e + p = _make_pdb() + p.reset() + exceptions, tb = p._get_tb_and_exceptions(exc) + p.setup(None, tb) + with p._hold_exceptions(exceptions): + p.onecmd("exceptions") + out = p.stdout.getvalue() + assert "..." in out + assert "x" * 200 not in out + + +@pytest.mark.skipif( + not debugger.CHAIN_EXCEPTIONS, + reason="chained exception navigation is not available", +) +def test_get_tb_and_exceptions_edge_cases(): + p = _make_pdb() + + # a plain traceback is passed through unchanged + exc = _simple_exc() + exceptions, tb = p._get_tb_and_exceptions(exc.__traceback__) + assert exceptions == tuple() + assert tb is exc.__traceback__ + + # cycles in the exception chain do not cause an infinite loop + e1, e2 = ValueError("one"), KeyError("two") + e1.__context__ = e2 + e2.__context__ = e1 + exceptions, _ = p._get_tb_and_exceptions(e1) + assert exceptions == (e2, e1) + + # overly long chains are truncated with a message + p.MAX_CHAINED_EXCEPTION_DEPTH = 2 + a, b, c = ValueError("a"), KeyError("b"), TypeError("c") + a.__context__ = b + b.__context__ = c + exceptions, _ = p._get_tb_and_exceptions(a) + assert len(exceptions) == 2 + assert "More than 2 chained exceptions found" in p.stdout.getvalue() + + +def test_pinfo_family_commands(tmp_path, capsys): + exc, path = _exec_failing_module(tmp_path) + p = _post_mortem_pdb(exc) + + p.onecmd("pdef example_function") + assert "example_function(x, y=3)" in _uncolor(capsys.readouterr().out) + + p.onecmd("pdoc ExampleClass") + assert "Docstring for ExampleClass" in _uncolor(capsys.readouterr().out) + + p.onecmd("pinfo value") + assert "int" in _uncolor(capsys.readouterr().out) + + p.onecmd("pinfo2 example_function") + assert "Docstring for example_function" in _uncolor(capsys.readouterr().out) + + p.onecmd("psource example_function") + assert "return x + y" in _uncolor(capsys.readouterr().out) + + p.onecmd("pfile example_function") + assert "Fake module for debugger tests" in _uncolor(capsys.readouterr().out) + + +def test_precmd_question_mark_rewriting(): + p = _make_pdb() + assert p.precmd("foo?") == "pinfo foo" + assert p.precmd("foo??") == "pinfo2 foo" + assert p.precmd("list") == "list" + + +def test_interaction_session_with_exception(): + exc = _simple_exc() + p, out = _run_pdb_session(["myvar + 1", "q"], exc) + assert "ipdb>" in out + assert "43" in out + + +def test_interaction_session_with_traceback_object(): + exc = _simple_exc() + p, out = _run_pdb_session(["myvar", "quit"], exc.__traceback__) + assert "42" in out + + +def test_cmdloop_resumes_after_keyboard_interrupt(): + exc = _simple_exc() + p, out = _run_pdb_session( + ["raise KeyboardInterrupt()", "myvar", "q"], exc + ) + # '--KeyboardInterrupt--' from cmdloop up to Python 3.12, + # '*** KeyboardInterrupt' from onecmd's error handling on 3.13+ + assert "KeyboardInterrupt" in out + assert "42" in out + + +def test_interruptible_pdb_aborts_session_on_keyboard_interrupt(): + exc = _simple_exc() + # on Python <= 3.12 the KeyboardInterrupt aborts the session and the + # trailing 'q' is never consumed; on 3.13+ onecmd reports the exception + # ('*** KeyboardInterrupt') and the session needs the 'q' to end + p, out = _run_pdb_session( + ["raise KeyboardInterrupt()", "q"], exc, cls=debugger.InterruptiblePdb + ) + assert "KeyboardInterrupt" in out + + +@pytest.mark.skipif( + sys.version_info >= (3, 15), + reason="the recursive debugger reads from real stdin on Python 3.15", +) +def test_do_debug_runs_recursive_debugger(): + exc = _simple_exc() + p = _post_mortem_pdb(exc) + p.stdin = _FakeInput(["c"]) + old_trace = sys.gettrace() + try: + p.onecmd("debug myvar + 1") + finally: + sys.settrace(old_trace) + out = p.stdout.getvalue() + assert "ENTERING RECURSIVE DEBUGGER" in out + assert "LEAVING RECURSIVE DEBUGGER" in out + + +def _frame_tagged_with_debuggerskip(): + __debuggerskip__ = True + return sys._getframe() + + +def _plain_frame_capture(): + return sys._getframe() + + +def _frame_with_skipping_parent(): + __debuggerskip__ = True + return _plain_frame_capture() + + +def test_break_anywhere_and_debuggerskip(restore_pdb_predicates): + p = _make_pdb() + tagged = _frame_tagged_with_debuggerskip() + child = _frame_with_skipping_parent() + plain = _plain_frame_capture() + + assert p.break_anywhere(tagged) is True + assert p.break_anywhere(child) is True + assert p.break_anywhere(plain) is False + + # a breakpoint in the frame's file short-circuits the predicates + assert p.set_break(p.canonic(plain.f_code.co_filename), plain.f_lineno) is None + try: + assert p.break_anywhere(plain) + finally: + p.clear_all_breaks() + + assert p._is_in_decorator_internal_and_should_skip(tagged) is True + assert p._cachable_skip(child) is True + assert p._cached_one_parent_frame_debuggerskip(plain) is None + + p._predicates["debuggerskip"] = False + assert p.break_anywhere(plain) is False + assert p._is_in_decorator_internal_and_should_skip(tagged) is False + + +def _hidden_frame_capture(): + __tracebackhide__ = True + return sys._getframe() + + +def test_stop_here_reports_hidden_and_ignored_frames(capsys): + p = _make_pdb() + p.reset() + + # frames in decorator internals tagged with __debuggerskip__ never stop + assert p.stop_here(_frame_tagged_with_debuggerskip()) is False + + hidden = _hidden_frame_capture() + assert p.stop_here(hidden) is True + assert "[... skipped 1 hidden frame(s)]" in _uncolor(capsys.readouterr().out) + + src = "def capture():\n holder.append(sys._getframe())\ncapture()\n" + holder = [] + exec( + compile(src, "", "exec"), + {"holder": holder, "sys": sys, "__name__": "pdb_skip_mod"}, + ) + mod_frame = holder[0] + p.skip = {"pdb_skip_mod"} + assert p.stop_here(mod_frame) is False + assert "[... skipped 1 ignored module(s)]" in _uncolor(capsys.readouterr().out) + + +def test_getsourcelines_module_object(): + p = _make_pdb() + lines, lineno = p.getsourcelines(debugger) + assert lineno == 1 + assert any("class Pdb" in line for line in lines) + + +def test_pdb_creates_shell_when_no_ipython(monkeypatch): + # when there is no IPython instance a terminal shell is created + monkeypatch.setattr(debugger, "get_ipython", lambda: None) + main_before = sys.modules["__main__"] + p = _make_pdb() + assert p.shell is not None + assert sys.modules["__main__"] is main_before + + +def test_set_trace_function_with_header(capsys): + old_trace = sys.gettrace() + try: + with PdbTestInput(["continue"]): + debugger.set_trace(header="custom-pdb-header") + finally: + sys.settrace(old_trace) + out = capsys.readouterr().out + assert "custom-pdb-header" in out + + +# ----------------------------------------------------------------------------- +# TerminalPdb tests +# ----------------------------------------------------------------------------- + + +def _cleanup_terminal_pdb(p): + # Wait for the worker thread, if the executor ever started one: leaving it + # alive keeps the process multi-threaded for every later test, and + # ``os.fork()`` warns about that since Python 3.12. + p.thread_executor.shutdown() + loop = getattr(p, "pt_loop", None) + if loop is not None: + loop.close() + + +@skip_win32 +@pytest.mark.skipif( + sys.version_info >= (3, 15), + reason="pdb internals changed in Python 3.15", +) +def test_terminal_pdb_cmdloop_requires_rawinput(): + p = TerminalPdb(stdout=io.StringIO(), readrc=False) + try: + with pytest.raises(ValueError, match="use_rawinput"): + p.cmdloop() + finally: + _cleanup_terminal_pdb(p) + + +@skip_win32 +def test_terminal_pdb_cmdqueue_session(capsys): + exc = _simple_exc() + p = TerminalPdb(readrc=False) + try: + p.set_theme_name("nocolor") + p.cmdqueue = ["where 1", "myvar", "q"] + p.reset() + p.setup(None, exc.__traceback__) + old_trace = sys.gettrace() + try: + p.cmdloop(intro="TERMINAL-PDB-INTRO") + finally: + sys.settrace(old_trace) + out = _uncolor(capsys.readouterr().out) + assert "TERMINAL-PDB-INTRO" in out + assert "42" in out + + # the completer offers the debugger command names + matches = p._ptcomp.ipy_completer.custom_matchers[0]("whe") + assert "where" in matches + finally: + _cleanup_terminal_pdb(p) + + +@skip_win32 +def test_terminal_pdb_reuses_shell_history(monkeypatch): + from prompt_toolkit.history import InMemoryHistory + + shell = _make_pdb().shell + hist = InMemoryHistory() + monkeypatch.setattr(shell, "debugger_history", hist) + p = TerminalPdb(readrc=False) + try: + assert p.debugger_history is hist + finally: + _cleanup_terminal_pdb(p) + + +@skip_win32 +def test_terminal_pdb_simple_prompt_input(monkeypatch, capsys): + import IPython.terminal.debugger as tdebugger + + monkeypatch.setattr(tdebugger, "_use_simple_prompt", True) + exc = _simple_exc() + p = TerminalPdb(readrc=False) + try: + p.set_theme_name("nocolor") + answers = iter(["myvar * 2", "q"]) + monkeypatch.setattr(builtins, "input", lambda prompt="": next(answers)) + p.reset() + old_trace = sys.gettrace() + try: + p.interaction(None, exc) + finally: + sys.settrace(old_trace) + assert "84" in capsys.readouterr().out + finally: + _cleanup_terminal_pdb(p) + + +@skip_win32 +def test_terminal_pdb_prompt_eof_quits(monkeypatch): + exc = _simple_exc() + p = TerminalPdb(readrc=False) + try: + p.set_theme_name("nocolor") + + def raise_eof(): + raise EOFError + + monkeypatch.setattr(p, "_prompt", raise_eof) + p.reset() + old_trace = sys.gettrace() + try: + p.interaction(None, exc) + finally: + sys.settrace(old_trace) + finally: + _cleanup_terminal_pdb(p) + + +@skip_win32 +def test_terminal_pdb_do_interact(monkeypatch): + # do_interact imports this lazily, so patch the embed module itself + from IPython.terminal import embed as tembed + + calls = {} + + class FakeEmbeddedShell: + def __init__(self, **kwargs): + calls["init"] = kwargs + + def __call__(self, module=None, local_ns=None): + calls["module"] = module + calls["local_ns"] = local_ns + + monkeypatch.setattr(tembed, "InteractiveShellEmbed", FakeEmbeddedShell) + exc = _simple_exc() + p = TerminalPdb(readrc=False) + try: + p.reset() + p.setup(None, exc.__traceback__) + p.do_interact("") + assert calls["init"]["banner1"] == "*interactive*" + assert calls["local_ns"]["myvar"] == 42 + finally: + _cleanup_terminal_pdb(p) + + +@skip_win32 +def test_terminal_debugger_set_trace_uses_caller_frame(monkeypatch): + import IPython.terminal.debugger as tdebugger + + recorded = {} + + def fake_set_trace(self, frame=None): + recorded["frame"] = frame + recorded["self"] = self + + monkeypatch.setattr(TerminalPdb, "set_trace", fake_set_trace) + tdebugger.set_trace() + try: + assert ( + recorded["frame"].f_code.co_name + == "test_terminal_debugger_set_trace_uses_caller_frame" + ) + finally: + _cleanup_terminal_pdb(recorded["self"]) diff --git a/tests/test_debugger_backport.py b/tests/test_debugger_backport.py new file mode 100644 index 00000000000..496bb1eadae --- /dev/null +++ b/tests/test_debugger_backport.py @@ -0,0 +1,171 @@ +"""Tests for IPython.core.debugger_backport. + +``PdbClosureBackport`` is a backport of the CPython 3.13 pdb changes making +closures work in the debugger (gh-83151). It is mixed into +``IPython.core.debugger.Pdb`` on Python < 3.13 only, so the tests going +through ``Pdb`` commands are skipped on newer versions, while the +``_exec_in_closure`` unit tests always run. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import io +import sys + +import pytest + +from IPython.core import debugger +from IPython.core.debugger_backport import PdbClosureBackport + +backport_in_use = pytest.mark.skipif( + sys.version_info >= (3, 13), + reason="the closure backport is only used on Python < 3.13", +) + + +class _FakeInput: + """Fake stdin feeding predefined lines to the debugger.""" + + def __init__(self, lines): + self.lines = iter(lines) + + def readline(self): + line = next(self.lines) + print(line) + return line + "\n" + + +def _post_mortem_pdb(): + """Return a Pdb instance set up post-mortem on a small traceback.""" + + def fail(): + value = 3 + raise ValueError("backport-boom %s" % value) + + try: + fail() + except ValueError as exc: + err = exc + + p = debugger.Pdb(stdout=io.StringIO(), readrc=False) + p.set_theme_name("nocolor") + p.reset() + p.setup(None, err.__traceback__) + assert p.curframe_locals["value"] == 3 + return p + + +# ----------------------------------------------------------------------------- +# _exec_in_closure unit tests (run on every Python version) +# ----------------------------------------------------------------------------- + + +def test_exec_in_closure_returns_false_for_flat_source(): + # no nested code object: the caller should fall back to a plain exec + ns = {"x": 1} + assert PdbClosureBackport()._exec_in_closure("x + 1", {}, ns) is False + + +def test_exec_in_closure_prints_expression_result(capsys): + ns = {"x": 2} + assert ( + PdbClosureBackport()._exec_in_closure( + "sum(x * i for i in range(3))", {}, ns + ) + is True + ) + assert "6" in capsys.readouterr().out + + +def test_exec_in_closure_none_result_is_not_printed(capsys): + ns = {"x": 2} + assert PdbClosureBackport()._exec_in_closure("(lambda: None)()", {}, ns) is True + assert "None" not in capsys.readouterr().out + + +def test_exec_in_closure_writes_locals_back(): + ns = {"x": 5} + src = "def f_local():\n return x\nresult = f_local()" + assert PdbClosureBackport()._exec_in_closure(src, {}, ns) is True + assert ns["result"] == 5 + assert "f_local" in ns + # the internal evaluation scaffolding must not leak into the locals + assert "__pdb_eval__" not in ns + + +def test_exec_in_closure_bad_local_names_fall_back(): + # "a-b" is not a valid identifier: building the closure source fails + # and the method reports False so a plain exec is used instead + assert ( + PdbClosureBackport()._exec_in_closure( + "def f():\n return 1\nf()", {}, {"a-b": 1} + ) + is False + ) + + +def test_exec_in_closure_error_in_code_falls_back(): + src = "def f():\n raise ValueError('inner-err')\nf()" + assert PdbClosureBackport()._exec_in_closure(src, {}, {"x": 1}) is False + + +# ----------------------------------------------------------------------------- +# default() through IPython's Pdb (only on Python versions using the backport) +# ----------------------------------------------------------------------------- + + +@backport_in_use +def test_default_executes_expressions(): + p = _post_mortem_pdb() + p.onecmd("value + 1") + assert "4" in p.stdout.getvalue() + + +@backport_in_use +def test_default_strips_bang_prefix(): + p = _post_mortem_pdb() + p.onecmd("!value * 10") + assert "30" in p.stdout.getvalue() + + +@backport_in_use +def test_default_closure_expression(): + p = _post_mortem_pdb() + # a generator expression needs the closure machinery to see `value`; + # default() redirects sys.stdout to the debugger stdout while executing + p.onecmd("sum(value * i for i in range(3))") + assert "9" in p.stdout.getvalue() + + +@backport_in_use +def test_default_assignment_updates_locals(): + p = _post_mortem_pdb() + p.onecmd("squares = [i * i for i in range(4)]") + assert p.curframe_locals["squares"] == [0, 1, 4, 9] + p.onecmd("squares") + assert "[0, 1, 4, 9]" in p.stdout.getvalue() + + +@backport_in_use +def test_default_reports_errors(): + p = _post_mortem_pdb() + p.onecmd("undefined_name_xyz") + out = p.stdout.getvalue() + assert "***" in out + assert "NameError" in out + + +@backport_in_use +def test_default_multiline_command(): + # The multi-line code path of the backported default() relies on + # pdb._disable_command_completion which only exists on Python >= 3.13, + # so on the versions actually using the backport it reports an error + # instead of silently hanging. + p = _post_mortem_pdb() + p.stdin = _FakeInput([" y = 1", ""]) + p.use_rawinput = False + p.onecmd("if True:") + out = p.stdout.getvalue() + assert "***" in out + assert "AttributeError" in out diff --git a/IPython/testing/tests/test_decorators.py b/tests/test_decorators.py similarity index 68% rename from IPython/testing/tests/test_decorators.py rename to tests/test_decorators.py index ef346253487..cd4854a2301 100644 --- a/IPython/testing/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,20 +1,19 @@ -"""Tests for the decorators we've created for IPython. -""" +"""Tests for the decorators we've created for IPython.""" # Module imports # Std lib import inspect import sys -# Third party -import nose.tools as nt - # Our own from IPython.testing import decorators as dec +from IPython.testing.skipdoctest import skip_doctest +from IPython.utils.text import dedent -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Utilities + # Note: copied from OInspect, kept here so the testing stuff doesn't create # circular dependencies and is easier to reuse. def getargspec(obj): @@ -33,46 +32,49 @@ def getargspec(obj): elif inspect.ismethod(obj): func_obj = obj.__func__ else: - raise TypeError('arg is not a Python function') + raise TypeError("arg is not a Python function") args, varargs, varkw = inspect.getargs(func_obj.__code__) return args, varargs, varkw, func_obj.__defaults__ -#----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- # Testing functions -@dec.as_unittest -def trivial(): + +def test_trivial(): """A trivial test""" pass -@dec.skip +@dec.skip() def test_deliberately_broken(): """A deliberately broken test - we want to skip this one.""" - 1/0 + 1 / 0 -@dec.skip('Testing the skip decorator') + +@dec.skip("Testing the skip decorator") def test_deliberately_broken2(): """Another deliberately broken test - we want to skip this one.""" - 1/0 + 1 / 0 # Verify that we can correctly skip the doctest for a function at will, but # that the docstring itself is NOT destroyed by the decorator. -def doctest_bad(x,y=1,**k): +@skip_doctest +def doctest_bad(x, y=1, **k): """A function whose doctest we need to skip. >>> 1+1 3 """ - print('x:',x) - print('y:',y) - print('k:',k) + print("x:", x) + print("y:", y) + print("k:", k) def call_doctest_bad(): """Check that we can still call the decorated functions. - + >>> doctest_bad(3,y=4) x: 3 y: 4 @@ -82,18 +84,18 @@ def call_doctest_bad(): def test_skip_dt_decorator(): - """Doctest-skipping decorator should preserve the docstring. - """ + """Doctest-skipping decorator should preserve the docstring.""" # Careful: 'check' must be a *verbatim* copy of the doctest_bad docstring! check = """A function whose doctest we need to skip. >>> 1+1 3 """ + # Fetch the docstring from doctest_bad after decoration. val = doctest_bad.__doc__ - - nt.assert_equal(check,val,"doctest_bad docstrings don't match") + + assert dedent(check) == dedent(val), "doctest_bad docstrings don't match" # Doctest skipping should work for class methods too @@ -106,7 +108,8 @@ class FooClass(object): 2 """ - def __init__(self,x): + @skip_doctest + def __init__(self, x): """Make a FooClass. Example: @@ -114,10 +117,11 @@ def __init__(self,x): >>> f = FooClass(3) junk """ - print('Making a FooClass.') + print("Making a FooClass.") self.x = x - - def bar(self,y): + + @skip_doctest + def bar(self, y): """Example: >>> ff = FooClass(3) @@ -126,9 +130,9 @@ def bar(self,y): >>> 1/0 bam! """ - return 1/y + return 1 / y - def baz(self,y): + def baz(self, y): """Example: >>> ff2 = FooClass(3) @@ -136,29 +140,15 @@ def baz(self,y): >>> ff2.baz(3) True """ - return self.x==y + return self.x == y def test_skip_dt_decorator2(): - """Doctest-skipping decorator should preserve function signature. - """ + """Doctest-skipping decorator should preserve function signature.""" # Hardcoded correct answer - dtargs = (['x', 'y'], None, 'k', (1,)) + dtargs = (["x", "y"], None, "k", (1,)) # Introspect out the value dtargsr = getargspec(doctest_bad) - assert dtargsr==dtargs, \ - "Incorrectly reconstructed args for doctest_bad: %s" % (dtargsr,) - - -@dec.skip_linux -def test_linux(): - nt.assert_false(sys.platform.startswith('linux'),"This test can't run under linux") - -@dec.skip_win32 -def test_win32(): - nt.assert_not_equal(sys.platform,'win32',"This test can't run under windows") - -@dec.skip_osx -def test_osx(): - nt.assert_not_equal(sys.platform,'darwin',"This test can't run under osx") - + assert dtargsr == dtargs, "Incorrectly reconstructed args for doctest_bad: %s" % ( + dtargsr, + ) diff --git a/IPython/utils/tests/test_decorators.py b/tests/test_decorators_2.py similarity index 85% rename from IPython/utils/tests/test_decorators.py rename to tests/test_decorators_2.py index 38e871deb7e..04c87b6594b 100644 --- a/IPython/utils/tests/test_decorators.py +++ b/tests/test_decorators_2.py @@ -1,10 +1,11 @@ from IPython.utils import decorators + def test_flag_calls(): @decorators.flag_calls def f(): pass - + assert not f.called f() - assert f.called \ No newline at end of file + assert f.called diff --git a/tests/test_deepreload.py b/tests/test_deepreload.py new file mode 100644 index 00000000000..4eeaa11c18b --- /dev/null +++ b/tests/test_deepreload.py @@ -0,0 +1,316 @@ +# -*- coding: utf-8 -*- +"""Test suite for the deepreload module.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import sys +import types +from pathlib import Path + +import pytest +from tempfile import TemporaryDirectory + +from IPython.lib import deepreload +from IPython.lib.deepreload import modules_reloading +from IPython.lib.deepreload import reload as dreload +from IPython.utils.syspathcontext import prepended_to_syspath + + +def test_deepreload(): + "Test that dreload does deep reloads and skips excluded modules." + with TemporaryDirectory() as tmpdir: + with prepended_to_syspath(tmpdir): + tmpdirpath = Path(tmpdir) + with open(tmpdirpath / "A.py", "w", encoding="utf-8") as f: + f.write("class Object:\n pass\nok = True\n") + with open(tmpdirpath / "B.py", "w", encoding="utf-8") as f: + f.write("import A\nassert A.ok, 'we are fine'\n") + import A + import B + + # Test that A is not reloaded. + obj = A.Object() + dreload(B, exclude=["A"]) + assert isinstance(obj, A.Object) is True + + # Test that an import failure will not blow-up us. + A.ok = False + with pytest.raises(AssertionError, match="we are fine"): + dreload(B, exclude=["A"]) + assert len(modules_reloading) == 0 + assert not A.ok + + # Test that A is reloaded. + obj = A.Object() + A.ok = False + dreload(B) + assert A.ok + assert isinstance(obj, A.Object) is False + + +def test_deepreload_package_relative_imports(): + """Reloading a package exercises dotted, relative and star imports.""" + with TemporaryDirectory() as tmpdir: + with prepended_to_syspath(tmpdir): + pkgdir = Path(tmpdir) / "dpkg" + pkgdir.mkdir() + (pkgdir / "__init__.py").write_text( + "from dpkg import dmod1\n__all__ = ['dmod1', 'dmod2']\n", + encoding="utf-8", + ) + (pkgdir / "dmod1.py").write_text( + "from . import dmod2\nfrom .dmod2 import val\n", encoding="utf-8" + ) + (pkgdir / "dmod2.py").write_text("val = 1\n", encoding="utf-8") + main = Path(tmpdir) / "dmain.py" + main.write_text( + "import dpkg.dmod1\nfrom dpkg import dmod2\nfrom dpkg import *\n", + encoding="utf-8", + ) + + import dmain + import dpkg + + # mutate state that only a re-execution of the modules restores + dpkg.dmod2.val = 999 + newmain = dreload(dmain) + assert newmain is dmain + # submodules reachable through relative imports were re-executed + assert dpkg.dmod2.val == 1 + assert dmain.dmod2.val == 1 + assert dmain.dmod1.val == 1 + assert len(modules_reloading) == 0 + + for name in list(sys.modules): + if name == "dmain" or name.startswith("dpkg"): + del sys.modules[name] + + +def test_not_module(): + pytest.raises(TypeError, dreload, "modulename") + + +def test_not_in_sys_modules(): + fake_module = types.ModuleType("fake_module") + with pytest.raises(ImportError, match="not in sys.modules"): + dreload(fake_module) + + +def test_reload_types_module_is_noop(): + # the types module is hardcoded to never be reloaded + assert dreload(types) is types + + +class TestGetParent: + def test_no_level(self): + assert deepreload.get_parent({"__package__": "os"}, 0) == (None, "") + + def test_globals_not_dict(self): + assert deepreload.get_parent(None, 1) == (None, "") + + def test_package_not_string(self): + with pytest.raises(ValueError, match="__package__ set to non-string"): + deepreload.get_parent({"__package__": 42}, 1) + + def test_relative_import_in_non_package(self): + with pytest.raises(ValueError, match="non-package"): + deepreload.get_parent({"__package__": ""}, 1) + + def test_empty_package_negative_level(self): + assert deepreload.get_parent({"__package__": ""}, -1) == (None, "") + + def test_package_set(self): + parent, name = deepreload.get_parent({"__package__": "os"}, 1) + assert parent is sys.modules["os"] + assert name == "os" + + def test_no_name(self): + assert deepreload.get_parent({}, 1) == (None, "") + + def test_name_with_path_is_package(self): + import json + + globals_ = {"__name__": "json", "__path__": []} + parent, name = deepreload.get_parent(globals_, 1) + assert parent is json + assert name == "json" + # get_parent fills in __package__ as a side effect + assert globals_["__package__"] == "json" + + def test_plain_module_infers_package(self): + globals_ = {"__name__": "os.path"} + parent, name = deepreload.get_parent(globals_, 1) + assert parent is sys.modules["os"] + assert name == "os" + assert globals_["__package__"] == "os" + + def test_toplevel_module_relative_import(self): + with pytest.raises(ValueError, match="non-package"): + deepreload.get_parent({"__name__": "os"}, 1) + + def test_toplevel_module_absolute_import(self): + globals_ = {"__name__": "os"} + assert deepreload.get_parent(globals_, -1) == (None, "") + assert globals_["__package__"] is None + + def test_relative_import_beyond_toplevel(self): + with pytest.raises(ValueError, match="beyond top-level"): + deepreload.get_parent({"__package__": "os"}, 2) + + def test_parent_not_loaded_relative(self): + with pytest.raises(SystemError, match="not loaded"): + deepreload.get_parent({"__package__": "not_a_real_module_xyz"}, 1) + + def test_parent_not_found_absolute(self): + with pytest.warns(UserWarning, match="not found"): + parent, name = deepreload.get_parent( + {"__package__": "not_a_real_module_xyz"}, -1 + ) + assert parent is None + assert name == "not_a_real_module_xyz" + + +class TestLoadNext: + def test_empty_name(self): + mod = types.ModuleType("mod") + assert deepreload.load_next(mod, None, "", "buf") == (mod, None, "buf") + + def test_dot_prefixed_name(self): + with pytest.raises(ValueError, match="Empty module name"): + deepreload.load_next(None, None, ".foo", "") + + def test_import_failure(self, monkeypatch): + monkeypatch.setattr( + deepreload, "import_submodule", lambda mod, subname, fullname: None + ) + with pytest.raises(ImportError, match="No module named"): + deepreload.load_next(None, None, "foo", "") + + def test_altmod_fallback(self, monkeypatch): + """When the parent lookup fails, load_next retries with altmod.""" + sentinel = types.ModuleType("sentinel") + mod = types.ModuleType("mod") + calls = [] + + def fake_import_submodule(m, subname, fullname): + calls.append((m, subname, fullname)) + return sentinel if m is None else None + + monkeypatch.setattr(deepreload, "import_submodule", fake_import_submodule) + result, next_, buf = deepreload.load_next(mod, None, "foo.bar", "pfx") + assert result is sentinel + assert next_ == "bar" + assert buf == "foo" + assert calls == [(mod, "foo", "pfx.foo"), (None, "foo", "foo")] + + +class TestEnsureFromlist: + def test_no_path_attribute(self): + # modules without __path__ (i.e. not packages) are ignored + mod = types.ModuleType("plain") + assert deepreload.ensure_fromlist(mod, ["anything"], "plain", 0) is None + + def test_fromlist_item_not_string(self): + pkg = types.ModuleType("fakepkg") + pkg.__path__ = [] + with pytest.raises(TypeError, match="not a string"): + deepreload.ensure_fromlist(pkg, [42], "fakepkg", 0) + + def test_star_without_all(self): + pkg = types.ModuleType("fakepkg") + pkg.__path__ = [] + # no __all__: '*' is silently ignored + assert deepreload.ensure_fromlist(pkg, ["*"], "fakepkg", 0) is None + + def test_star_recursive_is_skipped(self): + pkg = types.ModuleType("fakepkg") + pkg.__path__ = [] + pkg.__all__ = ["*"] + # a '*' inside __all__ must not recurse endlessly + assert deepreload.ensure_fromlist(pkg, ["*"], "fakepkg", 1) is None + + def test_existing_attribute_not_reimported(self, monkeypatch): + pkg = types.ModuleType("fakepkg") + pkg.__path__ = [] + pkg.present = True + + def boom(mod, subname, fullname): + raise AssertionError("should not be called") + + monkeypatch.setattr(deepreload, "import_submodule", boom) + deepreload.ensure_fromlist(pkg, ["present"], "fakepkg", 0) + + def test_missing_attribute_imported(self, monkeypatch): + pkg = types.ModuleType("fakepkg") + pkg.__path__ = [] + imported = [] + monkeypatch.setattr( + deepreload, + "import_submodule", + lambda mod, subname, fullname: imported.append((subname, fullname)), + ) + deepreload.ensure_fromlist(pkg, ["missing"], "fakepkg", 0) + assert imported == [("missing", "fakepkg.missing")] + + +class TestImportSubmodule: + def test_already_found(self, monkeypatch): + monkeypatch.setitem(deepreload.found_now, "os", 1) + assert deepreload.import_submodule(None, "os", "os") is sys.modules["os"] + + def test_fresh_import(self, capsys): + """A module not yet in sys.modules is imported, not reloaded.""" + with TemporaryDirectory() as tmpdir: + with prepended_to_syspath(tmpdir): + (Path(tmpdir) / "fresh_mod_xyz.py").write_text( + "fresh = True\n", encoding="utf-8" + ) + try: + m = deepreload.import_submodule( + None, "fresh_mod_xyz", "fresh_mod_xyz" + ) + assert m.fresh is True + assert "Reloading fresh_mod_xyz" in capsys.readouterr().out + finally: + sys.modules.pop("fresh_mod_xyz", None) + deepreload.found_now.clear() + + def test_failed_reload_restores_module(self, monkeypatch, capsys): + modname = "fake_failing_module" + oldm = types.ModuleType(modname) + monkeypatch.setitem(sys.modules, modname, oldm) + + def failing_reload(m): + del sys.modules[modname] + raise RuntimeError("reload failed") + + monkeypatch.setattr(deepreload.importlib, "reload", failing_reload) + with pytest.raises(RuntimeError, match="reload failed"): + deepreload.import_submodule(None, modname, modname) + # the original module object must be restored + assert sys.modules[modname] is oldm + assert "Reloading fake_failing_module" in capsys.readouterr().out + deepreload.found_now.clear() + + +class TestAddSubmodule: + def test_mod_none_is_noop(self): + assert deepreload.add_submodule(None, None, "foo", "foo") is None + + def test_sets_attribute(self): + mod = types.ModuleType("parent") + sub = types.ModuleType("parent.child") + deepreload.add_submodule(mod, sub, "parent.child", "child") + assert mod.child is sub + + def test_submod_none_looked_up_in_sys_modules(self): + mod = types.ModuleType("parent") + deepreload.add_submodule(mod, None, "os", "os") + assert mod.os is sys.modules["os"] + + +class TestDeepImportHook: + def test_empty_module_name(self): + with pytest.raises(ValueError, match="Empty module name"): + deepreload.deep_import_hook("") diff --git a/tests/test_demo.py b/tests/test_demo.py new file mode 100644 index 00000000000..f8e386c5a23 --- /dev/null +++ b/tests/test_demo.py @@ -0,0 +1,434 @@ +# -*- coding: utf-8 -*- +"""Tests for the IPython.lib.demo module.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import builtins +import io + +import pytest + +from IPython.lib import demo as demo_module +from IPython.lib.demo import ( + ClearDemo, + ClearIPDemo, + Demo, + IPythonDemo, + IPythonLineDemo, + LineDemo, + slide, +) + +SAMPLE_DEMO = """\ +print('hello') + +# stop + +x = 1 +y = 2 + +# stop +# silent + +z = x + y + +# stop +# auto +print('auto block') + +# --- stop --- + +print('last block') +""" + + +@pytest.fixture +def make_demo(tmp_path): + """Factory building demo objects from source text, closing files on exit.""" + demos = [] + + def _make(src_text, cls=Demo, filename="sample_demo.py", **kwargs): + fname = tmp_path / filename + fname.write_text(src_text, encoding="utf-8") + d = cls(str(fname), **kwargs) + demos.append(d) + return d + + yield _make + for d in demos: + d.fobj.close() + + +def run_to_completion(d): + """Run all remaining blocks of an automatic demo.""" + while not d.finished: + d() + + +def test_demo_parsing(make_demo): + d = make_demo(SAMPLE_DEMO) + assert d.nblocks == 5 + assert d._silent == [False, False, True, False, False] + assert d._auto == [False, False, False, True, False] + assert d.auto_all is False + assert d.block_index == 0 + assert d.finished is False + assert d.title == "sample_demo.py" + # stop markers are removed by the block splitting, auto tags stripped + assert " stop" not in "".join(d.src_blocks) + assert " auto" not in d.src_blocks[3] + assert "auto block" in d.src_blocks[3] + + +def test_demo_title_and_argv(make_demo): + d = make_demo(SAMPLE_DEMO, title="My Demo", arg_str="--opt value") + assert d.title == "My Demo" + assert d.sys_argv[1:] == ["--opt", "value"] + + +def test_demo_from_filelike(): + src = io.StringIO("print('from filelike')\n") + d = Demo(src) + assert d.fname == "from a file-like object" + assert d.title == "from a file-like object" + assert d.nblocks == 1 + src.close() + + src2 = io.StringIO("print('named')\n") + d2 = Demo(src2, title="named demo") + assert d2.title == "named demo" + src2.close() + + +def test_demo_auto_all_tag(make_demo): + src = "# auto_all\na = 1\n# stop\nb = 2\n" + d = make_demo(src) + assert d.auto_all is True + # the auto_all marker must be stripped from the first block + assert "auto_all" not in d.src_blocks[0] + run_to_completion(d) + assert d.user_ns["a"] == 1 + assert d.user_ns["b"] == 2 + + +def test_demo_run_and_finish(make_demo, capsys): + d = make_demo(SAMPLE_DEMO, auto_all=True) + run_to_completion(d) + out = capsys.readouterr().out + assert "hello" in out + assert "auto block" in out + assert "last block" in out + assert "Executing silent block" in out + assert "END OF DEMO" in out + assert d.finished is True + assert d.user_ns["z"] == 3 + # the interactive namespace is updated at each block + assert d.ip_ns["z"] == 3 + + # calling a finished demo only prints a message + capsys.readouterr() + d() + out = capsys.readouterr().out + assert "Demo finished" in out + # explicitly running one block by index still works on a finished demo + d(0) + out = capsys.readouterr().out + assert "hello" in out + + +def test_demo_interactive_confirm(make_demo, capsys, monkeypatch): + d = make_demo("run_me = True\n") + monkeypatch.setattr("builtins.input", lambda prompt="": "") + d() + out = capsys.readouterr().out + assert "Press to quit" in out + assert d.user_ns["run_me"] is True + + +def test_demo_interactive_quit(make_demo, capsys, monkeypatch): + d = make_demo("skipped = True\n") + monkeypatch.setattr("builtins.input", lambda prompt="": "q") + d() + out = capsys.readouterr().out + assert "Block NOT executed" in out + assert "skipped" not in d.user_ns + + +def test_demo_seek_back_jump_again(make_demo, capsys): + d = make_demo(SAMPLE_DEMO, auto_all=True) + d() # block 0 + d() # block 1 + assert d.block_index == 2 + d.back() + assert d.block_index == 1 + d.jump(1) + assert d.block_index == 2 + d.seek(0) + assert d.block_index == 0 + # negative indices seek from the end + d.seek(-1) + assert d.block_index == d.nblocks - 1 + run_to_completion(d) + assert d.finished is True + # seeking resets the finished flag + d.seek(1) + assert d.finished is False + # again() re-runs the previous block + d() + capsys.readouterr() + d.again() + assert d.block_index == 2 + out = capsys.readouterr().out + assert "block # 1" in out + + +def test_demo_invalid_index(make_demo): + d = make_demo(SAMPLE_DEMO, auto_all=True) + with pytest.raises(ValueError, match="invalid block index"): + d.seek(d.nblocks) + with pytest.raises(ValueError, match="invalid block index"): + d(-1) + with pytest.raises(ValueError, match="invalid block index"): + d.jump(1000) + + +def test_demo_reload(make_demo): + d = make_demo(SAMPLE_DEMO, auto_all=True) + run_to_completion(d) + # reload() re-reads the source (closing the previous file) and resets + d.reload() + assert d.block_index == 0 + assert d.finished is False + assert d.nblocks == 5 + + +def test_demo_show_and_edit_when_finished(make_demo, capsys): + d = make_demo("done = 1\n", auto_all=True) + run_to_completion(d) + capsys.readouterr() + # show() and edit() on a finished demo just print a message + d.show() + assert "Demo finished" in capsys.readouterr().out + d.edit() + assert "Demo finished" in capsys.readouterr().out + + +def test_demo_reset(make_demo): + d = make_demo(SAMPLE_DEMO, auto_all=True) + run_to_completion(d) + assert d.finished is True + d.reset() + assert d.block_index == 0 + assert d.finished is False + assert d.user_ns == {} + + +def test_demo_show(make_demo, capsys): + d = make_demo(SAMPLE_DEMO) + d.show(0) + out = capsys.readouterr().out + assert "block # 0" in out + assert "4 remaining" in out + assert "hello" in out + + +def test_demo_show_all(make_demo, capsys): + d = make_demo(SAMPLE_DEMO) + d.show_all() + out = capsys.readouterr().out + # every block is shown, silent ones marked as such + assert "block # 0" in out + assert "SILENT block # 2" in out + assert "last block" in out + + +def test_demo_exception_shows_traceback(make_demo, monkeypatch): + d = make_demo("1/0\n", auto_all=True) + tracebacks = [] + monkeypatch.setattr( + d, "ip_showtb", lambda *a, **kw: tracebacks.append(kw), raising=False + ) + d() + assert tracebacks == [{"filename": d.fname}] + assert d.finished is True + + +def test_demo_outside_ipython(make_demo, monkeypatch, capsys): + monkeypatch.delattr(builtins, "get_ipython") + d = make_demo("standalone = 42\n", auto_all=True) + assert d.inside_ipython is False + run_to_completion(d) + assert d.user_ns["standalone"] == 42 + assert "END OF DEMO" in capsys.readouterr().out + + +def test_demo_edit(make_demo, monkeypatch, capsys): + d = make_demo("first = 1\n# stop\nsecond = 2\n", auto_all=True) + d() # run block 0 + + def fake_editor(filename, linenum=None, *args, **kwargs): + with open(filename, "w", encoding="utf-8") as f: + f.write("first = 100\n") + + monkeypatch.setitem(d.shell.hooks, "editor", fake_editor) + d.edit() + # the edited block was stored and re-executed + assert d.src_blocks[0] == "first = 100\n" + assert d.user_ns["first"] == 100 + assert d.block_index == 1 + + +def test_demo_marquee(): + src = io.StringIO("pass\n") + d = Demo(src) + mq = d.marquee("hello", width=20, mark="*") + assert "hello" in mq + assert "*" in mq + src.close() + + +def test_ipython_demo(make_demo, capsys): + d = make_demo("via_ip = 11\nprint('ip ran')\n", cls=IPythonDemo, auto_all=True) + run_to_completion(d) + out = capsys.readouterr().out + assert "ip ran" in out + # IPythonDemo runs code through the real shell namespace + assert d.shell.user_ns["via_ip"] == 11 + + +def test_line_demo(make_demo): + d = make_demo("a = 1\n\nb = 2\nc = a + b\n", cls=LineDemo) + # blank lines are dropped, each remaining line is a block + assert d.nblocks == 3 + assert d.auto_all is True + assert d._silent == [False] * 3 + assert d._auto == [True] * 3 + d() + assert d.user_ns["a"] == 1 + assert "b" not in d.user_ns + run_to_completion(d) + assert d.user_ns["c"] == 3 + assert d.finished is True + + +def test_ipython_line_demo(make_demo): + d = make_demo("line_demo_x = 5\n", cls=IPythonLineDemo) + run_to_completion(d) + assert d.shell.user_ns["line_demo_x"] == 5 + + +def test_clear_demo(make_demo, monkeypatch, capsys): + cleared = [] + from IPython.utils import terminal + + monkeypatch.setattr(terminal, "_term_clear", lambda: cleared.append(True)) + d = make_demo("cleared_var = 1\n", cls=ClearDemo, auto_all=True) + # ClearMixin uses empty marquees + assert d.marquee("anything") == "" + run_to_completion(d) + out = capsys.readouterr().out + # empty marquee suppresses the END OF DEMO banner + assert "END OF DEMO" not in out + assert cleared == [True] + assert d.user_ns["cleared_var"] == 1 + assert d.finished is True + + +def test_clear_ip_demo(make_demo, monkeypatch): + from IPython.utils import terminal + + monkeypatch.setattr(terminal, "_term_clear", lambda: None) + d = make_demo("clear_ip_var = 7\n", cls=ClearIPDemo, auto_all=True) + assert d.marquee("x") == "" + run_to_completion(d) + assert d.shell.user_ns["clear_ip_var"] == 7 + + +def test_demo_format_rst(make_demo): + src = '"""A docstring long enough for rst.\n\nMore text.\n"""\n# a comment\nx = 1\n' + d = make_demo(src, format_rst=True) + assert d.format_rst is True + assert hasattr(d, "rst_lexer") + colored = d.src_blocks_colored[0] + assert "comment" in colored + assert "x" in colored + # highlight() can also be called directly on new content + assert "y" in d.highlight("y = 2 # another comment") + + +def test_slide(tmp_path, monkeypatch, capsys): + fname = tmp_path / "slides.py" + fname.write_text("s1 = 1\n# stop\ns2 = 2\n", encoding="utf-8") + + created = [] + + class TrackingDemo(Demo): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + created.append(self) + + monkeypatch.setattr(demo_module, "Demo", TrackingDemo) + monkeypatch.setattr("builtins.input", lambda prompt="": "") + try: + slide(str(fname), noclear=True, auto_all=True, format_rst=False) + (d,) = created + assert d.finished is True + assert d.user_ns["s2"] == 2 + out = capsys.readouterr().out + # the delimiter prompt is emitted between slides + assert "END OF DEMO" in out + finally: + for d in created: + d.fobj.close() + + +def test_slide_with_clear(tmp_path, monkeypatch, capsys): + from IPython.utils import terminal + + fname = tmp_path / "slides_clear.py" + fname.write_text("c1 = 1\n", encoding="utf-8") + + created = [] + + class TrackingClearDemo(ClearDemo): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + created.append(self) + + monkeypatch.setattr(demo_module, "ClearDemo", TrackingClearDemo) + monkeypatch.setattr("builtins.input", lambda prompt="": "") + monkeypatch.setattr(terminal, "_term_clear", lambda: None) + try: + slide(str(fname), noclear=False, auto_all=True, format_rst=False) + (d,) = created + assert isinstance(d, ClearDemo) + assert d.finished is True + assert d.user_ns["c1"] == 1 + finally: + for d in created: + d.fobj.close() + + +def test_slide_keyboard_interrupt(tmp_path, monkeypatch): + fname = tmp_path / "slides2.py" + fname.write_text("s1 = 1\n", encoding="utf-8") + + created = [] + + class TrackingDemo(Demo): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + created.append(self) + + def raise_interrupt(prompt=""): + raise KeyboardInterrupt + + monkeypatch.setattr(demo_module, "Demo", TrackingDemo) + monkeypatch.setattr("builtins.input", raise_interrupt) + try: + with pytest.raises(SystemExit): + slide(str(fname), noclear=True, auto_all=True, format_rst=False) + finally: + for d in created: + d.fobj.close() diff --git a/IPython/utils/tests/test_dir2.py b/tests/test_dir2.py similarity index 57% rename from IPython/utils/tests/test_dir2.py rename to tests/test_dir2.py index bbebf60aed8..bf7f5e57ea2 100644 --- a/IPython/utils/tests/test_dir2.py +++ b/tests/test_dir2.py @@ -1,6 +1,7 @@ -import nose.tools as nt from IPython.utils.dir2 import dir2 +import pytest + class Base(object): x = 1 @@ -9,22 +10,22 @@ class Base(object): def test_base(): res = dir2(Base()) - assert ('x' in res) - assert ('z' in res) - assert ('y' not in res) - assert ('__class__' in res) - nt.assert_equal(res.count('x'), 1) - nt.assert_equal(res.count('__class__'), 1) + assert "x" in res + assert "z" in res + assert "y" not in res + assert "__class__" in res + assert res.count("x") == 1 + assert res.count("__class__") == 1 -def test_SubClass(): +def test_SubClass(): class SubClass(Base): y = 2 res = dir2(SubClass()) - assert ('y' in res) - nt.assert_equal(res.count('y'), 1) - nt.assert_equal(res.count('x'), 1) + assert "y" in res + assert res.count("y") == 1 + assert res.count("x") == 1 def test_SubClass_with_trait_names_attr(): @@ -35,24 +36,31 @@ class SubClass(Base): trait_names = 44 res = dir2(SubClass()) - assert('trait_names' in res) + assert "trait_names" in res def test_misbehaving_object_without_trait_names(): # dir2 shouldn't raise even when objects are dumb and raise # something other than AttribteErrors on bad getattr. - class MisbehavingGetattr(object): - def __getattr__(self): + class MisbehavingGetattr: + def __getattr__(self, attr): raise KeyError("I should be caught") def some_method(self): - pass + return True class SillierWithDir(MisbehavingGetattr): def __dir__(self): - return ['some_method'] + return ["some_method"] for bad_klass in (MisbehavingGetattr, SillierWithDir): - res = dir2(bad_klass()) - assert('some_method' in res) + obj = bad_klass() + + assert obj.some_method() + + with pytest.raises(KeyError): + obj.other_method() + + res = dir2(obj) + assert "some_method" in res diff --git a/tests/test_display.py b/tests/test_display.py new file mode 100644 index 00000000000..8dc4b4dd05b --- /dev/null +++ b/tests/test_display.py @@ -0,0 +1,351 @@ +"""Tests for IPython.lib.display.""" + +# ----------------------------------------------------------------------------- +# Copyright (c) 2012, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- +from tempfile import NamedTemporaryFile, mkdtemp +from os.path import split, join as pjoin, dirname +import pathlib +from unittest import mock +import struct +import wave +from io import BytesIO + +# Third-party imports +import pytest + +try: + import numpy +except ImportError: + pass + +# Our own imports +from IPython.lib import display + +from IPython.testing.decorators import skipif_not_numpy + +# ----------------------------------------------------------------------------- +# Classes and functions +# ----------------------------------------------------------------------------- + +# -------------------------- +# FileLink tests +# -------------------------- + + +def test_instantiation_FileLink(): + """FileLink: Test class can be instantiated""" + fl = display.FileLink("example.txt") + fl = display.FileLink(pathlib.PurePath("example.txt")) + + +def test_warning_on_non_existent_path_FileLink(): + """FileLink: Calling _repr_html_ on non-existent files returns a warning""" + fl = display.FileLink("example.txt") + assert fl._repr_html_().startswith("Path (example.txt)") + + +def test_existing_path_FileLink(): + """FileLink: Calling _repr_html_ functions as expected on existing filepath""" + with NamedTemporaryFile() as tf: + fl = display.FileLink(tf.name) + actual = fl._repr_html_() + expected = "%s
" % (tf.name, tf.name) + assert actual == expected + + +def test_existing_path_FileLink_repr(): + """FileLink: Calling repr() functions as expected on existing filepath""" + with NamedTemporaryFile() as tf: + fl = display.FileLink(tf.name) + actual = repr(fl) + expected = tf.name + assert actual == expected + + +def test_error_on_directory_to_FileLink(): + """FileLink: Raises error when passed directory""" + td = mkdtemp() + pytest.raises(ValueError, display.FileLink, td) + + +# -------------------------- +# FileLinks tests +# -------------------------- + + +def test_instantiation_FileLinks(): + """FileLinks: Test class can be instantiated""" + fls = display.FileLinks("example") + + +@pytest.mark.parametrize("recursive", [True, False]) +@pytest.mark.parametrize("path_type", [pathlib.Path, pathlib.PurePath]) +def test_pathlike_FileLinks(tmp_path, recursive, path_type): + (tmp_path / "example.txt").touch() + expected = display.FileLinks(str(tmp_path), recursive=recursive) + actual = display.FileLinks(path_type(tmp_path), recursive=recursive) + + assert actual._repr_html_() == expected._repr_html_() + assert repr(actual) == repr(expected) + + +def test_warning_on_non_existent_path_FileLinks(): + """FileLinks: Calling _repr_html_ on non-existent files returns a warning""" + fls = display.FileLinks("example") + assert fls._repr_html_().startswith("Path (example)") + + +def test_existing_path_FileLinks(): + """FileLinks: Calling _repr_html_ functions as expected on existing dir""" + td = mkdtemp() + with NamedTemporaryFile(dir=td) as tf1, NamedTemporaryFile(dir=td) as tf2: + fl = display.FileLinks(td) + actual = fl._repr_html_() + actual = actual.split("\n") + actual.sort() + # the links should always have forward slashes, even on windows, so replace + # backslashes with forward slashes here + expected = [ + "%s/
" % td, + "  %s
" + % (tf2.name.replace("\\", "/"), split(tf2.name)[1]), + "  %s
" + % (tf1.name.replace("\\", "/"), split(tf1.name)[1]), + ] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_existing_path_FileLinks_alt_formatter(): + """FileLinks: Calling _repr_html_ functions as expected w/ an alt formatter""" + td = mkdtemp() + with NamedTemporaryFile(dir=td) as tf1, NamedTemporaryFile(dir=td) as tf2: + + def fake_formatter(dirname, fnames, included_suffixes): + return ["hello", "world"] + + fl = display.FileLinks(td, notebook_display_formatter=fake_formatter) + actual = fl._repr_html_() + actual = actual.split("\n") + actual.sort() + expected = ["hello", "world"] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_existing_path_FileLinks_repr(): + """FileLinks: Calling repr() functions as expected on existing directory""" + td = mkdtemp() + with NamedTemporaryFile(dir=td) as tf1, NamedTemporaryFile(dir=td) as tf2: + fl = display.FileLinks(td) + actual = repr(fl) + actual = actual.split("\n") + actual.sort() + expected = ["%s/" % td, " %s" % split(tf1.name)[1], " %s" % split(tf2.name)[1]] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_existing_path_FileLinks_repr_alt_formatter(): + """FileLinks: Calling repr() functions as expected w/ alt formatter""" + td = mkdtemp() + with NamedTemporaryFile(dir=td) as tf1, NamedTemporaryFile(dir=td) as tf2: + + def fake_formatter(dirname, fnames, included_suffixes): + return ["hello", "world"] + + fl = display.FileLinks(td, terminal_display_formatter=fake_formatter) + actual = repr(fl) + actual = actual.split("\n") + actual.sort() + expected = ["hello", "world"] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_error_on_file_to_FileLinks(): + """FileLinks: Raises error when passed file""" + td = mkdtemp() + with NamedTemporaryFile(dir=td) as tf1: + pytest.raises(ValueError, display.FileLinks, tf1.name) + + +def test_recursive_FileLinks(): + """FileLinks: Does not recurse when recursive=False""" + td = mkdtemp() + subtd = mkdtemp(dir=td) + with NamedTemporaryFile(dir=td) as tf, NamedTemporaryFile(dir=subtd) as subtf: + fl = display.FileLinks(td) + actual = str(fl) + actual = actual.split("\n") + assert len(actual) == 4, actual + fl = display.FileLinks(td, recursive=False) + actual = str(fl) + actual = actual.split("\n") + assert len(actual) == 2, actual + + +def test_escaped_names_FileLinks(): + """FileLinks: html metacharacters in file names are escaped""" + td = mkdtemp() + # links are emitted as href='...', so the apostrophe is what breaks out of + # the attribute; "<" and ">" are not usable as they are invalid on windows + name = "a' onmouseover='alert(1)&.txt" + with open(pjoin(td, name), "w"): + pass + actual = display.FileLinks(td)._repr_html_() + assert "a' onmouseover='alert(1)&.txt" in actual + assert name not in actual + + +def test_audio_from_file(): + path = pjoin(dirname(__file__), "test.wav") + display.Audio(filename=path) + + +def test_escaped_url_Audio(): + """Audio: quotes in url and element_id do not break out of the attribute""" + audio = display.Audio(url='http://example.com/a.wav" onerror="alert(1)') + assert ( + 'src="http://example.com/a.wav" onerror="alert(1)"' + in audio._repr_html_() + ) + + audio = display.Audio( + url="http://example.com/a.wav", element_id='x" onload="alert(1)' + ) + assert 'id="x" onload="alert(1)"' in audio._repr_html_() + + +@skipif_not_numpy +def test_audio_from_numpy_array(): + test_tone = get_test_tone() + audio = display.Audio(test_tone, rate=44100) + assert len(read_wav(audio.data)) == len(test_tone) + + +@skipif_not_numpy +def test_audio_from_list(): + test_tone = get_test_tone() + audio = display.Audio(list(test_tone), rate=44100) + assert len(read_wav(audio.data)) == len(test_tone) + + +@skipif_not_numpy +def test_audio_from_numpy_array_without_rate_raises(): + pytest.raises(ValueError, display.Audio, get_test_tone()) + + +@skipif_not_numpy +def test_audio_data_normalization(): + expected_max_value = numpy.iinfo(numpy.int16).max + for scale in [1, 0.5, 2]: + audio = display.Audio(get_test_tone(scale), rate=44100) + actual_max_value = numpy.max(numpy.abs(read_wav(audio.data))) + assert actual_max_value == expected_max_value + + +@skipif_not_numpy +def test_audio_data_without_normalization(): + max_int16 = numpy.iinfo(numpy.int16).max + for scale in [1, 0.5, 0.2]: + test_tone = get_test_tone(scale) + test_tone_max_abs = numpy.max(numpy.abs(test_tone)) + expected_max_value = int(max_int16 * test_tone_max_abs) + audio = display.Audio(test_tone, rate=44100, normalize=False) + actual_max_value = numpy.max(numpy.abs(read_wav(audio.data))) + assert actual_max_value == expected_max_value + + +def test_audio_data_without_normalization_raises_for_invalid_data(): + pytest.raises(ValueError, display.Audio, [1.001], rate=44100, normalize=False) + pytest.raises(ValueError, display.Audio, [-1.001], rate=44100, normalize=False) + + +@mock.patch("numpy.array", mock.MagicMock(side_effect=ImportError)) +@skipif_not_numpy +def test_audio_from_numpy_array_no_numpy(): + test_tone = get_test_tone() + audio = display.Audio(test_tone, rate=44100) + assert len(read_wav(audio.data)) == len(test_tone) + + +@mock.patch("numpy.array", mock.MagicMock(side_effect=ImportError)) +@skipif_not_numpy +def test_audio_from_list_no_numpy(): + test_tone = get_test_tone() + audio = display.Audio(list(test_tone), rate=44100) + assert len(read_wav(audio.data)) == len(test_tone) + + +@mock.patch("numpy.array", mock.MagicMock(side_effect=ImportError)) +@skipif_not_numpy +def test_audio_from_numpy_array_without_rate_raises_no_numpy(): + pytest.raises(ValueError, display.Audio, get_test_tone()) + + +@mock.patch("numpy.array", mock.MagicMock(side_effect=ImportError)) +@skipif_not_numpy +def test_audio_data_normalization_no_numpy(): + expected_max_value = numpy.iinfo(numpy.int16).max + for scale in [1, 0.5, 2]: + audio = display.Audio(get_test_tone(scale), rate=44100) + actual_max_value = numpy.max(numpy.abs(read_wav(audio.data))) + assert actual_max_value == expected_max_value + + +@mock.patch("numpy.array", mock.MagicMock(side_effect=ImportError)) +@skipif_not_numpy +def test_audio_data_without_normalization_no_numpy(): + max_int16 = numpy.iinfo(numpy.int16).max + for scale in [1, 0.5, 0.2]: + test_tone = get_test_tone(scale) + test_tone_max_abs = numpy.max(numpy.abs(test_tone)) + expected_max_value = int(max_int16 * test_tone_max_abs) + audio = display.Audio(test_tone, rate=44100, normalize=False) + actual_max_value = numpy.max(numpy.abs(read_wav(audio.data))) + assert actual_max_value == expected_max_value + + +@mock.patch("numpy.array", mock.MagicMock(side_effect=ImportError)) +@skipif_not_numpy +def test_audio_data_without_normalization_raises_for_invalid_data_no_numpy(): + pytest.raises(ValueError, display.Audio, [1.001], rate=44100, normalize=False) + pytest.raises(ValueError, display.Audio, [-1.001], rate=44100, normalize=False) + + +@mock.patch("numpy.array", mock.MagicMock(side_effect=ImportError)) +@skipif_not_numpy +def test_audio_raises_for_nested_list(): + stereo_signal = [list(get_test_tone())] * 2 + pytest.raises(TypeError, display.Audio, stereo_signal, rate=44100) + + +@skipif_not_numpy +def get_test_tone(scale=1): + return numpy.sin(2 * numpy.pi * 440 * numpy.linspace(0, 1, 44100)) * scale + + +def read_wav(data): + with wave.open(BytesIO(data)) as wave_file: + wave_data = wave_file.readframes(wave_file.getnframes()) + num_samples = wave_file.getnframes() * wave_file.getnchannels() + return struct.unpack("<%sh" % num_samples, wave_data) + + +def test_code_from_file(): + c = display.Code(filename=__file__) + assert c._repr_html_().startswith("" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Utilities" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define functions to render Markdown using the notebook and Pandoc." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from IPython.nbconvert.utils.pandoc import pandoc\n", - "from IPython.display import HTML, Javascript, display\n", - "\n", - "from IPython.nbconvert.filters import citation2latex, strip_files_prefix, \\\n", - " markdown2html, markdown2latex\n", - "\n", - "def pandoc_render(markdown):\n", - " \"\"\"Render Pandoc Markdown->LaTeX content.\"\"\"\n", - " \n", - " ## Convert the markdown directly to latex. This is what nbconvert does.\n", - " #latex = pandoc(markdown, \"markdown\", \"latex\")\n", - " #html = pandoc(markdown, \"markdown\", \"html\", [\"--mathjax\"])\n", - " \n", - " # nbconvert template conversions\n", - " html = strip_files_prefix(markdown2html(markdown))\n", - " latex = markdown2latex(citation2latex(markdown))\n", - " display(HTML(data=\"
\" \\\n", - " \"
NBConvert Latex Output
\" \\\n", - " \"
\" + latex + \"
\"\\\n", - " \"
\" \\\n", - " \"
\" \\\n", - " \"
\" \\\n", - " \"
NBViewer Output
\" \\\n", - " \"
\" + html + \"
\" \\\n", - " \"
\"))\n", - " javascript = \"\"\"\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n", - "\"\"\"\n", - " display(Javascript(data=javascript))\n", - "\n", - "def notebook_render(markdown):\n", - " javascript = \"\"\"\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\"\"\" + markdown.replace(\"\\\\\", \"\\\\\\\\\").replace(\"'\", \"\\'\").replace(\"\\n\", \"\\\\n\") + \"\"\"');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n", - "\"\"\"\n", - " display(Javascript(data=javascript))\n", - "\n", - " \n", - "def pandoc_html_render(markdown):\n", - " \"\"\"Render Pandoc Markdown->LaTeX content.\"\"\"\n", - " \n", - " # Convert the markdown directly to latex. This is what nbconvert does.\n", - " latex = pandoc(markdown, \"markdown\", \"latex\")\n", - " \n", - " # Convert the pandoc generated latex to HTML so it can be rendered in \n", - " # the web browser.\n", - " html = pandoc(latex, \"latex\", \"html\", [\"--mathjax\"])\n", - " display(HTML(data=\"
HTML Pandoc Output
\" \\\n", - " \"
\" + html + \"
\"))\n", - " return html\n", - " \n", - "def compare_render(markdown):\n", - " notebook_render(markdown)\n", - " pandoc_render(markdown)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Outputs" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "LXML found!\n" - ] - } - ], - "source": [ - "try:\n", - " import lxml\n", - " print 'LXML found!'\n", - "except:\n", - " print 'Warning! No LXML found - the old citation2latex filter will not work'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## General markdown" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Heading level 6 is not supported by Pandoc." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n\\n# Heading 1 \\n## Heading 2 \\n### Heading 3 \\n#### Heading 4 \\n##### Heading 5 \\n###### Heading 6');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\section{Heading 1}\n",
-       "\n",
-       "\\subsection{Heading 2}\n",
-       "\n",
-       "\\subsubsection{Heading 3}\n",
-       "\n",
-       "\\paragraph{Heading 4}\n",
-       "\n",
-       "\\subparagraph{Heading 5}\n",
-       "\n",
-       "Heading 6
NBViewer Output

Heading 1

\n", - "

Heading 2

\n", - "

Heading 3

\n", - "

Heading 4

\n", - "
Heading 5
\n", - "
Heading 6
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "\n", - "# Heading 1 \n", - "## Heading 2 \n", - "### Heading 3 \n", - "#### Heading 4 \n", - "##### Heading 5 \n", - "###### Heading 6\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Headers aren't recognized by (Pandoc on Windows?) if there isn't a blank line above the headers." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n# Heading 1 \\n## Heading 2 \\n### Heading 3 \\n#### Heading 4 \\n##### Heading 5 \\n###### Heading 6 ');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\section{Heading 1}\n",
-       "\n",
-       "\\subsection{Heading 2}\n",
-       "\n",
-       "\\subsubsection{Heading 3}\n",
-       "\n",
-       "\\paragraph{Heading 4}\n",
-       "\n",
-       "\\subparagraph{Heading 5}\n",
-       "\n",
-       "Heading 6
NBViewer Output

Heading 1

\n", - "

Heading 2

\n", - "

Heading 3

\n", - "

Heading 4

\n", - "
Heading 5
\n", - "
Heading 6
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ] - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "# Heading 1 \n", - "## Heading 2 \n", - "### Heading 3 \n", - "#### Heading 4 \n", - "##### Heading 5 \n", - "###### Heading 6 \"\"\")\n", - "\n", - "print(\"\\n\"*10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If internal links are defined, these will not work in nbviewer and latex as the local link is not existing." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n[Link2Heading](http://127.0.0.1:8888/0a2d8086-ee24-4e5b-a32b-f66b525836cb#General-markdown)\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\href{http://127.0.0.1:8888/0a2d8086-ee24-4e5b-a32b-f66b525836cb\\#General-markdown}{Link2Heading}
NBViewer Output
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "[Link2Heading](http://127.0.0.1:8888/0a2d8086-ee24-4e5b-a32b-f66b525836cb#General-markdown)\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Basic Markdown bold and italic works." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nThis is Markdown **bold** and *italic* text.\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
This is Markdown \\textbf{bold} and \\emph{italic} text.
NBViewer Output

This is Markdown bold and italic text.

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "This is Markdown **bold** and *italic* text.\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Nested lists work as well" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n- li 1\\n- li 2\\n 1. li 3\\n 1. li 4\\n- li 5\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\begin{itemize}\n",
-       "\\itemsep1pt\\parskip0pt\\parsep0pt\n",
-       "\\item\n",
-       "  li 1\n",
-       "\\item\n",
-       "  li 2\n",
-       "\n",
-       "  \\begin{enumerate}\n",
-       "  \\def\\labelenumi{\\arabic{enumi}.}\n",
-       "  \\itemsep1pt\\parskip0pt\\parsep0pt\n",
-       "  \\item\n",
-       "    li 3\n",
-       "  \\item\n",
-       "    li 4\n",
-       "  \\end{enumerate}\n",
-       "\\item\n",
-       "  li 5\n",
-       "\\end{itemize}
NBViewer Output
    \n", - "
  • li 1
  • \n", - "
  • li 2\n", - "
      \n", - "
    1. li 3
    2. \n", - "
    3. li 4
    4. \n", - "
  • \n", - "
  • li 5
  • \n", - "
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "- li 1\n", - "- li 2\n", - " 1. li 3\n", - " 1. li 4\n", - "- li 5\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Unicode support" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nüberschuß +***^°³³ α β θ\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
überschuß +\\emph{*}\\^{}°³³ α β θ
NBViewer Output

überschuß +*^°³³ α β θ

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(ur\"\"\"\n", - "überschuß +***^°³³ α β θ\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Pandoc may produce invalid latex, e.g \\sout is not allowed in headings" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n\\n# Heading 1 ~~strikeout~~\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\section{Heading 1 \\sout{strikeout}}
NBViewer Output

Heading 1 strikeout

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "\n", - "# Heading 1 ~~strikeout~~\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Horizontal lines work just fine" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nabove\\n\\n--------\\n\\nbelow\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
above\n",
-       "\n",
-       "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n",
-       "\n",
-       "below
NBViewer Output

above

\n", - "
\n", - "

below

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "above\n", - "\n", - "--------\n", - "\n", - "below\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extended markdown of pandoc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "(maybe we should deactivate this) " - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nThis is Markdown ~subscript~ and ^superscript^ text.\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
This is Markdown \\textsubscript{subscript} and\n",
-       "\\textsuperscript{superscript} text.
NBViewer Output

This is Markdown subscript and superscript text.

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "This is Markdown ~subscript~ and ^superscript^ text.\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "No space before underline behaves inconsistent (Pandoc extension: intraword_underscores - deactivate?)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nThis is Markdown not_italic_.\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
This is Markdown not\\_italic\\_.
NBViewer Output

This is Markdown not_italic_.

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "This is Markdown not_italic_.\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Pandoc allows to define tex macros which are respected for all output formats, the notebook not. " - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n\\\\newcommand{\\\\tuple}[1]{\\\\langle #1 \\\\rangle}\\n\\n$\\\\tuple{a, b, c}$\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\newcommand{\\tuple}[1]{\\langle #1 \\rangle}\n",
-       "\n",
-       "$\\tuple{a, b, c}$
NBViewer Output

\\(\\langle a, b, c \\rangle\\)

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "\\newcommand{\\tuple}[1]{\\langle #1 \\rangle}\n", - "\n", - "$\\tuple{a, b, c}$\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When placing the \\newcommand inside a math environment it works within the notebook and nbviewer, but produces invalid latex (the newcommand is only valid in the same math environment)." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n$\\\\newcommand{\\\\foo}[1]{...:: #1 ::...}$\\n$\\\\foo{bar}$\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
$\\newcommand{\\foo}[1]{...:: #1 ::...}$ $\\foo{bar}$
NBViewer Output

\\(\\newcommand{\\foo}[1]{...:: #1 ::...}\\) \\(\\foo{bar}\\)

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "$\\newcommand{\\foo}[1]{...:: #1 ::...}$\n", - "$\\foo{bar}$\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## HTML or LaTeX injections" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Raw HTML gets dropped entirely when converting to $\\LaTeX$." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nThis is HTML bold and italic text.\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
This is HTML bold and italic text.
NBViewer Output

This is HTML bold and italic text.

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "This is HTML bold and italic text.\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Same for something like center" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n
Center aligned
\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
Center aligned
NBViewer Output
\n", - "Center aligned\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "
Center aligned
\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Raw $\\LaTeX$ gets droppen entirely when converted to HTML. (I don't know why the HTML output is cropped here???)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nThis is \\\\LaTeX \\\\bf{bold} and \\\\emph{italic} text.\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
This is \\LaTeX \\bf{bold} and \\emph{italic} text.
NBViewer Output

This is

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "This is \\LaTeX \\bf{bold} and \\emph{italic} text.\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A combination of raw $\\LaTeX$ and raw HTML" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n**foo** $\\\\left( \\\\sum_{k=1}^n a_k b_k \\\\right)^2 \\\\leq$ b\\\\$ar $$test$$ \\n\\\\cite{}\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\textbf{foo} $\\left( \\sum_{k=1}^n a_k b_k \\right)^2 \\leq$ b\\$ar \\[test\\]\n",
-       "\\cite{}
NBViewer Output

foo \\(\\left( \\sum_{k=1}^n a_k b_k \\right)^2 \\leq\\) b$ar \\[test\\]

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "**foo** $\\left( \\sum_{k=1}^n a_k b_k \\right)^2 \\leq$ b\\$ar $$test$$ \n", - "\\cite{}\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Tables" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "HTML tables render in the notebook, but not in Pandoc." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n\\n \\n \\n \\n \\n \\n \\n \\n \\n
ab
cd
\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
a\n",
-       "\n",
-       "b\n",
-       "\n",
-       "c\n",
-       "\n",
-       "d
NBViewer Output
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - "a\n", - "\n", - "b\n", - "
\n", - "c\n", - "\n", - "d\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
ab
cd
\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Instead, Pandoc supports simple ascii tables. Unfortunately marked.js doesn't support this, and therefore it is not supported in the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n+---+---+\\n| a | b |\\n+---+---+\\n| c | d |\\n+---+---+\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\begin{longtable}[c]{@{}ll@{}}\n",
-       "\\hline\\noalign{\\medskip}\n",
-       "\\begin{minipage}[t]{0.06\\columnwidth}\\raggedright\n",
-       "a\n",
-       "\\end{minipage} & \\begin{minipage}[t]{0.06\\columnwidth}\\raggedright\n",
-       "b\n",
-       "\\end{minipage}\n",
-       "\\\\\\noalign{\\medskip}\n",
-       "\\begin{minipage}[t]{0.06\\columnwidth}\\raggedright\n",
-       "c\n",
-       "\\end{minipage} & \\begin{minipage}[t]{0.06\\columnwidth}\\raggedright\n",
-       "d\n",
-       "\\end{minipage}\n",
-       "\\\\\\noalign{\\medskip}\n",
-       "\\hline\n",
-       "\\end{longtable}
NBViewer Output
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "

a

b

c

d

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "+---+---+\n", - "| a | b |\n", - "+---+---+\n", - "| c | d |\n", - "+---+---+\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "An alternative to basic ascii tables is pipe tables. Pipe tables can be recognized by Pandoc and are supported by marked, hence, this is the **best way to add tables**." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n|Left |Center |Right|\\n|:----|:-----:|----:|\\n|Text1|Text2 |Text3|\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\begin{longtable}[c]{@{}lcr@{}}\n",
-       "\\hline\\noalign{\\medskip}\n",
-       "Left & Center & Right\n",
-       "\\\\\\noalign{\\medskip}\n",
-       "\\hline\\noalign{\\medskip}\n",
-       "Text1 & Text2 & Text3\n",
-       "\\\\\\noalign{\\medskip}\n",
-       "\\hline\n",
-       "\\end{longtable}
NBViewer Output
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
LeftCenterRight
Text1Text2Text3
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "|Left |Center |Right|\n", - "|:----|:-----:|----:|\n", - "|Text1|Text2 |Text3|\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Pandoc recognizes cell alignment in simple tables. Since marked.js doesn't recognize ascii tables, it can't render this table." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nRight Aligned Center Aligned Left Aligned\\n------------- -------------- ------------\\n Why does this\\n actually work? Who\\n knows ...\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\begin{longtable}[c]{@{}lll@{}}\n",
-       "\\hline\\noalign{\\medskip}\n",
-       "Right Aligned & Center Aligned & Left Aligned\n",
-       "\\\\\\noalign{\\medskip}\n",
-       "\\hline\\noalign{\\medskip}\n",
-       "Why & does & this\n",
-       "\\\\\\noalign{\\medskip}\n",
-       "actually & work? & Who\n",
-       "\\\\\\noalign{\\medskip}\n",
-       "knows & \\ldots{} &\n",
-       "\\\\\\noalign{\\medskip}\n",
-       "\\hline\n",
-       "\\end{longtable}
NBViewer Output
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
Right AlignedCenter AlignedLeft Aligned
Whydoesthis
actuallywork?Who
knows...
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ] - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "Right Aligned Center Aligned Left Aligned\n", - "------------- -------------- ------------\n", - " Why does this\n", - " actually work? Who\n", - " knows ...\n", - "\"\"\")\n", - "\n", - "print(\"\\n\"*5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Images" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Markdown images work on both. However, remote images are not allowed in $\\LaTeX$. Maybe add a preprocessor to download these.\n", - "The alternate text is displayed in nbviewer next to the image." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n![Alternate Text](https://ipython.org/_static/IPy_header.png)\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\begin{figure}[htbp]\n",
-       "\\centering\n",
-       "\\includegraphics{https://ipython.org/_static/IPy_header.png}\n",
-       "\\caption{Alternate Text}\n",
-       "\\end{figure}
NBViewer Output
\n", - "\"Alternate

Alternate Text

\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "![Alternate Text](https://ipython.org/_static/IPy_header.png)\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "HTML Images only work in the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
NBViewer Output

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Math" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Simple inline and displaystyle maths work fine" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nMy equation:\\n$$ 5/x=2y $$\\n\\nIt is inline $ 5/x=2y $ here.\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
My equation: \\[ 5/x=2y \\]\n",
-       "\n",
-       "It is inline \\$ 5/x=2y \\$ here.
NBViewer Output

My equation: \\[ 5/x=2y \\]

\n", - "

It is inline $ 5/x=2y $ here.

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "My equation:\n", - "$$ 5/x=2y $$\n", - "\n", - "It is inline $ 5/x=2y $ here.\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If the first \\$ is on a new line, the equation is not captured by md2tex, if both \\$s are on a new line md2html fails (Note the raw latex is dropped) but the notebook renders it correctly." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n$5 \\\\cdot x=2$\\n\\n$\\n5 \\\\cdot x=2$\\n\\n$\\n5 \\\\cdot x=2\\n$\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
$5 \\cdot x=2$\n",
-       "\n",
-       "\\$ 5 \\cdot x=2\\$\n",
-       "\n",
-       "\\$ 5 \\cdot x=2 \\$
NBViewer Output

\\(5 \\cdot x=2\\)

\n", - "

$ 5 x=2$

\n", - "

$ 5 x=2 $

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "$5 \\cdot x=2$\n", - "\n", - "$\n", - "5 \\cdot x=2$\n", - "\n", - "$\n", - "5 \\cdot x=2\n", - "$\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "MathJax permits some $\\LaTeX$ math constructs without \\$s, of course these raw $\\LaTeX$ is stripped when converting to html.\n", - "Moreove, the & are escaped by the lxml parsing [#4251](https://github.com/ipython/ipython/issues/4251)." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n\\\\begin{align}\\na & b\\\\\\\\\\nd & c\\n\\\\end{align}\\n\\n\\\\begin{eqnarray}\\na & b \\\\\\\\\\nc & d\\n\\\\end{eqnarray}\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
\\begin{align}\n",
-       "a &amp; b\\\\\n",
-       "d &amp; c\n",
-       "\\end{align}\n",
-       "\n",
-       "\\begin{eqnarray}\n",
-       "a &amp; b \\\\\n",
-       "c &amp; d\n",
-       "\\end{eqnarray}
NBViewer Output
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "\\begin{align}\n", - "a & b\\\\\n", - "d & c\n", - "\\end{align}\n", - "\n", - "\\begin{eqnarray}\n", - "a & b \\\\\n", - "c & d\n", - "\\end{eqnarray}\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is another lxml issue, [#4283](https://github.com/ipython/ipython/issues/4283)" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\n1<2 is true, but 3>4 is false.\\n\\n$1<2$ is true, but $3>4$ is false.\\n\\n1<2 it is even worse if it is alone in a line.\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
14 is false.\n",
-       "\n",
-       "$14$ is false.\n",
-       "\n",
-       "1
NBViewer Output

1<2 is true, but 3>4 is false.

\n", - "

\\(1<2\\) is true, but \\(3>4\\) is false.

\n", - "

1<2 it is even worse if it is alone in a line.

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "1<2 is true, but 3>4 is false.\n", - "\n", - "$1<2$ is true, but $3>4$ is false.\n", - "\n", - "1<2 it is even worse if it is alone in a line.\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Listings, and Code blocks" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nsome source code\\n\\n```\\na = \"test\"\\nprint(a)\\n```\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
some source code\n",
-       "\n",
-       "\\begin{verbatim}\n",
-       "a = \"test\"\n",
-       "print(a)\n",
-       "\\end{verbatim}
NBViewer Output

some source code

\n", - "
a = "test"\n",
-       "print(a)
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "some source code\n", - "\n", - "```\n", - "a = \"test\"\n", - "print(a)\n", - "```\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Language specific syntax highlighting by Pandoc requires additional dependencies to render correctly." - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "var mdcell = new IPython.MarkdownCell();\n", - "mdcell.create_element();\n", - "mdcell.set_text('\\nsome source code\\n\\n```python\\na = \"test\"\\nprint(a)\\n```\\n');\n", - "mdcell.render();\n", - "$(element).append(mdcell.element)\n", - ".removeClass()\n", - ".css('left', '66%')\n", - ".css('position', 'absolute')\n", - ".css('width', '30%')\n", - "mdcell.element.prepend(\n", - " $('
')\n", - " .removeClass()\n", - " .css('background', '#AAAAFF')\n", - " .css('width', '100 %')\n", - " .html('Notebook Output')\n", - "\n", - ");\n", - "container.show()\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
NBConvert Latex Output
some source code\n",
-       "\n",
-       "\\begin{Shaded}\n",
-       "\\begin{Highlighting}[]\n",
-       "\\NormalTok{a = }\\StringTok{\"test\"}\n",
-       "\\KeywordTok{print}\\NormalTok{(a)}\n",
-       "\\end{Highlighting}\n",
-       "\\end{Shaded}
NBViewer Output

some source code

\n", - "
a = "test"\n",
-       "print(a)
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - " $.getScript(\"https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js\");\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "compare_render(r\"\"\"\n", - "some source code\n", - "\n", - "```python\n", - "a = \"test\"\n", - "print(a)\n", - "```\n", - "\"\"\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/tools/tests/Test Output Callbacks.ipynb b/tools/tests/Test Output Callbacks.ipynb deleted file mode 100644 index 94ec19073df..00000000000 --- a/tools/tests/Test Output Callbacks.ipynb +++ /dev/null @@ -1,291 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Basic Output" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from IPython.display import display" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "print('hi')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "display('hi')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "plt.plot([1,3,2])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%javascript\n", - "console.log(\"I ran!\");" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%html\n", - "bold" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%latex\n", - "$$\n", - "a = 5\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# input_request" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "raw_input(\"prompt > \")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# set_next_input" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%writefile tst.py\n", - "def foo():\n", - " pass\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%load tst.py" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Pager in execute_reply" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "plt?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# object_info" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# press shift-tab after parentheses\n", - "int(" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# complete" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# press tab after f\n", - "f" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# clear_output" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import sys, time\n", - "from IPython.display import clear_output" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "for i in range(10):\n", - " clear_output()\n", - " time.sleep(0.25)\n", - " print(i)\n", - " sys.stdout.flush()\n", - " time.sleep(0.25)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "for i in range(10):\n", - " clear_output(wait=True)\n", - " time.sleep(0.25)\n", - " print(i)\n", - " sys.stdout.flush()\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/tools/tests/heartbeat/gilsleep.ipynb b/tools/tests/heartbeat/gilsleep.ipynb deleted file mode 100644 index 026682f026e..00000000000 --- a/tools/tests/heartbeat/gilsleep.ipynb +++ /dev/null @@ -1,72 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Holding the GIL for too long could disrupt the heartbeat due to non-copying sends.\n", - "\n", - "The following cell repeatedly calls a function that holds the GIL for five seconds.\n", - "\n", - "The heartbeat will fail after a few iterations prior to fixing Issue [#1260](https://github.com/ipython/ipython/issues/1260)." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import sys\n", - "import time\n", - "\n", - "from cython import inline\n", - "\n", - "def gilsleep(t):\n", - " \"\"\"gil-holding sleep with cython.inline\"\"\"\n", - " code = '\\n'.join([\n", - " 'from posix cimport unistd',\n", - " 'unistd.sleep(t)',\n", - " ])\n", - " while True:\n", - " inline(code, quiet=True, t=t)\n", - " print time.time()\n", - " sys.stdout.flush() # this is important\n", - "\n", - "gilsleep(5)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/tools/tests/mpl-switch/Matplotlib Switching.ipynb b/tools/tests/mpl-switch/Matplotlib Switching.ipynb deleted file mode 100644 index 252fe675634..00000000000 --- a/tools/tests/mpl-switch/Matplotlib Switching.ipynb +++ /dev/null @@ -1,654 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using matplotlib backend: MacOSX\n" - ] - }, - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Should pop up a GUI window\n", - "%matplotlib\n", - "import matplotlib.pyplot as plt\n", - "plt.plot([1,2,3])" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": [ - "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\n", - "RdKZhBBKQggPkNq8/VfAvBjjljM8J2d1Sz5t7L4ceBJ4JITwKFAHzASuBPYCn+90/TTgAmDyaWJ9\n", - "DlgA3BtC+BDwWvr6paR2y1+ejTcgaUDK9NyyIoSwHng5fWwOqfnmCHBtjLEp4+9A0oATQqgErkk/\n", - "PC89zgohfCH9/eYYY8dfDq1bJJ1RFuYW6xZJy4CPA8eATcA/hhBOd90TMcZfpL/PWd2SFyuxAGKM\n", - "NcBCUsvbqkm98VnAt4HLYoxvdHpKMv11ulivAvOAVcBF6VgLSX0Kx8IYo0vypSEik3ML8Kl0nPHA\n", - "XwMfBSYC9wEXxxjXZDp/SQPWDOBr6a/lpOaNSzocW9bpeusWSd2RsbkF6xZJKe2fGDgCWEHqj/id\n", - "vz5HahVVO+sWSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk\n", - "SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk\n", - "SZIkSZIkSZIkSZIkSZIkSZIkSZLy0v8DnhwIE1EJTfYAAAAASUVORK5CYII=\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 379, - "width": 600 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "# Should make an inline figure\n", - "%matplotlib inline\n", - "plt.plot([1,2,3])" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# New GUI window--should *NOT* have the visual settings of inline\n", - "%matplotlib qt\n", - "plt.plot([1,2,3])" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/tools/testupload b/tools/testupload deleted file mode 100755 index 77adbe79055..00000000000 --- a/tools/testupload +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -"""Simple upload script to push up into the testing directory a local build -""" -from __future__ import print_function - -from toollib import * - -# Get main ipython dir, this will raise if it doesn't pass some checks -ipdir = get_ipdir() -distdir = pjoin(ipdir, 'dist') - -# Load release info -execfile(pjoin(ipdir, 'IPython','core','release.py')) - -# Build site addresses for file uploads -testing_site = '%s/testing/%s' % (archive, version) - -cd(distdir) - -# Create remote path if it doesn't exist. -sh('ssh %s "mkdir -p %s/testing/%s" ' % (archive_user, archive_dir, version)) -sh('scp * %s' % testing_site) diff --git a/tools/toollib.py b/tools/toollib.py index 510b54e99d4..73bf16f94ae 100644 --- a/tools/toollib.py +++ b/tools/toollib.py @@ -5,23 +5,15 @@ import os import sys +from pathlib import Path + # Useful shorthands -pjoin = os.path.join cd = os.chdir -# Constants - -# SSH root address of the archive site -archive_user = 'ipython@archive.ipython.org' -archive_dir = 'archive.ipython.org' -archive = '%s:%s' % (archive_user, archive_dir) - # Build commands # Source dists -sdists = './setup.py sdist --formats=gztar' -# Binary dists -def buildwheels(): - sh('{python} setupegg.py bdist_wheel'.format(python=sys.executable)) +build_command = "{python} -m build".format(python=sys.executable) + # Utility functions def sh(cmd): @@ -36,15 +28,10 @@ def get_ipdir(): """Get IPython directory from command line, or assume it's the one above.""" # Initialize arguments and check location - ipdir = pjoin(os.path.dirname(__file__), os.pardir) - - ipdir = os.path.abspath(ipdir) + ipdir = Path(__file__).parent / os.pardir + ipdir = ipdir.resolve() cd(ipdir) - if not os.path.isdir('IPython') and os.path.isfile('setup.py'): - raise SystemExit('Invalid ipython directory: %s' % ipdir) + if not Path("IPython").is_dir() and Path("setup.py").is_file(): + raise SystemExit("Invalid ipython directory: %s" % ipdir) return ipdir - -def execfile(fname, globs, locs=None): - locs = locs or globs - exec(compile(open(fname).read(), fname, "exec"), globs, locs) diff --git a/tools/update_whatsnew.py b/tools/update_whatsnew.py old mode 100755 new mode 100644 index f3156c52a15..f5870758bc3 --- a/tools/update_whatsnew.py +++ b/tools/update_whatsnew.py @@ -7,26 +7,24 @@ import io import sys -from glob import glob -from os.path import dirname, basename, abspath, join as pjoin +from pathlib import Path from subprocess import check_call, check_output -repo_root = dirname(dirname(abspath(__file__))) -whatsnew_dir = pjoin(repo_root, 'docs', 'source', 'whatsnew') -pr_dir = pjoin(whatsnew_dir, 'pr') -target = pjoin(whatsnew_dir, 'development.rst') +repo_root = Path(__file__).resolve().parent.parent +whatsnew_dir = repo_root / "docs" / "source" / "whatsnew" +pr_dir = whatsnew_dir / "pr" +target = whatsnew_dir / "development.rst" FEATURE_MARK = ".. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT." INCOMPAT_MARK = ".. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT." # 1. Collect the whatsnew snippet files --------------------------------------- -files = set(glob(pjoin(pr_dir, '*.rst'))) +files = set(pr_dir.glob("*.rst")) # Ignore explanatory and example files -files.difference_update({pjoin(pr_dir, f) for f in { - 'incompat-switching-to-perl.rst', - 'antigravity-feature.rst'} - }) +files.difference_update( + {pr_dir / f for f in {"incompat-switching-to-perl.rst", "antigravity-feature.rst"}} +) if not files: print("No automatic update available for what's new") @@ -34,30 +32,31 @@ def getmtime(f): - return check_output(['git', 'log', '-1', '--format="%ai"', '--', f]) + return check_output(["git", "log", "-1", '--format="%ai"', "--", f]) + files = sorted(files, key=getmtime) features, incompats = [], [] for path in files: - with io.open(path, encoding='utf-8') as f: + with io.open(path, encoding="utf-8") as f: try: content = f.read().rstrip() except Exception as e: raise Exception('Error reading "{}"'.format(f)) from e - if basename(path).startswith('incompat-'): + if path.name.startswith("incompat-"): incompats.append(content) else: features.append(content) # Put the insertion markers back on the end, so they're ready for next time. -feature_block = '\n\n'.join(features + [FEATURE_MARK]) -incompat_block = '\n\n'.join(incompats + [INCOMPAT_MARK]) +feature_block = "\n\n".join(features + [FEATURE_MARK]) +incompat_block = "\n\n".join(incompats + [INCOMPAT_MARK]) # 2. Update the target file --------------------------------------------------- -with io.open(target, encoding='utf-8') as f: +with io.open(target, encoding="utf-8") as f: content = f.read() assert content.count(FEATURE_MARK) == 1 @@ -67,16 +66,16 @@ def getmtime(f): content = content.replace(INCOMPAT_MARK, incompat_block) # Clean trailing whitespace -content = '\n'.join(l.rstrip() for l in content.splitlines()) +content = "\n".join(l.rstrip() for l in content.splitlines()) -with io.open(target, 'w', encoding='utf-8') as f: +with io.open(target, "w", encoding="utf-8") as f: f.write(content) # 3. Stage the changes in git ------------------------------------------------- for file in files: - check_call(['git', 'rm', file]) + check_call(["git", "rm", file]) -check_call(['git', 'add', target]) +check_call(["git", "add", target]) print("Merged what's new changes. Check the diff and commit the change.")