├── tests ├── __init__.py ├── test_import.py ├── test_opts.py └── test_config.py ├── pyproject.toml ├── MANIFEST.in ├── setup.cfg ├── .gitignore ├── AUTHORS ├── ipdb ├── __init__.py ├── stdout.py └── __main__.py ├── manual_test.py ├── COPYING.txt ├── .github └── workflows │ └── tests.yml ├── setup.py ├── README.rst └── HISTORY.txt /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | 4 | 5 | [tool.black] 6 | line-length = 88 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include manual_test.py 3 | include *.txt 4 | include AUTHORS 5 | recursive-include tests *.py 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [check-manifest] 2 | ignore = 3 | tox.ini 4 | .travis.yml 5 | 6 | [zest.releaser] 7 | create-wheel = yes 8 | python-file-with-version = ipdb/__main__.py 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | lib 3 | include 4 | 5 | # Python 6 | *.pyc 7 | /__pycache__/ 8 | 9 | # setuptools / distribute 10 | /*.egg-info/ 11 | /*.egg/ 12 | /build/ 13 | /dist/ 14 | 15 | # tox 16 | .tox 17 | 18 | # pip 19 | pip-selfcheck.json 20 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | aldrik 2 | andreagrandi 3 | bmw 4 | dimasad 5 | emulbreh 6 | felixonmars 7 | gnebehay 8 | gotcha 9 | IxDay 10 | JamshedVesuna 11 | kynan 12 | lebedov 13 | marciomazza 14 | mauritsvanrees 15 | msabramo 16 | nikolas 17 | omergertel 18 | pgularski 19 | pjdelport 20 | Psycojoker 21 | sas23 22 | steinnes 23 | Wilfred 24 | WouterVH 25 | zvodd 26 | d1618033 27 | -------------------------------------------------------------------------------- /ipdb/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2007-2016 Godefroid Chapelle and ipdb development team 2 | # 3 | # This file is part of ipdb. 4 | # Redistributable under the revised BSD license 5 | # https://opensource.org/licenses/BSD-3-Clause 6 | 7 | from ipdb.__main__ import set_trace, post_mortem, pm, run, iex # noqa 8 | from ipdb.__main__ import runcall, runeval, launch_ipdb_on_exception # noqa 9 | 10 | from ipdb.stdout import sset_trace, spost_mortem, spm # noqa 11 | from ipdb.stdout import slaunch_ipdb_on_exception # noqa 12 | -------------------------------------------------------------------------------- /manual_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2016 Godefroid Chapelle and ipdb development team 2 | # 3 | # This file is part of ipdb. 4 | # Redistributable under the revised BSD license 5 | # https://opensource.org/licenses/BSD-3-Clause 6 | 7 | 8 | def output(arg): 9 | print("MANUAL: arg=%s" % arg) 10 | 11 | 12 | def main(): 13 | for abc in range(10): 14 | import ipdb; ipdb.set_trace() 15 | output(abc) 16 | 17 | 18 | # code to test with nose 19 | import unittest 20 | 21 | 22 | class IpdbUsageTests(unittest.TestCase): 23 | 24 | def testMain(self): 25 | main() 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /tests/test_import.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2016 Marc Abramowitz and ipdb development team 2 | # 3 | # This file is part of ipdb. 4 | # Redistributable under the revised BSD license 5 | # https://opensource.org/licenses/BSD-3-Clause 6 | 7 | import unittest 8 | 9 | 10 | class ImportTest(unittest.TestCase): 11 | def test_import(self): 12 | from ipdb import set_trace, post_mortem, pm, iex, run, runcall, runeval 13 | 14 | set_trace # please pyflakes 15 | post_mortem # please pyflakes 16 | pm # please pyflakes 17 | iex # please pyflakes 18 | run # please pyflakes 19 | runcall # please pyflakes 20 | runeval # please pyflakes 21 | -------------------------------------------------------------------------------- /ipdb/stdout.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | from contextlib import contextmanager 4 | from IPython.utils import io 5 | from .__main__ import set_trace 6 | from .__main__ import post_mortem 7 | 8 | 9 | def update_stdout(): 10 | # setup stdout to ensure output is available with nose 11 | io.stdout = sys.stdout = sys.__stdout__ 12 | 13 | 14 | def sset_trace(frame=None, context=3): 15 | update_stdout() 16 | if frame is None: 17 | frame = sys._getframe().f_back 18 | set_trace(frame, context) 19 | 20 | 21 | def spost_mortem(tb=None): 22 | update_stdout() 23 | post_mortem(tb) 24 | 25 | 26 | def spm(): 27 | spost_mortem(sys.last_traceback) 28 | 29 | 30 | @contextmanager 31 | def slaunch_ipdb_on_exception(): 32 | try: 33 | yield 34 | except Exception: 35 | e, m, tb = sys.exc_info() 36 | print(m.__repr__(), file=sys.stderr) 37 | spost_mortem(tb) 38 | finally: 39 | pass 40 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2019 ipdb development team 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | Neither the name of the ipdb Development Team nor the names of its 17 | contributors may be used to endorse or promote products derived from this 18 | software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | # Allow to run this workflow manually from the Actions tab 7 | workflow_dispatch: 8 | 9 | jobs: 10 | python-runners: 11 | name: on ${{ matrix.os }} machine - Python ${{ matrix.python-version }} 12 | runs-on: ${{ matrix.os }} 13 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] 19 | exclude: 20 | # excludes 2.7 on windows as chocolatey does not support it anymore 21 | - os: windows-latest 22 | python-version: 2.7 23 | - os: ubuntu-latest 24 | python-version: 3.5 25 | - os: ubuntu-latest 26 | python-version: 3.6 27 | include: 28 | - os: ubuntu-20.04 29 | python-version: 3.5 30 | - os: ubuntu-20.04 31 | python-version: 3.6 32 | 33 | steps: 34 | - uses: actions/checkout@v3 35 | - name: Set up Python ${{ matrix.python-version }} 36 | uses: actions/setup-python@v4 37 | with: 38 | python-version: ${{ matrix.python-version }} 39 | 40 | - name: before install 41 | run: | 42 | pip install --upgrade pip setuptools 43 | pip install codecov 44 | - name: install 45 | run: | 46 | # Install ipdb, which will install the right IPython version for the current python. 47 | pip install -e . 48 | - name: run tests 49 | run: | 50 | coverage run setup.py test 51 | codecov 52 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2007-2016 Godefroid Chapelle and ipdb development team 2 | # 3 | # This file is part of ipdb. 4 | # Redistributable under the revised BSD license 5 | # https://opensource.org/licenses/BSD-3-Clause 6 | 7 | import io 8 | import re 9 | from sys import version_info 10 | 11 | from setuptools import find_packages, setup 12 | 13 | version = re.search( 14 | r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', # It excludes inline comment too 15 | io.open('ipdb/__main__.py', encoding='utf_8_sig').read() 16 | ).group(1) 17 | 18 | long_description = (open('README.rst').read() + 19 | '\n\n' + open('HISTORY.txt').read()) 20 | 21 | 22 | if version_info[0] == 2: 23 | console_script = 'ipdb' 24 | else: 25 | console_script = 'ipdb%d' % version_info.major 26 | 27 | 28 | setup(name='ipdb', 29 | version=version, 30 | description="IPython-enabled pdb", 31 | long_description=long_description, 32 | classifiers=[ 33 | 'Programming Language :: Python :: 2.7', 34 | 'Programming Language :: Python :: 3', 35 | 'Programming Language :: Python :: 3.4', 36 | 'Programming Language :: Python :: 3.5', 37 | 'Programming Language :: Python :: 3.6', 38 | 'Programming Language :: Python :: 3.7', 39 | 'Programming Language :: Python :: 3.8', 40 | 'Programming Language :: Python :: 3.9', 41 | 'Programming Language :: Python :: 3.10', 42 | 'Programming Language :: Python :: 3.11', 43 | 'Operating System :: MacOS :: MacOS X', 44 | 'Operating System :: POSIX :: Linux', 45 | 'Operating System :: Microsoft :: Windows', 46 | 'Topic :: Software Development :: Debuggers', 47 | 'License :: OSI Approved :: BSD License', 48 | ], 49 | keywords='pdb ipython', 50 | author='Godefroid Chapelle', 51 | author_email='gotcha@bubblenet.be', 52 | url='https://github.com/gotcha/ipdb', 53 | license='BSD', 54 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 55 | include_package_data=True, 56 | zip_safe=True, 57 | test_suite='tests', 58 | python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', 59 | extras_require={ 60 | ':python_version == "2.7"': ['ipython >= 5.1.0, < 6.0.0', 'toml >= 0.10.2', 'decorator < 5.0.0', 'pathlib'], 61 | # No support for python 3.0, 3.1, 3.2., 3.3 62 | # FTR, `decorator` is also a dependency of Ipython. 63 | ':python_version == "3.4"': ['ipython >= 6.0.0, < 7.0.0', 'toml >= 0.10.2', 'decorator < 5.0.0'], 64 | ':python_version == "3.5"': ['ipython >= 7.0.0, < 7.10.0', 'toml >= 0.10.2', 'decorator'], 65 | ':python_version == "3.6"': ['ipython >= 7.16.3, < 7.17.0', 'tomli', 'decorator'], 66 | ':python_version > "3.6" and python_version < "3.11"': ['ipython >= 7.31.1', 'tomli', 'decorator'], 67 | ':python_version >= "3.11"': ['ipython >= 7.31.1', 'decorator'], 68 | }, 69 | tests_require=[ 70 | 'mock; python_version<"3"' 71 | ], 72 | entry_points={ 73 | 'console_scripts': ['%s = ipdb.__main__:main' % console_script] 74 | } 75 | ) 76 | -------------------------------------------------------------------------------- /tests/test_opts.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2016 Marc Abramowitz and ipdb development team 2 | # 3 | # This file is part of ipdb. 4 | # Redistributable under the revised BSD license 5 | # https://opensource.org/licenses/BSD-3-Clause 6 | 7 | import sys 8 | import unittest 9 | import os 10 | 11 | try: 12 | from unittest.mock import patch 13 | except ImportError: 14 | from mock import patch 15 | 16 | from getopt import GetoptError 17 | from ipdb.__main__ import main 18 | 19 | 20 | @patch("ipdb.__main__._get_debugger_cls") 21 | class OptsTest(unittest.TestCase): 22 | def set_argv(self, *argv): 23 | argv_patch = patch("ipdb.__main__.sys.argv", argv) 24 | argv_patch.start() 25 | self.addCleanup(argv_patch.stop) 26 | 27 | @unittest.skipIf( 28 | sys.version_info[0] == 3 and sys.version_info[1] >= 11, 29 | ">3.11 requires different test", 30 | ) 31 | @patch("ipdb.__main__.sys.version_info", (3, 7)) 32 | def test_debug_module_script(self, get_debugger_cls): 33 | module_name = "my_buggy_module" 34 | self.set_argv("ipdb", "-m", module_name) 35 | 36 | main() 37 | 38 | debugger = get_debugger_cls.return_value.return_value 39 | debugger._runmodule.assert_called_once_with(module_name) 40 | 41 | @unittest.skipIf( 42 | sys.version_info[0] == 3 and sys.version_info[1] >= 11, 43 | ">3.11 requires different test", 44 | ) 45 | @patch("ipdb.__main__.os.path.exists") 46 | def test_debug_script(self, exists, get_debugger_cls): 47 | script_name = "my_buggy_script" 48 | self.set_argv("ipdb", script_name) 49 | 50 | main() 51 | 52 | debugger = get_debugger_cls.return_value.return_value 53 | debugger._runscript.assert_called_once_with(script_name) 54 | 55 | @unittest.skipIf( 56 | sys.version_info[0] != 3 or sys.version_info[1] < 11, 57 | "<3.11 requires different test", 58 | ) 59 | def test_debug_module_script_3_11(self, get_debugger_cls): 60 | module_name = "my_buggy_module_3_11" 61 | self.set_argv("ipdb", "-m", module_name) 62 | 63 | main() 64 | 65 | debugger = get_debugger_cls.return_value.return_value 66 | debugger._run.assert_called_once_with(module_name) 67 | 68 | @unittest.skipIf( 69 | sys.version_info[0] != 3 or sys.version_info[1] < 11, 70 | "<3.11 requires different test", 71 | ) 72 | @patch("ipdb.__main__.os.path.exists") 73 | def test_debug_script_3_11(self, exists, get_debugger_cls): 74 | script_name = "my_buggy_script_3_11" 75 | self.set_argv("ipdb", script_name) 76 | 77 | main() 78 | 79 | debugger = get_debugger_cls.return_value.return_value 80 | debugger._run.assert_called_once_with(os.path.join(os.getcwd(), script_name)) 81 | 82 | def test_option_m_fallback_on_py36(self, get_debugger_cls): 83 | self.set_argv("ipdb", "-m", "my.module") 84 | with patch("ipdb.__main__.sys.version_info", (3, 6)): 85 | with self.assertRaises(GetoptError): 86 | main() 87 | 88 | with patch("ipdb.__main__.sys.version_info", (3, 7)): 89 | self.set_argv("ipdb", "-m", "my.module") 90 | try: 91 | main() 92 | except GetoptError: 93 | self.fail("GetoptError raised unexpectedly.") 94 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | IPython `pdb` 2 | ============= 3 | 4 | .. image:: https://github.com/gotcha/ipdb/actions/workflows/tests.yml/badge.svg 5 | :target: https://github.com/gotcha/ipdb/actions/workflows/tests.yml 6 | .. image:: https://codecov.io/gh/gotcha/ipdb/branch/master/graphs/badge.svg?style=flat 7 | :target: https://codecov.io/gh/gotcha/ipdb?branch=master 8 | 9 | Use 10 | --- 11 | 12 | ipdb exports functions to access the IPython_ debugger, which features 13 | tab completion, syntax highlighting, better tracebacks, better introspection 14 | with the same interface as the `pdb` module. 15 | 16 | Example usage: 17 | 18 | .. code-block:: python 19 | 20 | import ipdb 21 | ipdb.set_trace() 22 | ipdb.set_trace(context=5) # will show five lines of code 23 | # instead of the default three lines 24 | # or you can set it via IPDB_CONTEXT_SIZE env variable 25 | # or setup.cfg file 26 | ipdb.pm() 27 | ipdb.run('x[0] = 3') 28 | result = ipdb.runcall(function, arg0, arg1, kwarg='foo') 29 | result = ipdb.runeval('f(1,2) - 3') 30 | 31 | 32 | Arguments for `set_trace` 33 | +++++++++++++++++++++++++ 34 | 35 | The `set_trace` function accepts `context` which will show as many lines of code as defined, 36 | and `cond`, which accepts boolean values (such as `abc == 17`) and will start ipdb's 37 | interface whenever `cond` equals to `True`. 38 | 39 | Using configuration file 40 | ++++++++++++++++++++++++ 41 | 42 | It's possible to set up context using a `.ipdb` file on your home folder, `setup.cfg` 43 | or `pyproject.toml` on your project folder. You can also set your file location via 44 | env var `$IPDB_CONFIG`. Your environment variable has priority over the home 45 | configuration file, which in turn has priority over the setup config file. 46 | Currently, only context setting is available. 47 | 48 | A valid setup.cfg is as follows 49 | 50 | :: 51 | 52 | [ipdb] 53 | context=5 54 | 55 | 56 | A valid .ipdb is as follows 57 | 58 | :: 59 | 60 | context=5 61 | 62 | 63 | A valid pyproject.toml is as follows 64 | 65 | :: 66 | 67 | [tool.ipdb] 68 | context=5 69 | 70 | 71 | The post-mortem function, ``ipdb.pm()``, is equivalent to the magic function 72 | ``%debug``. 73 | 74 | .. _IPython: http://ipython.org 75 | 76 | If you install ``ipdb`` with a tool which supports ``setuptools`` entry points, 77 | an ``ipdb`` script is made for you. You can use it to debug your python 2 scripts like 78 | 79 | :: 80 | 81 | $ bin/ipdb mymodule.py 82 | 83 | And for python 3 84 | 85 | :: 86 | 87 | $ bin/ipdb3 mymodule.py 88 | 89 | Alternatively with Python 2.7 only, you can also use 90 | 91 | :: 92 | 93 | $ python -m ipdb mymodule.py 94 | 95 | You can also enclose code with the ``with`` statement to launch ipdb if an exception is raised: 96 | 97 | .. code-block:: python 98 | 99 | from ipdb import launch_ipdb_on_exception 100 | 101 | with launch_ipdb_on_exception(): 102 | [...] 103 | 104 | .. warning:: 105 | Context managers were introduced in Python 2.5. 106 | Adding a context manager implies dropping Python 2.4 support. 107 | Use ``ipdb==0.6`` with 2.4. 108 | 109 | Or you can use ``iex`` as a function decorator to launch ipdb if an exception is raised: 110 | 111 | .. code-block:: python 112 | 113 | from ipdb import iex 114 | 115 | @iex 116 | def main(): 117 | [...] 118 | 119 | .. warning:: 120 | Using ``from future import print_function`` for Python 3 compat implies dropping Python 2.5 support. 121 | Use ``ipdb<=0.8`` with 2.5. 122 | 123 | Issues with ``stdout`` 124 | ---------------------- 125 | 126 | Some tools, like ``nose`` fiddle with ``stdout``. 127 | 128 | Until ``ipdb==0.9.4``, we tried to guess when we should also 129 | fiddle with ``stdout`` to support those tools. 130 | However, all strategies tried until 0.9.4 have proven brittle. 131 | 132 | If you use ``nose`` or another tool that fiddles with ``stdout``, you should 133 | explicitly ask for ``stdout`` fiddling by using ``ipdb`` like this 134 | 135 | .. code-block:: python 136 | 137 | import ipdb 138 | ipdb.sset_trace() 139 | ipdb.spm() 140 | 141 | from ipdb import slaunch_ipdb_on_exception 142 | with slaunch_ipdb_on_exception(): 143 | [...] 144 | 145 | 146 | Development 147 | ----------- 148 | 149 | ``ipdb`` source code and tracker are at https://github.com/gotcha/ipdb. 150 | 151 | Pull requests should take care of updating the changelog ``HISTORY.txt``. 152 | 153 | Under the unreleased section, add your changes and your username. 154 | 155 | Manual testing 156 | ++++++++++++++ 157 | 158 | To test your changes, make use of ``manual_test.py``. Create a virtual environment, 159 | install IPython and run ``python manual_test.py`` and check if your changes are in effect. 160 | If possible, create automated tests for better behaviour control. 161 | 162 | Automated testing 163 | +++++++++++++++++ 164 | 165 | To run automated tests locally, create a virtual environment, install `coverage` 166 | and run `coverage run setup.py test`. 167 | 168 | Third-party support 169 | ------------------- 170 | 171 | pytest 172 | +++++++ 173 | pytest_ supports a ``--pdb`` option which can run ``ipdb`` / 174 | ``IPython.terminal.debugger:Pdb`` on ``Exception`` and ``breakpoint()``: 175 | 176 | .. code:: bash 177 | 178 | pytest --pdb --pdbcls=IPython.terminal.debugger:Pdb -v ./test_example.py 179 | 180 | You don't need to specify ``--pdbcls`` for every ``pytest`` invocation 181 | if you add ``addopts`` to ``pytest.ini`` or ``pyproject.toml``. 182 | 183 | ``pytest.ini``: 184 | 185 | .. code:: bash 186 | 187 | [tool.pytest.ini_options] 188 | addopts = "--pdbcls=IPython.terminal.debugger:Pdb" 189 | 190 | ``pyproject.toml``: 191 | 192 | .. code:: yml 193 | 194 | [tool.pytest.ini_options] 195 | addopts = "--pdbcls=IPython.terminal.debugger:Pdb" 196 | 197 | 198 | .. _pytest: https://pypi.python.org/pypi/pytest 199 | -------------------------------------------------------------------------------- /HISTORY.txt: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.13.14 (unreleased) 5 | -------------------- 6 | 7 | - Run ``black`` on ipdb-codebase with line-length 88. [WouterVH] 8 | 9 | 10 | 0.13.13 (2023-03-09) 11 | -------------------- 12 | 13 | - Use context manager for opening toml config 14 | [andrewzwicky] 15 | 16 | 17 | 0.13.12 (2023-03-09) 18 | -------------------- 19 | 20 | - Modify tests to test 3.11 separately from earlier python versions. 21 | [andrewzwicky] 22 | 23 | 24 | 0.13.11 (2022-12-13) 25 | -------------------- 26 | 27 | - Better exception handling when looking for config. 28 | [bignose-debian] 29 | 30 | 31 | 0.13.10 (2022-12-13) 32 | -------------------- 33 | 34 | - Better toml support (use tomlib for 3.11, tomli for 3.6 to 3.10). 35 | [salty-horse, mgorny] 36 | 37 | - Minimal PEP 517 support. 38 | [cpcloud] 39 | 40 | - 3.11 support for run as module and run as script. 41 | [nphilipp, gotcha] 42 | 43 | - Based on OSV:PYSEC-2022-12 change ipython dependencies, 44 | for users using python 3.6, install 7.16.3 <= IPython < 7.17.0, 45 | for users using python>3.6, install IPython >= 7.31.1. 46 | [malkstar] 47 | 48 | 49 | 0.13.9 (2021-06-02) 50 | ------------------- 51 | 52 | - Fix again when `pyproject.toml` does not contain `tool` section. 53 | [markab108] 54 | 55 | 56 | 0.13.8 (2021-05-26) 57 | ------------------- 58 | 59 | - Fix when `pyproject.toml` does not contain `tool` section. 60 | [anjos] 61 | 62 | - Add the convenience function ``iex()``. 63 | [alanbernstein] 64 | 65 | 66 | 0.13.7 (2021-03-16) 67 | ------------------- 68 | 69 | - Do not instantiate IPython on import 70 | [adamchainz] 71 | 72 | 73 | 0.13.6 (2021-03-08) 74 | ------------------- 75 | 76 | - Fix broken parsing of pyproject.toml 77 | [alexandrebarbaruiva] 78 | 79 | 80 | 0.13.5 (2021-03-02) 81 | ------------------- 82 | 83 | - Add support for pyproject.toml as configuration file 84 | [alexandrebarbaruiva] 85 | 86 | - For users using python 3.4, install 6.0.0 <= IPython < 7.0.0, 87 | for users using python 3.5, install 7.0.0 <= IPython < 7.10.0, 88 | for users using python 3.6, install 7.10.0 <= IPython < 7.17.0, 89 | for users using python>3.6, install IPython >= 7.17.0. 90 | [d1618033] 91 | 92 | 93 | 0.13.4 (2020-10-01) 94 | ------------------- 95 | 96 | - Add '-m' option 97 | [mrmino] 98 | 99 | 100 | 0.13.3 (2020-06-23) 101 | ------------------- 102 | 103 | - Allow runcall, runeval to also use set context value 104 | [meowser] 105 | 106 | - Add condition argument to set_trace 107 | [alexandrebarbaruiva] 108 | 109 | 110 | 0.13.2 (2020-03-03) 111 | ------------------- 112 | 113 | - Remove leftover debug code 114 | [gotcha] 115 | 116 | 117 | 0.13.1 (2020-02-28) 118 | ------------------- 119 | 120 | - Fix when no configuration file 121 | [gotcha] 122 | 123 | 124 | 0.13.0 (2020-02-28) 125 | ------------------- 126 | 127 | - Add option to set context via environment variable or configuration file 128 | [alexandrebarbaruiva] 129 | 130 | 131 | 0.12.3 (2019-12-03) 132 | ------------------- 133 | 134 | - Fix version in usage 135 | [gotcha] 136 | 137 | 138 | 0.12.2 (2019-07-30) 139 | ------------------- 140 | 141 | - Avoid emitting term-title bytes 142 | [steinnes] 143 | 144 | 145 | 0.12.1 (2019-07-26) 146 | ------------------- 147 | 148 | - Fix --help 149 | [native-api] 150 | 151 | 152 | 0.12 (2019-03-20) 153 | ----------------- 154 | 155 | - Drop support for Python 3.3.x 156 | [bmw] 157 | - Stop deprecation warnings from being raised when IPython >= 5.1 is used. 158 | Support for IPython < 5.1 has been dropped. 159 | [bmw] 160 | 161 | 162 | 0.11 (2018-02-15) 163 | ----------------- 164 | 165 | - Simplify loading IPython and getting information from it. 166 | Drop support for python 2.6 167 | Drop support for IPython < 5.0.0 168 | [takluyver] 169 | 170 | 171 | 0.10.3 (2017-04-22) 172 | ------------------- 173 | 174 | - For users using python 2.6, do not install IPython >= 2.0.0. 175 | And for users using python 2.7, do not install IPython >= 6.0.0. 176 | [vphilippon] 177 | - Drop support for python 3.2. 178 | [vphilippon] 179 | - Command line usage consistent with pdb - Add argument commands 180 | [zvodd] 181 | 182 | 183 | 0.10.2 (2017-01-25) 184 | ------------------- 185 | 186 | - Ask IPython which debugger class to use. 187 | Closes https://github.com/gotcha/ipdb/issues/105 188 | [gnebehay, JBKahn] 189 | 190 | - ipdb.set_trace() does not ignore context arg anymore. 191 | Closes https://github.com/gotcha/ipdb/issues/93. 192 | [gnebehay, Garrett-R] 193 | 194 | 195 | 0.10.1 (2016-06-14) 196 | ------------------- 197 | 198 | - Support IPython 5.0. 199 | [ngoldbaum] 200 | 201 | 202 | 0.10.0 (2016-04-29) 203 | ------------------- 204 | 205 | - Stop trying to magically guess when stdout needs to be captured. 206 | Like needed by `nose`. 207 | Rather, provide a set of function that can be called explicitely when needed. 208 | [gotcha] 209 | 210 | - drop support of IPython before 0.10.2 211 | 212 | 213 | 0.9.4 (2016-04-29) 214 | ------------------ 215 | 216 | - Fix Restart error when using `python -m ipdb` 217 | Closes https://github.com/gotcha/ipdb/issues/93. 218 | [gotcha] 219 | 220 | 221 | 0.9.3 (2016-04-15) 222 | ------------------ 223 | 224 | - Don't require users to pass a traceback to post_mortem. 225 | [Wilfred] 226 | 227 | 228 | 0.9.2 (2016-04-15) 229 | ------------------ 230 | 231 | - Closes https://github.com/gotcha/ipdb/issues/93. 232 | [gotcha] 233 | 234 | 235 | 0.9.1 (2016-04-12) 236 | ------------------ 237 | 238 | - Reset ``sys.modules['__main__']`` to original value. 239 | Closes https://github.com/gotcha/ipdb/issues/85 240 | [gotcha] 241 | 242 | - Fix support of IPython versions 0.x 243 | [asivokon] 244 | 245 | 246 | 0.9.0 (2016-02-22) 247 | ------------------ 248 | 249 | - Switch to revised BSD license (with approval of all contributors). 250 | Closes https://github.com/gotcha/ipdb/issues/68 251 | [lebedov, gotcha] 252 | 253 | 0.8.3 (2016-02-17) 254 | ------------------ 255 | 256 | - Don't pass sys.argv to IPython for configuration. 257 | [emulbreh] 258 | 259 | 260 | 0.8.2 (2016-02-15) 261 | ------------------ 262 | 263 | - Fix lexical comparison for version numbers. 264 | [sas23] 265 | 266 | - Allow configuring how many lines of code context are displayed 267 | by `set_trace` 268 | [JamshedVesuna] 269 | 270 | - If an instance of IPython is already running its configuration will be 271 | loaded. 272 | [IxDay] 273 | 274 | 275 | 0.8.1 (2015-06-03) 276 | ------------------ 277 | 278 | - Make Nose support less invasive. 279 | Closes https://github.com/gotcha/ipdb/issues/52 280 | Closes https://github.com/gotcha/ipdb/issues/31 281 | [blink1073, gotcha] 282 | 283 | - Fix for post_mortem in context manager. 284 | Closes https://github.com/gotcha/ipdb/issues/20 285 | [omergertel] 286 | 287 | 288 | 0.8 (2013-09-19) 289 | ---------------- 290 | 291 | - More Python 3 compatibility; implies dropping Python 2.5 support. 292 | Closes https://github.com/gotcha/ipdb/issues/37 293 | [gotcha] 294 | 295 | 296 | 0.7.1 (2013-09-19) 297 | ------------------ 298 | 299 | - IPython 1.0 compatibility. 300 | Closes https://github.com/gotcha/ipdb/issues/44 301 | [pgularski] 302 | 303 | - Index into version_info in setup.py for Python 2.6 compatibility. 304 | [kynan] 305 | 306 | - Add Travis CI configuration. 307 | [kynan] 308 | 309 | 0.7 (2012-07-06) 310 | ---------------- 311 | 312 | - Add ``launch_ipdb_on_exception`` context manager. Implies dropping Python 2.4 support. 313 | [Psycojoker] 314 | 315 | - Wrap sys.excepthook only once. 316 | [marciomazza] 317 | 318 | - Add GPL file and refer in headers. 319 | [stan3] 320 | 321 | - Python 3 support. 322 | [Piet Delport] 323 | 324 | - Basic tests. 325 | [msabramo] 326 | 327 | - Added the functions ``runcall``, ``runeval`` and ``run``. 328 | [dimasad] 329 | 330 | 331 | 0.6.1 (2011-10-17) 332 | ------------------ 333 | 334 | - State dependency on IPython later or equal to 0.10. 335 | [gotcha] 336 | 337 | 338 | 0.6 (2011-09-01) 339 | ---------------- 340 | 341 | - Add setuptools ``console_scripts`` entry point. 342 | [akrito, gotcha] 343 | 344 | - Nose support. 345 | Closes https://github.com/gotcha/ipdb/issues/8 346 | [akaihola, gotcha] 347 | 348 | 349 | 0.5 (2011-08-05) 350 | ---------------- 351 | 352 | - IPython 0.11 support. 353 | [lebedov] 354 | 355 | 356 | 0.4 (2011-06-13) 357 | ---------------- 358 | 359 | - When used from IPython, use its colors. 360 | Closes https://github.com/gotcha/ipdb/issues/1 361 | [gotcha] 362 | 363 | - Fixed errors when exiting with "q". 364 | [gotcha] 365 | 366 | - Allow use of ``python -m ipdb mymodule.py``. 367 | Python 2.7 only. 368 | Closes https://github.com/gotcha/ipdb/issues/3 369 | [gotcha] 370 | 371 | - Fixed post_mortem when the traceback is None. 372 | [maurits] 373 | 374 | 375 | 0.3 (2011-01-16) 376 | ---------------- 377 | 378 | - Add ``post_mortem()`` for ``Products.PDBDebugMode`` support. 379 | [Jean Jordaan] 380 | 381 | - Moved to github.com. 382 | 383 | 384 | 0.2 (2010-10-20) 385 | ---------------- 386 | 387 | - Added ``pm()``. 388 | [Paulo Benedict Ang] 389 | 390 | 391 | 0.1 (2010-04-26) 392 | ---------------- 393 | 394 | - First "non dev" release. 395 | -------------------------------------------------------------------------------- /ipdb/__main__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2016 Godefroid Chapelle and ipdb development team 2 | # 3 | # This file is part of ipdb. 4 | # Redistributable under the revised BSD license 5 | # https://opensource.org/licenses/BSD-3-Clause 6 | 7 | from __future__ import print_function 8 | import os 9 | import sys 10 | 11 | from decorator import contextmanager 12 | 13 | __version__ = "0.13.14.dev0" 14 | 15 | from IPython import get_ipython 16 | from IPython.core.debugger import BdbQuit_excepthook 17 | from IPython.terminal.ipapp import TerminalIPythonApp 18 | from IPython.terminal.embed import InteractiveShellEmbed 19 | 20 | try: 21 | import configparser 22 | except: 23 | import ConfigParser as configparser 24 | 25 | 26 | def _get_debugger_cls(): 27 | shell = get_ipython() 28 | if shell is None: 29 | # Not inside IPython 30 | # Build a terminal app in order to force ipython to load the 31 | # configuration 32 | ipapp = TerminalIPythonApp() 33 | # Avoid output (banner, prints) 34 | ipapp.interact = False 35 | ipapp.initialize(["--no-term-title"]) 36 | shell = ipapp.shell 37 | else: 38 | # Running inside IPython 39 | 40 | # Detect if embed shell or not and display a message 41 | if isinstance(shell, InteractiveShellEmbed): 42 | sys.stderr.write( 43 | "\nYou are currently into an embedded ipython shell,\n" 44 | "the configuration will not be loaded.\n\n" 45 | ) 46 | 47 | # Let IPython decide about which debugger class to use 48 | # This is especially important for tools that fiddle with stdout 49 | return shell.debugger_cls 50 | 51 | 52 | def _init_pdb(context=None, commands=[]): 53 | if context is None: 54 | context = os.getenv("IPDB_CONTEXT_SIZE", get_context_from_config()) 55 | debugger_cls = _get_debugger_cls() 56 | try: 57 | p = debugger_cls(context=context) 58 | except TypeError: 59 | p = debugger_cls() 60 | p.rcLines.extend(commands) 61 | return p 62 | 63 | 64 | def wrap_sys_excepthook(): 65 | # make sure we wrap it only once or we would end up with a cycle 66 | # BdbQuit_excepthook.excepthook_ori == BdbQuit_excepthook 67 | if sys.excepthook != BdbQuit_excepthook: 68 | BdbQuit_excepthook.excepthook_ori = sys.excepthook 69 | sys.excepthook = BdbQuit_excepthook 70 | 71 | 72 | def set_trace(frame=None, context=None, cond=True): 73 | if not cond: 74 | return 75 | wrap_sys_excepthook() 76 | if frame is None: 77 | frame = sys._getframe().f_back 78 | p = _init_pdb(context).set_trace(frame) 79 | if p and hasattr(p, "shell"): 80 | p.shell.restore_sys_module_state() 81 | 82 | 83 | def get_context_from_config(): 84 | parser = get_config() 85 | try: 86 | return parser.getint("ipdb", "context") 87 | except (configparser.NoSectionError, configparser.NoOptionError): 88 | return 3 89 | except ValueError: 90 | value = parser.get("ipdb", "context") 91 | raise ValueError( 92 | "In %s, context value [%s] cannot be converted into an integer." 93 | % (parser.filepath, value) 94 | ) 95 | 96 | 97 | class ConfigFile(object): 98 | """ 99 | Filehandle wrapper that adds a "[ipdb]" section to the start of a config 100 | file so that users don't actually have to manually add a [ipdb] section. 101 | Works with configparser versions from both Python 2 and 3 102 | """ 103 | 104 | def __init__(self, filepath): 105 | self.first = True 106 | with open(filepath) as f: 107 | self.lines = f.readlines() 108 | 109 | # Python 2.7 (Older dot versions) 110 | def readline(self): 111 | try: 112 | return self.__next__() 113 | except StopIteration: 114 | return "" 115 | 116 | # Python 2.7 (Newer dot versions) 117 | def next(self): 118 | return self.__next__() 119 | 120 | # Python 3 121 | def __iter__(self): 122 | return self 123 | 124 | def __next__(self): 125 | if self.first: 126 | self.first = False 127 | return "[ipdb]\n" 128 | if self.lines: 129 | return self.lines.pop(0) 130 | raise StopIteration 131 | 132 | 133 | def get_config(): 134 | """ 135 | Get ipdb config file settings. 136 | All available config files are read. If settings are in multiple configs, 137 | the last value encountered wins. Values specified on the command-line take 138 | precedence over all config file settings. 139 | Returns: A ConfigParser object. 140 | """ 141 | parser = configparser.ConfigParser() 142 | 143 | filepaths = [] 144 | 145 | # Low priority goes first in the list 146 | for cfg_file in ("setup.cfg", ".ipdb", "pyproject.toml"): 147 | cwd_filepath = os.path.join(os.getcwd(), cfg_file) 148 | if os.path.isfile(cwd_filepath): 149 | filepaths.append(cwd_filepath) 150 | 151 | # Medium priority (whenever user wants to set a specific path to config file) 152 | home = os.getenv("HOME") 153 | if home: 154 | default_filepath = os.path.join(home, ".ipdb") 155 | if os.path.isfile(default_filepath): 156 | filepaths.append(default_filepath) 157 | 158 | # High priority (default files) 159 | env_filepath = os.getenv("IPDB_CONFIG") 160 | if env_filepath and os.path.isfile(env_filepath): 161 | filepaths.append(env_filepath) 162 | 163 | if filepaths: 164 | # Python 3 has parser.read_file(iterator) while Python2 has 165 | # parser.readfp(obj_with_readline) 166 | try: 167 | read_func = parser.read_file 168 | except AttributeError: 169 | read_func = parser.readfp 170 | for filepath in filepaths: 171 | parser.filepath = filepath 172 | # Users are expected to put an [ipdb] section 173 | # only if they use setup.cfg 174 | if filepath.endswith("setup.cfg"): 175 | with open(filepath) as f: 176 | parser.remove_section("ipdb") 177 | read_func(f) 178 | # To use on pyproject.toml, put [tool.ipdb] section 179 | elif filepath.endswith("pyproject.toml"): 180 | try: 181 | import tomllib 182 | 183 | file_mode = "rb" 184 | except ImportError: 185 | try: 186 | import tomli as tomllib 187 | 188 | file_mode = "rb" 189 | except ImportError: 190 | import toml as tomllib 191 | 192 | file_mode = "r" 193 | with open(filepath, file_mode) as f: 194 | toml_file = tomllib.load(f) 195 | if "tool" in toml_file and "ipdb" in toml_file["tool"]: 196 | if not parser.has_section("ipdb"): 197 | parser.add_section("ipdb") 198 | for key, value in toml_file["tool"]["ipdb"].items(): 199 | parser.set("ipdb", key, str(value)) 200 | else: 201 | read_func(ConfigFile(filepath)) 202 | return parser 203 | 204 | 205 | def post_mortem(tb=None): 206 | wrap_sys_excepthook() 207 | p = _init_pdb() 208 | p.reset() 209 | if tb is None: 210 | # sys.exc_info() returns (type, value, traceback) if an exception is 211 | # being handled, otherwise it returns None 212 | tb = sys.exc_info()[2] 213 | if tb: 214 | p.interaction(None, tb) 215 | 216 | 217 | def pm(): 218 | post_mortem(sys.last_traceback) 219 | 220 | 221 | def run(statement, globals=None, locals=None): 222 | _init_pdb().run(statement, globals, locals) 223 | 224 | 225 | def runcall(*args, **kwargs): 226 | return _init_pdb().runcall(*args, **kwargs) 227 | 228 | 229 | def runeval(expression, globals=None, locals=None): 230 | return _init_pdb().runeval(expression, globals, locals) 231 | 232 | 233 | @contextmanager 234 | def launch_ipdb_on_exception(): 235 | try: 236 | yield 237 | except Exception: 238 | e, m, tb = sys.exc_info() 239 | print(m.__repr__(), file=sys.stderr) 240 | post_mortem(tb) 241 | finally: 242 | pass 243 | 244 | 245 | # iex is a concise alias 246 | iex = launch_ipdb_on_exception() 247 | 248 | 249 | _usage = ( 250 | """\ 251 | usage: python -m ipdb [-m] [-c command] ... pyfile [arg] ... 252 | 253 | Debug the Python program given by pyfile. 254 | 255 | Initial commands are read from .pdbrc files in your home directory 256 | and in the current directory, if they exist. Commands supplied with 257 | -c are executed after commands from .pdbrc files. 258 | 259 | To let the script run until an exception occurs, use "-c continue". 260 | To let the script run up to a given line X in the debugged file, use 261 | "-c 'until X'" 262 | 263 | Option -m is available only in Python 3.7 and later. 264 | 265 | ipdb version %s.""" 266 | % __version__ 267 | ) 268 | 269 | 270 | def main(): 271 | import traceback 272 | import sys 273 | import getopt 274 | 275 | try: 276 | from pdb import Restart 277 | except ImportError: 278 | 279 | class Restart(Exception): 280 | pass 281 | 282 | if sys.version_info >= (3, 7): 283 | opts, args = getopt.getopt(sys.argv[1:], "mhc:", ["help", "command="]) 284 | else: 285 | opts, args = getopt.getopt(sys.argv[1:], "hc:", ["help", "command="]) 286 | 287 | commands = [] 288 | run_as_module = False 289 | for opt, optarg in opts: 290 | if opt in ["-h", "--help"]: 291 | print(_usage) 292 | sys.exit() 293 | elif opt in ["-c", "--command"]: 294 | commands.append(optarg) 295 | elif opt in ["-m"]: 296 | run_as_module = True 297 | 298 | if not args: 299 | print(_usage) 300 | sys.exit(2) 301 | 302 | mainpyfile = args[0] # Get script filename 303 | if not run_as_module and not os.path.exists(mainpyfile): 304 | print("Error:", mainpyfile, "does not exist") 305 | sys.exit(1) 306 | 307 | sys.argv = args # Hide "pdb.py" from argument list 308 | 309 | # Replace pdb's dir with script's dir in front of module search path. 310 | if not run_as_module: 311 | sys.path[0] = os.path.dirname(mainpyfile) 312 | 313 | # Note on saving/restoring sys.argv: it's a good idea when sys.argv was 314 | # modified by the script being debugged. It's a bad idea when it was 315 | # changed by the user from the command line. There is a "restart" command 316 | # which allows explicit specification of command line arguments. 317 | pdb = _init_pdb(commands=commands) 318 | while 1: 319 | try: 320 | import pdb as stdlib_pdb 321 | 322 | if hasattr(stdlib_pdb.Pdb, "_run"): 323 | # Looks like Pdb from Python 3.11+ 324 | if run_as_module: 325 | pdb._run(stdlib_pdb._ModuleTarget(mainpyfile)) 326 | else: 327 | pdb._run(stdlib_pdb._ScriptTarget(mainpyfile)) 328 | else: 329 | if run_as_module: 330 | pdb._runmodule(mainpyfile) 331 | else: 332 | pdb._runscript(mainpyfile) 333 | if pdb._user_requested_quit: 334 | break 335 | print("The program finished and will be restarted") 336 | except Restart: 337 | print("Restarting", mainpyfile, "with arguments:") 338 | print("\t" + " ".join(sys.argv[1:])) 339 | except SystemExit: 340 | # In most cases SystemExit does not warrant a post-mortem session. 341 | print("The program exited via sys.exit(). Exit status: ", end="") 342 | print(sys.exc_info()[1]) 343 | except: 344 | traceback.print_exc() 345 | print("Uncaught exception. Entering post mortem debugging") 346 | print("Running 'cont' or 'step' will restart the program") 347 | t = sys.exc_info()[2] 348 | pdb.interaction(None, t) 349 | print( 350 | "Post mortem debugger finished. The " 351 | + mainpyfile 352 | + " will be restarted" 353 | ) 354 | 355 | 356 | if __name__ == "__main__": 357 | main() 358 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2016 Marc Abramowitz and ipdb development team 2 | # 3 | # This file is part of ipdb. 4 | # Redistributable under the revised BSD license 5 | # https://opensource.org/licenses/BSD-3-Clause 6 | 7 | try: 8 | import configparser 9 | except: 10 | import ConfigParser as configparser 11 | import unittest 12 | import os 13 | import tempfile 14 | import shutil 15 | 16 | from ipdb.__main__ import ( 17 | get_config, 18 | get_context_from_config, 19 | ) 20 | 21 | 22 | class ModifiedEnvironment(object): 23 | """ 24 | I am a context manager that sets up environment variables for a test case. 25 | """ 26 | 27 | def __init__(self, **kwargs): 28 | self.prev = {} 29 | self.excur = kwargs 30 | for k in kwargs: 31 | self.prev[k] = os.getenv(k) 32 | 33 | def __enter__(self): 34 | self.update_environment(self.excur) 35 | 36 | def __exit__(self, type, value, traceback): 37 | self.update_environment(self.prev) 38 | 39 | def update_environment(self, d): 40 | for k in d: 41 | if d[k] is None: 42 | if k in os.environ: 43 | del os.environ[k] 44 | else: 45 | os.environ[k] = d[k] 46 | 47 | 48 | def write_lines_to_file(path, lines): 49 | """ 50 | Write `lines` to file at `path`. 51 | 52 | :param path: Filesystem path to write. 53 | :param lines: Sequence of text lines, without line endings. 54 | :return: None. 55 | """ 56 | f = open(path, "w") 57 | f.writelines([x + "\n" for x in lines]) 58 | f.close() 59 | 60 | 61 | def set_config_files_fixture(testcase): 62 | """ 63 | Set a data fixture of configuration files for `testcase`. 64 | """ 65 | testcase.tmpd = tempfile.mkdtemp() 66 | testcase.addCleanup(shutil.rmtree, testcase.tmpd) 67 | # Set CWD to known empty directory so we don't pick up some other .ipdb 68 | # file from the CWD tests are actually run from. 69 | save_cwd = os.getcwd() 70 | testcase.addCleanup(os.chdir, save_cwd) 71 | cwd_dir = os.path.join(testcase.tmpd, "cwd") 72 | os.mkdir(cwd_dir) 73 | os.chdir(cwd_dir) 74 | # This represents the $HOME config file, and doubles for the current 75 | # working directory config file if we set CWD to testcase.tmpd 76 | testcase.default_filename = os.path.join(testcase.tmpd, ".ipdb") 77 | testcase.default_context = 10 78 | write_lines_to_file( 79 | testcase.default_filename, 80 | [ 81 | "# this is a test config file for ipdb", 82 | "context = {}".format(str(testcase.default_context)), 83 | ], 84 | ) 85 | testcase.env_filename = os.path.join(testcase.tmpd, "ipdb.env") 86 | testcase.env_context = 20 87 | write_lines_to_file( 88 | testcase.env_filename, 89 | [ 90 | "# this is a test config file for ipdb", 91 | "context = {}".format(str(testcase.env_context)), 92 | ], 93 | ) 94 | testcase.setup_filename = os.path.join(cwd_dir, "setup.cfg") 95 | testcase.setup_context = 25 96 | write_lines_to_file( 97 | testcase.setup_filename, 98 | [ 99 | "[ipdb]", 100 | "context = {}".format(str(testcase.setup_context)), 101 | ], 102 | ) 103 | testcase.pyproject_filename = os.path.join(cwd_dir, "pyproject.toml") 104 | testcase.pyproject_context = 30 105 | write_lines_to_file( 106 | testcase.pyproject_filename, 107 | [ 108 | "[tool.ipdb]", 109 | "context = {}".format(str(testcase.pyproject_context)), 110 | ], 111 | ) 112 | 113 | 114 | class ConfigTest(unittest.TestCase): 115 | """ 116 | All variations of config file parsing works as expected. 117 | """ 118 | 119 | def setUp(self): 120 | """ 121 | Set fixtures for this test case. 122 | """ 123 | set_config_files_fixture(self) 124 | 125 | def test_noenv_nodef_nosetup_pyproject(self): 126 | """ 127 | Setup: $IPDB_CONFIG unset, $HOME/.ipdb does not exist, 128 | setup.cfg does not exist, pyproject.toml exists 129 | Result: load pyproject.toml 130 | """ 131 | os.unlink(self.env_filename) 132 | os.unlink(self.default_filename) 133 | os.remove(self.setup_filename) 134 | with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): 135 | cfg = get_config() 136 | self.assertEqual(["ipdb"], cfg.sections()) 137 | self.assertEqual(self.pyproject_context, cfg.getint("ipdb", "context")) 138 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 139 | 140 | def test_env_nodef_setup_pyproject(self): 141 | """ 142 | Setup: $IPDB_CONFIG is set, $HOME/.ipdb does not exist, 143 | setup.cfg exists, pyproject.toml exists 144 | Result: load $IPDB_CONFIG 145 | """ 146 | os.unlink(self.default_filename) 147 | with ModifiedEnvironment(IPDB_CONFIG=self.env_filename, HOME=self.tmpd): 148 | cfg = get_config() 149 | self.assertEqual(["ipdb"], cfg.sections()) 150 | self.assertEqual(self.env_context, cfg.getint("ipdb", "context")) 151 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 152 | 153 | def test_env_def_setup_pyproject(self): 154 | """ 155 | Setup: $IPDB_CONFIG is set, $HOME/.ipdb exists, 156 | setup.cfg exists, pyproject.toml exists 157 | Result: load $IPDB_CONFIG 158 | """ 159 | with ModifiedEnvironment(IPDB_CONFIG=self.env_filename, HOME=self.tmpd): 160 | cfg = get_config() 161 | self.assertEqual(["ipdb"], cfg.sections()) 162 | self.assertEqual(self.env_context, cfg.getint("ipdb", "context")) 163 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 164 | 165 | def test_noenv_nodef_setup_pyproject(self): 166 | """ 167 | Setup: $IPDB_CONFIG unset, $HOME/.ipdb does not exist, 168 | setup.cfg exists, pyproject.toml exists 169 | Result: load pyproject.toml 170 | """ 171 | os.unlink(self.env_filename) 172 | os.unlink(self.default_filename) 173 | with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): 174 | cfg = get_config() 175 | self.assertEqual(["ipdb"], cfg.sections()) 176 | self.assertEqual(self.pyproject_context, cfg.getint("ipdb", "context")) 177 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 178 | 179 | def test_noenv_def_setup_pyproject(self): 180 | """ 181 | Setup: $IPDB_CONFIG unset, $HOME/.ipdb exists, 182 | setup.cfg exists, pyproject.toml exists 183 | Result: load .ipdb 184 | """ 185 | os.unlink(self.env_filename) 186 | with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): 187 | cfg = get_config() 188 | self.assertEqual(["ipdb"], cfg.sections()) 189 | self.assertEqual(self.default_context, cfg.getint("ipdb", "context")) 190 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 191 | 192 | def test_env_nodef_nosetup(self): 193 | """ 194 | Setup: $IPDB_CONFIG is set, $HOME/.ipdb does not exist, 195 | setup.cfg does not exist, pyproject.toml does not exist 196 | Result: load $IPDB_CONFIG 197 | """ 198 | os.unlink(self.default_filename) 199 | os.unlink(self.pyproject_filename) 200 | os.remove(self.setup_filename) 201 | with ModifiedEnvironment(IPDB_CONFIG=self.env_filename, HOME=self.tmpd): 202 | cfg = get_config() 203 | self.assertEqual(["ipdb"], cfg.sections()) 204 | self.assertEqual(self.env_context, cfg.getint("ipdb", "context")) 205 | self.assertRaises( 206 | configparser.NoOptionError, cfg.getboolean, "ipdb", "version" 207 | ) 208 | 209 | def test_noenv_def_nosetup(self): 210 | """ 211 | Setup: $IPDB_CONFIG unset, $HOME/.ipdb exists, 212 | setup.cfg does not exist, pyproject.toml does not exist 213 | Result: load $HOME/.ipdb 214 | """ 215 | os.unlink(self.env_filename) 216 | os.unlink(self.pyproject_filename) 217 | os.remove(self.setup_filename) 218 | with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): 219 | cfg = get_config() 220 | self.assertEqual(["ipdb"], cfg.sections()) 221 | self.assertEqual(self.default_context, cfg.getint("ipdb", "context")) 222 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 223 | 224 | def test_noenv_nodef_nosetup(self): 225 | """ 226 | Setup: $IPDB_CONFIG unset, $HOME/.ipdb does not 227 | exist, setup.cfg does not exist, pyproject.toml does not exist 228 | Result: load nothing 229 | """ 230 | os.unlink(self.env_filename) 231 | os.unlink(self.default_filename) 232 | os.unlink(self.pyproject_filename) 233 | os.remove(self.setup_filename) 234 | with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): 235 | cfg = get_config() 236 | self.assertEqual([], cfg.sections()) 237 | 238 | def test_env_cwd(self): 239 | """ 240 | Setup: $IPDB_CONFIG is set, .ipdb in local dir, 241 | setup.cfg does not exist, pyproject.toml does not exist 242 | Result: load .ipdb 243 | """ 244 | os.chdir(self.tmpd) # setUp is already set to restore us to our pre-testing cwd 245 | os.unlink(self.pyproject_filename) 246 | os.remove(self.setup_filename) 247 | with ModifiedEnvironment(IPDB_CONFIG=self.env_filename, HOME=self.tmpd): 248 | cfg = get_config() 249 | self.assertEqual(["ipdb"], cfg.sections()) 250 | self.assertEqual(self.env_context, cfg.getint("ipdb", "context")) 251 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 252 | 253 | def test_env_def_nosetup(self): 254 | """ 255 | Setup: $IPDB_CONFIG is set, $HOME/.ipdb exists, 256 | setup.cfg does not exist, pyproject.toml does not exist 257 | Result: load $IPDB_CONFIG 258 | """ 259 | os.unlink(self.pyproject_filename) 260 | os.remove(self.setup_filename) 261 | with ModifiedEnvironment(IPDB_CONFIG=self.env_filename, HOME=self.tmpd): 262 | cfg = get_config() 263 | self.assertEqual(["ipdb"], cfg.sections()) 264 | self.assertEqual(self.env_context, cfg.getint("ipdb", "context")) 265 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 266 | 267 | def test_noenv_def_setup(self): 268 | """ 269 | Setup: $IPDB_CONFIG unset, $HOME/.ipdb exists, 270 | setup.cfg exists, pyproject.toml does not exist 271 | Result: load $HOME/.ipdb 272 | """ 273 | os.unlink(self.env_filename) 274 | os.unlink(self.pyproject_filename) 275 | with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): 276 | cfg = get_config() 277 | self.assertEqual(["ipdb"], cfg.sections()) 278 | self.assertEqual(self.default_context, cfg.getint("ipdb", "context")) 279 | self.assertRaises( 280 | configparser.NoOptionError, cfg.getboolean, "ipdb", "version" 281 | ) 282 | 283 | def test_noenv_nodef_setup(self): 284 | """ 285 | Setup: $IPDB_CONFIG unset, $HOME/.ipdb does not exist, 286 | setup.cfg exists, pyproject.toml does not exist 287 | Result: load setup 288 | """ 289 | os.unlink(self.env_filename) 290 | os.unlink(self.default_filename) 291 | os.unlink(self.pyproject_filename) 292 | with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): 293 | cfg = get_config() 294 | self.assertEqual(["ipdb"], cfg.sections()) 295 | self.assertEqual(self.setup_context, cfg.getint("ipdb", "context")) 296 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 297 | 298 | def test_env_def_setup(self): 299 | """ 300 | Setup: $IPDB_CONFIG is set, $HOME/.ipdb exists, 301 | setup.cfg exists, pyproject.toml does not exist 302 | Result: load $IPDB_CONFIG 303 | """ 304 | os.unlink(self.pyproject_filename) 305 | with ModifiedEnvironment(IPDB_CONFIG=self.env_filename, HOME=self.tmpd): 306 | cfg = get_config() 307 | self.assertEqual(["ipdb"], cfg.sections()) 308 | self.assertEqual(self.env_context, cfg.getint("ipdb", "context")) 309 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 310 | 311 | def test_env_nodef_setup(self): 312 | """ 313 | Setup: $IPDB_CONFIG is set, $HOME/.ipdb does not 314 | exist, setup.cfg exists, pyproject.toml does not exist 315 | Result: load $IPDB_CONFIG 316 | """ 317 | os.unlink(self.default_filename) 318 | os.unlink(self.pyproject_filename) 319 | with ModifiedEnvironment(IPDB_CONFIG=self.env_filename, HOME=self.tmpd): 320 | cfg = get_config() 321 | self.assertEqual(["ipdb"], cfg.sections()) 322 | self.assertEqual(self.env_context, cfg.getint("ipdb", "context")) 323 | self.assertRaises( 324 | configparser.NoOptionError, cfg.getboolean, "ipdb", "version" 325 | ) 326 | 327 | def test_noenv_def_setup(self): 328 | """ 329 | Setup: $IPDB_CONFIG unset, $HOME/.ipdb exists, 330 | setup.cfg exists, pyproject.toml does not exist 331 | Result: load $HOME/.ipdb 332 | """ 333 | os.unlink(self.env_filename) 334 | os.unlink(self.pyproject_filename) 335 | with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): 336 | cfg = get_config() 337 | self.assertEqual(["ipdb"], cfg.sections()) 338 | self.assertEqual(self.default_context, cfg.getint("ipdb", "context")) 339 | self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") 340 | 341 | 342 | class get_context_from_config_TestCase(unittest.TestCase): 343 | """ 344 | Test cases for function `get_context_from_config`. 345 | """ 346 | 347 | def setUp(self): 348 | """ 349 | Set fixtures for this test case. 350 | """ 351 | set_config_files_fixture(self) 352 | 353 | def test_noenv_nodef_invalid_setup(self): 354 | """ 355 | Setup: $IPDB_CONFIG unset, $HOME/.ipdb does not exist, 356 | setup.cfg does not exist, pyproject.toml content is invalid. 357 | Result: Propagate exception from `get_config`. 358 | """ 359 | os.unlink(self.env_filename) 360 | os.unlink(self.default_filename) 361 | os.unlink(self.setup_filename) 362 | write_lines_to_file( 363 | self.pyproject_filename, 364 | [ 365 | "[ipdb]", 366 | "spam = abc", 367 | ], 368 | ) 369 | 370 | try: 371 | from tomllib import TOMLDecodeError 372 | except ImportError: 373 | try: 374 | from tomli import TOMLDecodeError 375 | except ImportError: 376 | from toml.decoder import TomlDecodeError as TOMLDecodeError 377 | 378 | with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): 379 | try: 380 | get_context_from_config() 381 | except TOMLDecodeError: 382 | pass 383 | else: 384 | self.fail("Expected TomlDecodeError from invalid config file") 385 | --------------------------------------------------------------------------------