├── MANIFEST.in ├── src ├── __init__.py └── pdbp.py ├── requirements.txt ├── setup.cfg ├── SECURITY.md ├── pyproject.toml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── README.md ├── setup.py └── LICENSE /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include .gitignore 2 | include LICENSE -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | import pdb # noqa 2 | import pdbp # noqa 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pygments>=2.19.2 2 | tabcompleter>=1.4.0 3 | colorama>=0.4.6;platform_system=="Windows" 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # W503 (line break before binary operator) can be ignored. 3 | exclude=recordings,temp 4 | ignore=W503 5 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you've found a security vulnerability in ``pdbp``, (or a dependency we use), please open an issue. 6 | 7 | [github.com/mdmintz/pdbp/issues](https://github.com/mdmintz/pdbp/issues) 8 | 9 | Please describe the results you're seeing, and the results you're expecting. 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=70.2.0", "wheel>=0.44.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pdbp" 7 | readme = "README.md" 8 | dynamic = [ 9 | "urls", 10 | "version", 11 | "license", 12 | "authors", 13 | "scripts", 14 | "keywords", 15 | "classifiers", 16 | "description", 17 | "maintainers", 18 | "entry-points", 19 | "dependencies", 20 | "requires-python", 21 | "optional-dependencies", 22 | ] 23 | 24 | [tool.setuptools.packages.find] 25 | where = ["src"] 26 | 27 | [flake8] 28 | ignore = ["W503"] 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Python Bytecode 2 | *.py[cod] 3 | 4 | # Packages 5 | *.egg 6 | *.egg-info 7 | dist 8 | build 9 | .eggs 10 | eggs 11 | parts 12 | bin 13 | var 14 | sdist 15 | develop-eggs 16 | .installed.cfg 17 | lib 18 | lib64 19 | __pycache__ 20 | 21 | # Python3 pyvenv 22 | .env 23 | .venv 24 | env/ 25 | venv/ 26 | ENV/ 27 | VENV/ 28 | env.bak/ 29 | venv.bak/ 30 | pyvenv.cfg 31 | .Python 32 | include 33 | pip-delete-this-directory.txt 34 | pip-selfcheck.json 35 | ipython.1.gz 36 | nosetests.1 37 | 38 | # Installer logs 39 | pip-log.txt 40 | .swp 41 | 42 | # Unit test / coverage reports 43 | .coverage 44 | .tox 45 | coverage.xml 46 | nosetests.xml 47 | 48 | # py.test 49 | .cache/* 50 | .pytest_cache/* 51 | .pytest_config 52 | 53 | # Azure Pipelines 54 | junit 55 | test-results.xml 56 | 57 | # Developer 58 | .idea 59 | .project 60 | .pydevproject 61 | .vscode 62 | 63 | # MkDocs WebSite Generator 64 | site/* 65 | mkdocs_build/*.md 66 | mkdocs_build/*/*.md 67 | mkdocs_build/*/*/*.md 68 | mkdocs_build/*/*/*/*.md 69 | 70 | # macOS system files 71 | .DS_Store 72 | 73 | # Other 74 | assets 75 | temp 76 | temp_*/ 77 | node_modules 78 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | (``pdbp`` uses a modified version of [Flutter's Code of conduct](https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md)) 4 | 5 | The ``pdbp`` project expects contributors to act professionally and respectfully. ``pdbp`` contributors are expected to maintain the safety and dignity of social environments (such as GitHub and Gitter). 6 | 7 | Specifically: 8 | 9 | * Respect people, their identities, their culture, and their work. 10 | * Be kind. Be courteous. Be welcoming. 11 | * Listen. Consider and acknowledge people's points before responding. 12 | 13 | Should you experience anything that makes you feel unwelcome in our community, please [contact us](https://gitter.im/seleniumbase/SeleniumBase). 14 | 15 | The ``pdbp`` project will not tolerate harassment in our community, even outside of our public communication channels. 16 | 17 | ## Questions 18 | 19 | It's always OK to ask questions. ``pdbp`` is a big project, and we don't expect everyone to know everything about everything. 20 | 21 | !["I try not to make fun of people for admitting they don't know things, because for each thing 'everyone knows' by the time they're adults, every day there are, on average, 10,000 people in the US hearing about it for the first time. If I make fun of people, I train them not to tell me when they have those moments. And I miss out on the fun." "Diet coke and mentos thing? What's that?" "Oh, man! We're going to the grocery store." "Why?" "You're one of today's lucky 10,000."](https://imgs.xkcd.com/comics/ten_thousand.png) 22 | 23 | Source: _[xkcd, May 2012](https://xkcd.com/1053/)_ 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ``pdbp`` 2 | 3 | The ``pdbp`` project welcomes meaningful contributions. 4 | 5 | There are many ways to help: 6 | 7 | ## Bug Reports 8 | 9 | When opening a new issue or commenting on an existing issue, please make sure to provide concise, detailed instructions on how to reproduce the issue. If the issue can't be reproduced, it will be closed. Clearly describe the results you're seeing, and the results you're expecting. 10 | 11 | ## Feature Requests 12 | 13 | If you find that ``pdbp`` is missing something, feel free to open an issue with details describing what feature(s) you'd like added or changed. 14 | 15 | ## Documentation 16 | 17 | ``pdbp`` is a big software project, and documentation is key to 18 | understanding how it works and how to use it properly. If you feel that important documentation is missing, please let us know, or submit a pull request. 19 | 20 | ## Code Contributions 21 | 22 | The ``pdbp`` project welcomes meaningful contributions. Given the complexity of the project, it may be easier to open an issue for a change you want made than to try implementing the change yourself. 23 | 24 | ## (A Note on Style Guide Rules) 25 | 26 | [flake8](https://github.com/PyCQA/flake8) is the law of the land. The only flake8 rule ignored is [W503](https://github.com/grantmcconnaughey/Flake8Rules/blob/master/_rules/W503.md). For more details on why W503 should be ignored, see [this explanation](https://peps.python.org/pep-0008/#should-a-line-break-before-or-after-a-binary-operator), or [this shorter explanation](https://github.com/PyCQA/flake8/issues/494) by Python expert [Anthony Sottile](https://github.com/asottile). 27 | 28 | -------- 29 | 30 | For questions about this document, reach out to [Michael Mintz](https://github.com/mdmintz). 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pdbp (Pdb+) [![](https://img.shields.io/pypi/v/pdbp.svg)](https://pypi.python.org/pypi/pdbp) 2 | 3 | Pdb+ Advanced Python Console Debugger
4 | 5 | **[pdbp (Pdb+)](https://github.com/mdmintz/pdbp)** is an advanced console debugger for Python. It can be used as a drop-in replacement for [pdb](https://docs.python.org/3/library/pdb.html) and [pdbpp](https://github.com/pdbpp/pdbpp). 6 | 7 |

pdbp (Pdb+) makes Python debugging a lot easier (and more fun!)

8 | 9 | -------- 10 | 11 | ## Installation: 12 | 13 | ```bash 14 | pip install pdbp 15 | ``` 16 | 17 | Then add ``import pdbp`` to an ``__init__.py`` of your project, which will automatically make **``Pdb+``** the default debugger at breakpoints: 18 | 19 | ```python 20 | import pdbp 21 | ``` 22 | 23 | (If using ``flake8`` for code-linting, you may want to add ``# noqa`` to that line): 24 | 25 | ```python 26 | import pdbp # noqa 27 | ``` 28 | 29 | You can also make ``pdbp`` the default debugger by setting an environmental variable: 30 | 31 | ```bash 32 | PYTHONBREAKPOINT=pdbp.set_trace 33 | ``` 34 | 35 | ## Usage: 36 | 37 | To trigger a breakpoint in your code with ``pytest``, add ``--trace`` (to start tests with a breakpoint) or ``--pdb`` (to trigger a breakpoint if a test fails). 38 | 39 | To trigger a breakpoint from a pure ``python`` run, use: 40 | 41 | ```bash 42 | python -m pdbp 43 | ``` 44 | 45 | -------- 46 | 47 | Basic **``Pdb+``** console commands: 48 | ``n``, ``c``, ``s``, ``u``, ``d`` => ``next``, ``continue``, ``step``, ``up``, ``down`` 49 | 50 | (To learn more **Pdb+** console commands, type ``help`` in the **Pdb+** console and press ``Enter/Return``.) 51 | 52 | -------- 53 | 54 | **``pdbp`` (Pdb+)** makes improvements to ``pdbpp`` so that it works in all environments. It also includes other bug-fixes. "Sticky" mode is the default option, which shows multiple lines of code while letting you see where you're going (while typing ``n`` + ``Enter``). 55 | 56 | If you somehow reset ``pdb`` to Python's built-in version, you can always replace ``pdb`` with **``pdbp``** again as the default debugger by running this: 57 | 58 | ```python 59 | import pdb 60 | import pdbp 61 | for key in pdbp.__dict__.keys(): 62 | pdb.__dict__[key] = pdbp.__dict__[key] 63 | ``` 64 | 65 | Here's how to customize **``pdbp``**/``pdb`` options if you don't like the default settings: (Shown below are the default settings.) 66 | 67 | ```python 68 | import pdb 69 | if hasattr(pdb, "DefaultConfig"): 70 | pdb.DefaultConfig.filename_color = pdb.Color.fuchsia 71 | pdb.DefaultConfig.line_number_color = pdb.Color.turquoise 72 | pdb.DefaultConfig.truncate_long_lines = False 73 | pdb.DefaultConfig.sticky_by_default = True 74 | ``` 75 | 76 | You can also trigger **``Pdb+``** activation like this: 77 | 78 | ```python 79 | import pdbp 80 | pdbp.set_trace() 81 | ``` 82 | 83 | 84 | ### pdbp (Pdb+) commands: 85 | 86 | Pdb+ commands 87 | 88 | 89 | ### Post Mortem Debug Mode: 90 | 91 | Pdb+ Post Mortem Debug Mode 92 | 93 | 94 | ### The ``where`` / ``w`` command, which displays the current stack: 95 | 96 | Example of the 'where' command 97 | 98 | -------- 99 | 100 | ### Sticky Mode vs Non-Sticky Mode: 101 | 102 | The default mode (``sticky``) lets you see a lot more lines of code from the debugger when active. In Non-Sticky mode, only one line of code is shown at a time. You can switch between the two modes by typing ``sticky`` in the **Pdb+** console prompt and pressing ``Enter/Return``. 103 | 104 | > **Sticky Mode:** 105 | 106 | Pdb+ Stick Mode 107 | 108 | > **Non-Sticky Mode:** 109 | 110 | Pdb+ Non-Sticky Mode 111 | 112 | -------- 113 | 114 | ### Tab completion: 115 | 116 | Pdb+ Tab Completion 117 | 118 | -------- 119 | 120 | ### Multi-layer highlighting in the same stack: 121 | 122 | Pdb+ Advanced Python Console Debugger 123 | 124 | 125 | ### More examples: 126 | 127 | **``Pdb+``** is used by packages such as **``seleniumbase``**: 128 | 129 | * https://pypi.org/project/seleniumbase/ 130 | * https://github.com/seleniumbase/SeleniumBase 131 | 132 | -------- 133 | 134 | Pdb+ Advanced Python Console Debugger 135 | 136 | -------- 137 | 138 | (**Pdb+** is maintained by the [SeleniumBase Dev Team](https://github.com/seleniumbase/SeleniumBase)) 139 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """*** pdbp (Pdb+) *** 2 | An advanced console debugger for Python. 3 | Can be used as a drop-in replacement for pdb and pdbpp. 4 | (Python 3.8+)""" 5 | from setuptools import setup, find_packages # noqa 6 | import os 7 | import sys 8 | 9 | 10 | this_dir = os.path.abspath(os.path.dirname(__file__)) 11 | long_description = None 12 | total_description = None 13 | try: 14 | with open(os.path.join(this_dir, "README.md"), "rb") as f: 15 | total_description = f.read().decode("utf-8") 16 | description_lines = total_description.split("\n") 17 | long_description_lines = [] 18 | for line in description_lines: 19 | if not line.startswith("=3.9") 33 | sys.exit() 34 | print("\n*** Checking code health with flake8:\n") 35 | os.system("python -m pip install 'flake8==7.3.0'") 36 | flake8_status = os.system("flake8 --exclude=.eggs,temp") 37 | if flake8_status != 0: 38 | print("\nERROR! Fix flake8 issues before publishing to PyPI!\n") 39 | sys.exit() 40 | else: 41 | print("*** No flake8 issues detected. Continuing...") 42 | print("\n*** Removing existing distribution packages: ***\n") 43 | os.system("rm -f dist/*.egg; rm -f dist/*.tar.gz; rm -f dist/*.whl") 44 | os.system("rm -rf build/bdist.*; rm -rf build/lib") 45 | print("\n*** Installing build: *** (Required for PyPI uploads)\n") 46 | os.system("python -m pip install --upgrade 'build'") 47 | print("\n*** Installing pkginfo: *** (Required for PyPI uploads)\n") 48 | os.system("python -m pip install --upgrade 'pkginfo'") 49 | print("\n*** Installing readme-renderer: *** (For PyPI uploads)\n") 50 | os.system("python -m pip install --upgrade 'readme-renderer'") 51 | print("\n*** Installing jaraco.classes: *** (For PyPI uploads)\n") 52 | os.system("python -m pip install --upgrade 'jaraco.classes'") 53 | print("\n*** Installing more-itertools: *** (For PyPI uploads)\n") 54 | os.system("python -m pip install --upgrade 'more-itertools'") 55 | print("\n*** Installing zipp: *** (Required for PyPI uploads)\n") 56 | os.system("python -m pip install --upgrade 'zipp'") 57 | print("\n*** Installing importlib-metadata: *** (For PyPI uploads)\n") 58 | os.system("python -m pip install --upgrade 'importlib-metadata'") 59 | print("\n*** Installing keyring, requests-toolbelt: *** (For PyPI)\n") 60 | os.system("python -m pip install --upgrade keyring requests-toolbelt") 61 | print("\n*** Installing twine: *** (Required for PyPI uploads)\n") 62 | os.system("python -m pip install --upgrade 'twine'") 63 | print("\n*** Rebuilding distribution packages: ***\n") 64 | os.system("python -m build") # Create new tar/wheel 65 | print("\n*** Publishing The Release to PyPI: ***\n") 66 | os.system("python -m twine upload dist/*") # Requires ~/.pypirc Keys 67 | print("\n*** The Release was PUBLISHED SUCCESSFULLY to PyPI! :) ***\n") 68 | else: 69 | print("\n>>> The Release was NOT PUBLISHED to PyPI! <<<\n") 70 | sys.exit() 71 | 72 | setup( 73 | name="pdbp", 74 | version="1.8.1", 75 | description="pdbp (Pdb+): A drop-in replacement for pdb and pdbpp.", 76 | long_description=long_description, 77 | long_description_content_type="text/markdown", 78 | keywords="pdb debugger tab color completion", 79 | url="https://github.com/mdmintz/pdbp", 80 | project_urls={ 81 | "Changelog": "https://github.com/mdmintz/pdbp/releases", 82 | "Download": "https://pypi.org/project/pdbp/#files", 83 | "Bug Tracker": "https://github.com/mdmintz/pdbp/issues", 84 | "PyPI": "https://pypi.org/project/pdbp/", 85 | "Source": "https://github.com/mdmintz/pdbp", 86 | }, 87 | py_modules=["pdbp"], 88 | package_dir={"": "src"}, 89 | platforms=["Windows", "Linux", "Mac OS-X"], 90 | author="Michael Mintz", 91 | author_email="mdmintz@gmail.com", 92 | maintainer="Michael Mintz", 93 | license="PSF", 94 | classifiers=[ 95 | "Development Status :: 5 - Production/Stable", 96 | "Environment :: Console", 97 | "Environment :: MacOS X", 98 | "Environment :: Win32 (MS Windows)", 99 | "Intended Audience :: Developers", 100 | "Operating System :: MacOS :: MacOS X", 101 | "Operating System :: Microsoft :: Windows", 102 | "Operating System :: POSIX :: Linux", 103 | "Programming Language :: Python", 104 | "Programming Language :: Python :: 3", 105 | "Programming Language :: Python :: 3.8", 106 | "Programming Language :: Python :: 3.9", 107 | "Programming Language :: Python :: 3.10", 108 | "Programming Language :: Python :: 3.11", 109 | "Programming Language :: Python :: 3.12", 110 | "Programming Language :: Python :: 3.13", 111 | "Programming Language :: Python :: 3.14", 112 | "Programming Language :: Python :: Implementation :: CPython", 113 | "Programming Language :: Python :: Implementation :: PyPy", 114 | "Topic :: Scientific/Engineering", 115 | "Topic :: Software Development", 116 | "Topic :: Software Development :: Debuggers", 117 | "Topic :: Software Development :: Libraries", 118 | "Topic :: Software Development :: Quality Assurance", 119 | "Topic :: Software Development :: Testing", 120 | "Topic :: Utilities", 121 | ], 122 | python_requires=">=3.8", 123 | install_requires=[ 124 | "pygments>=2.19.2", 125 | "tabcompleter>=1.4.0", 126 | 'colorama>=0.4.6;platform_system=="Windows"', 127 | ], 128 | setup_requires=[], 129 | include_package_data=True, 130 | ) 131 | 132 | print("\n*** pdbp (Pdb+) Installation Complete! ***\n") 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | pdbp - Adapted from the CPython 3.11 pdb.py code. 2 | Copyright (c) 2001-2023 Python Software Foundation; All Rights Reserved. 3 | 4 | 5 | Key changes: pdb becomes a multi-line debugger with syntax-highlighting. 6 | (To replace "pdb", add "import pdbp" to an "__init__.py" file.) 7 | 8 | 9 | A. HISTORY OF THE SOFTWARE 10 | ========================== 11 | 12 | Python was created in the early 1990s by Guido van Rossum at Stichting 13 | Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands 14 | as a successor of a language called ABC. Guido remains Python's 15 | principal author, although it includes many contributions from others. 16 | 17 | In 1995, Guido continued his work on Python at the Corporation for 18 | National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) 19 | in Reston, Virginia where he released several versions of the 20 | software. 21 | 22 | In May 2000, Guido and the Python core development team moved to 23 | BeOpen.com to form the BeOpen PythonLabs team. In October of the same 24 | year, the PythonLabs team moved to Digital Creations, which became 25 | Zope Corporation. In 2001, the Python Software Foundation (PSF, see 26 | https://www.python.org/psf/) was formed, a non-profit organization 27 | created specifically to own Python-related Intellectual Property. 28 | Zope Corporation was a sponsoring member of the PSF. 29 | 30 | All Python releases are Open Source (see https://opensource.org for 31 | the Open Source Definition). Historically, most, but not all, Python 32 | releases have also been GPL-compatible; the table below summarizes 33 | the various releases. 34 | 35 | Release Derived Year Owner GPL- 36 | from compatible? (1) 37 | 38 | 0.9.0 thru 1.2 1991-1995 CWI yes 39 | 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 40 | 1.6 1.5.2 2000 CNRI no 41 | 2.0 1.6 2000 BeOpen.com no 42 | 1.6.1 1.6 2001 CNRI yes (2) 43 | 2.1 2.0+1.6.1 2001 PSF no 44 | 2.0.1 2.0+1.6.1 2001 PSF yes 45 | 2.1.1 2.1+2.0.1 2001 PSF yes 46 | 2.1.2 2.1.1 2002 PSF yes 47 | 2.1.3 2.1.2 2002 PSF yes 48 | 2.2 and above 2.1.1 2001-now PSF yes 49 | 50 | Footnotes: 51 | 52 | (1) GPL-compatible doesn't mean that we're distributing Python under 53 | the GPL. All Python licenses, unlike the GPL, let you distribute 54 | a modified version without making your changes open source. The 55 | GPL-compatible licenses make it possible to combine Python with 56 | other software that is released under the GPL; the others don't. 57 | 58 | (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, 59 | because its license has a choice of law clause. According to 60 | CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 61 | is "not incompatible" with the GPL. 62 | 63 | Thanks to the many outside volunteers who have worked under Guido's 64 | direction to make these releases possible. 65 | 66 | 67 | B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON 68 | =============================================================== 69 | 70 | Python software and documentation are licensed under the 71 | Python Software Foundation License Version 2. 72 | 73 | Starting with Python 3.8.6, examples, recipes, and other code in 74 | the documentation are dual licensed under the PSF License Version 2 75 | and the Zero-Clause BSD license. 76 | 77 | Some software incorporated into Python is under different licenses. 78 | The licenses are listed with code falling under that license. 79 | 80 | 81 | PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 82 | -------------------------------------------- 83 | 84 | 1. This LICENSE AGREEMENT is between the Python Software Foundation 85 | ("PSF"), and the Individual or Organization ("Licensee") accessing and 86 | otherwise using this software ("Python") in source or binary form and 87 | its associated documentation. 88 | 89 | 2. Subject to the terms and conditions of this License Agreement, PSF hereby 90 | grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, 91 | analyze, test, perform and/or display publicly, prepare derivative works, 92 | distribute, and otherwise use Python alone or in any derivative version, 93 | provided, however, that PSF's License Agreement and PSF's notice of copyright, 94 | i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 95 | 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; 96 | All Rights Reserved" are retained in Python alone or in any derivative version 97 | prepared by Licensee. 98 | 99 | 3. In the event Licensee prepares a derivative work that is based on 100 | or incorporates Python or any part thereof, and wants to make 101 | the derivative work available to others as provided herein, then 102 | Licensee hereby agrees to include in any such work a brief summary of 103 | the changes made to Python. 104 | 105 | 4. PSF is making Python available to Licensee on an "AS IS" 106 | basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 107 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND 108 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 109 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT 110 | INFRINGE ANY THIRD PARTY RIGHTS. 111 | 112 | 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 113 | FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 114 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, 115 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 116 | 117 | 6. This License Agreement will automatically terminate upon a material 118 | breach of its terms and conditions. 119 | 120 | 7. Nothing in this License Agreement shall be deemed to create any 121 | relationship of agency, partnership, or joint venture between PSF and 122 | Licensee. This License Agreement does not grant permission to use PSF 123 | trademarks or trade name in a trademark sense to endorse or promote 124 | products or services of Licensee, or any third party. 125 | 126 | 8. By copying, installing or otherwise using Python, Licensee 127 | agrees to be bound by the terms and conditions of this License 128 | Agreement. 129 | 130 | 131 | BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 132 | ------------------------------------------- 133 | 134 | BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 135 | 136 | 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an 137 | office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the 138 | Individual or Organization ("Licensee") accessing and otherwise using 139 | this software in source or binary form and its associated 140 | documentation ("the Software"). 141 | 142 | 2. Subject to the terms and conditions of this BeOpen Python License 143 | Agreement, BeOpen hereby grants Licensee a non-exclusive, 144 | royalty-free, world-wide license to reproduce, analyze, test, perform 145 | and/or display publicly, prepare derivative works, distribute, and 146 | otherwise use the Software alone or in any derivative version, 147 | provided, however, that the BeOpen Python License is retained in the 148 | Software, alone or in any derivative version prepared by Licensee. 149 | 150 | 3. BeOpen is making the Software available to Licensee on an "AS IS" 151 | basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 152 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND 153 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 154 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT 155 | INFRINGE ANY THIRD PARTY RIGHTS. 156 | 157 | 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE 158 | SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS 159 | AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY 160 | DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 161 | 162 | 5. This License Agreement will automatically terminate upon a material 163 | breach of its terms and conditions. 164 | 165 | 6. This License Agreement shall be governed by and interpreted in all 166 | respects by the law of the State of California, excluding conflict of 167 | law provisions. Nothing in this License Agreement shall be deemed to 168 | create any relationship of agency, partnership, or joint venture 169 | between BeOpen and Licensee. This License Agreement does not grant 170 | permission to use BeOpen trademarks or trade names in a trademark 171 | sense to endorse or promote products or services of Licensee, or any 172 | third party. As an exception, the "BeOpen Python" logos available at 173 | http://www.pythonlabs.com/logos.html may be used according to the 174 | permissions granted on that web page. 175 | 176 | 7. By copying, installing or otherwise using the software, Licensee 177 | agrees to be bound by the terms and conditions of this License 178 | Agreement. 179 | 180 | 181 | CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 182 | --------------------------------------- 183 | 184 | 1. This LICENSE AGREEMENT is between the Corporation for National 185 | Research Initiatives, having an office at 1895 Preston White Drive, 186 | Reston, VA 20191 ("CNRI"), and the Individual or Organization 187 | ("Licensee") accessing and otherwise using Python 1.6.1 software in 188 | source or binary form and its associated documentation. 189 | 190 | 2. Subject to the terms and conditions of this License Agreement, CNRI 191 | hereby grants Licensee a nonexclusive, royalty-free, world-wide 192 | license to reproduce, analyze, test, perform and/or display publicly, 193 | prepare derivative works, distribute, and otherwise use Python 1.6.1 194 | alone or in any derivative version, provided, however, that CNRI's 195 | License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 196 | 1995-2001 Corporation for National Research Initiatives; All Rights 197 | Reserved" are retained in Python 1.6.1 alone or in any derivative 198 | version prepared by Licensee. Alternately, in lieu of CNRI's License 199 | Agreement, Licensee may substitute the following text (omitting the 200 | quotes): "Python 1.6.1 is made available subject to the terms and 201 | conditions in CNRI's License Agreement. This Agreement together with 202 | Python 1.6.1 may be located on the internet using the following 203 | unique, persistent identifier (known as a handle): 1895.22/1013. This 204 | Agreement may also be obtained from a proxy server on the internet 205 | using the following URL: http://hdl.handle.net/1895.22/1013". 206 | 207 | 3. In the event Licensee prepares a derivative work that is based on 208 | or incorporates Python 1.6.1 or any part thereof, and wants to make 209 | the derivative work available to others as provided herein, then 210 | Licensee hereby agrees to include in any such work a brief summary of 211 | the changes made to Python 1.6.1. 212 | 213 | 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" 214 | basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 215 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND 216 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 217 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT 218 | INFRINGE ANY THIRD PARTY RIGHTS. 219 | 220 | 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 221 | 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 222 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, 223 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 224 | 225 | 6. This License Agreement will automatically terminate upon a material 226 | breach of its terms and conditions. 227 | 228 | 7. This License Agreement shall be governed by the federal 229 | intellectual property law of the United States, including without 230 | limitation the federal copyright law, and, to the extent such 231 | U.S. federal law does not apply, by the law of the Commonwealth of 232 | Virginia, excluding Virginia's conflict of law provisions. 233 | Notwithstanding the foregoing, with regard to derivative works based 234 | on Python 1.6.1 that incorporate non-separable material that was 235 | previously distributed under the GNU General Public License (GPL), the 236 | law of the Commonwealth of Virginia shall govern this License 237 | Agreement only as to issues arising under or with respect to 238 | Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this 239 | License Agreement shall be deemed to create any relationship of 240 | agency, partnership, or joint venture between CNRI and Licensee. This 241 | License Agreement does not grant permission to use CNRI trademarks or 242 | trade name in a trademark sense to endorse or promote products or 243 | services of Licensee, or any third party. 244 | 245 | 8. By clicking on the "ACCEPT" button where indicated, or by copying, 246 | installing or otherwise using Python 1.6.1, Licensee agrees to be 247 | bound by the terms and conditions of this License Agreement. 248 | 249 | ACCEPT 250 | 251 | 252 | CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 253 | -------------------------------------------------- 254 | 255 | Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, 256 | The Netherlands. All rights reserved. 257 | 258 | Permission to use, copy, modify, and distribute this software and its 259 | documentation for any purpose and without fee is hereby granted, 260 | provided that the above copyright notice appear in all copies and that 261 | both that copyright notice and this permission notice appear in 262 | supporting documentation, and that the name of Stichting Mathematisch 263 | Centrum or CWI not be used in advertising or publicity pertaining to 264 | distribution of the software without specific, written prior 265 | permission. 266 | 267 | STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO 268 | THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 269 | FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE 270 | FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 271 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 272 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 273 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 274 | 275 | ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION 276 | ---------------------------------------------------------------------- 277 | 278 | Permission to use, copy, modify, and/or distribute this software for any 279 | purpose with or without fee is hereby granted. 280 | 281 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 282 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 283 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 284 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 285 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 286 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 287 | PERFORMANCE OF THIS SOFTWARE. 288 | -------------------------------------------------------------------------------- /src/pdbp.py: -------------------------------------------------------------------------------- 1 | """ 2 | pdbp (Pdb+): A drop-in replacement for pdb and pdbpp. 3 | ===================================================== 4 | """ 5 | import code 6 | import codecs 7 | import inspect 8 | import math 9 | import os 10 | import pprint 11 | import re 12 | import shutil 13 | import signal 14 | import sys 15 | import traceback 16 | import types 17 | from collections import OrderedDict 18 | from inspect import signature 19 | from io import StringIO 20 | from tabcompleter import Completer, ConfigurableClass, Color 21 | import tabcompleter 22 | 23 | __url__ = "https://github.com/mdmintz/pdbp" 24 | __version__ = tabcompleter.LazyVersion("pdbp") 25 | run_from_main = False 26 | 27 | # Digits, Letters, [], or Dots 28 | side_effects_free = re.compile(r"^ *[_0-9a-zA-Z\[\].]* *$") 29 | 30 | 31 | def import_from_stdlib(name): 32 | result = types.ModuleType(name) 33 | stdlibdir, _ = os.path.split(code.__file__) 34 | pyfile = os.path.join(stdlibdir, name + ".py") 35 | with open(pyfile) as f: 36 | src = f.read() 37 | co_module = compile(src, pyfile, "exec", dont_inherit=True) 38 | exec(co_module, result.__dict__) 39 | return result 40 | 41 | 42 | pdb = import_from_stdlib("pdb") 43 | 44 | 45 | def rebind_globals(func, newglobals): 46 | newfunc = types.FunctionType(func.__code__, newglobals, func.__name__, 47 | func.__defaults__, func.__closure__) 48 | return newfunc 49 | 50 | 51 | def is_char_wide(char): 52 | # Returns True if the char is Chinese, Japanese, Korean, or another double. 53 | special_c_r = [ 54 | {"from": ord("\u4e00"), "to": ord("\u9FFF")}, 55 | {"from": ord("\u3040"), "to": ord("\u30ff")}, 56 | {"from": ord("\uac00"), "to": ord("\ud7a3")}, 57 | {"from": ord("\uff01"), "to": ord("\uff60")}, 58 | ] 59 | sc = any( 60 | [range["from"] <= ord(char) <= range["to"] for range in special_c_r] 61 | ) 62 | return sc 63 | 64 | 65 | def get_width(line): 66 | # Return the true width of the line. Not the same as line length. 67 | # Chinese/Japanese/Korean characters take up two spaces of width. 68 | line_length = len(line) 69 | for char in line: 70 | if is_char_wide(char): 71 | line_length += 1 72 | return line_length 73 | 74 | 75 | def set_line_width(line, width, tll=True): 76 | """Trim line if too long. Fill line if too short. Return line.""" 77 | line_width = get_width(line) 78 | new_line = "" 79 | width = int(width) 80 | if width <= 0: 81 | return new_line 82 | elif line_width == width: 83 | return line 84 | elif line_width < width: 85 | new_line = line 86 | else: 87 | for char in line: 88 | updated_line = "%s%s" % (new_line, char) 89 | if get_width(updated_line) > width: 90 | break 91 | new_line = updated_line 92 | extra_spaces = "" 93 | if tll: 94 | extra_spaces = " " * (width - get_width(new_line)) 95 | return "%s%s" % (new_line, extra_spaces) 96 | 97 | 98 | def get_terminal_size(): 99 | if "linux" in sys.platform: 100 | return shutil.get_terminal_size((80, 20)) 101 | try: 102 | return os.get_terminal_size() 103 | except Exception: 104 | return shutil.get_terminal_size((80, 20)) 105 | 106 | 107 | class DefaultConfig(object): 108 | if "win32" in sys.platform: 109 | import colorama 110 | colorama.just_fix_windows_console() 111 | prompt = "(Pdb+) " 112 | highlight = True 113 | sticky_by_default = True 114 | bg = "dark" 115 | use_pygments = True 116 | colorscheme = None 117 | use_terminal256formatter = None # Defaults to `"256color" in $TERM`. 118 | editor = "${EDITOR:-vi}" # Use $EDITOR if set; else default to vi. 119 | stdin_paste = None 120 | exec_if_unfocused = None # This option was removed! 121 | truncate_long_lines = False 122 | shorten_path = True 123 | disable_pytest_capturing = True 124 | enable_hidden_frames = False 125 | show_hidden_frames_count = False 126 | encodings = ("utf-8", "latin-1") 127 | filename_color = Color.fuchsia 128 | line_number_color = Color.turquoise 129 | regular_stack_color = Color.yellow 130 | pm_stack_color = Color.red 131 | stack_color = regular_stack_color 132 | # https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit 133 | return_value_color = "90;1" # Gray 134 | pm_return_value_color = "31;1" # Red (Post Mortem failure) 135 | num_return_value_color = "95;1" # Bright Magenta (numbers) 136 | true_return_value_color = "32;1" # Green 137 | false_return_value_color = "33;1" # Yellow (red was taken) 138 | none_return_value_color = "33;1" # Yellow (same as False) 139 | regular_line_color = "97;44;1" # White on Blue (Old: "39;49;7") 140 | pm_cur_line_color = "97;41;1" # White on Red (Post Mortem Color) 141 | exc_line_color = "31;103;1" # Red on Yellow (Exception-handling) 142 | current_line_color = regular_line_color 143 | exception_caught = False 144 | last_return_color = None 145 | show_traceback_on_error = True 146 | show_traceback_on_error_limit = None 147 | default_pdb_kwargs = { 148 | } 149 | 150 | def setup(self, pdb): 151 | pass 152 | 153 | def before_interaction_hook(self, pdb): 154 | pass 155 | 156 | 157 | def setbgcolor(line, color): 158 | # Add a bgcolor attribute to all escape sequences found. 159 | setbg = "\x1b[%sm" % color 160 | regexbg = "\\1;%sm" % color 161 | result = setbg + re.sub("(\x1b\\[.*?)m", regexbg, line) + "\x1b[00m" 162 | if os.environ.get("TERM") == "eterm-color": 163 | result = result.replace(setbg, "\x1b[37;%dm" % color) 164 | result = result.replace("\x1b[00;%dm" % color, "\x1b[37;%dm" % color) 165 | result = result.replace("\x1b[39;49;00;", "\x1b[37;") 166 | return result 167 | 168 | 169 | CLEARSCREEN = "\033[2J\033[1;1H" 170 | 171 | 172 | def lasti2lineno(code, lasti): 173 | import dis 174 | linestarts = list(dis.findlinestarts(code)) 175 | linestarts.reverse() 176 | for i, lineno in linestarts: 177 | if lasti >= i: 178 | return lineno 179 | return 0 180 | 181 | 182 | class Restart(Exception): 183 | pass 184 | 185 | 186 | class Undefined: 187 | def __repr__(self): 188 | return "" 189 | 190 | 191 | undefined = Undefined() 192 | 193 | 194 | class Pdb(pdb.Pdb, ConfigurableClass, object): 195 | DefaultConfig = DefaultConfig 196 | config_filename = ".pdbrc.py" 197 | 198 | def __init__(self, *args, **kwds): 199 | self.ConfigFactory = kwds.pop("Config", None) 200 | self.start_lineno = kwds.pop("start_lineno", None) 201 | self.start_filename = kwds.pop("start_filename", None) 202 | self.config = self.get_config(self.ConfigFactory) 203 | self.config.setup(self) 204 | if self.config.disable_pytest_capturing: 205 | self._disable_pytest_capture_maybe() 206 | kwargs = self.config.default_pdb_kwargs.copy() 207 | kwargs.update(**kwds) 208 | super().__init__(*args, **kwargs) 209 | self.prompt = self.config.prompt 210 | self.display_list = {} # frame --> (name --> last seen value) 211 | self.sticky = self.config.sticky_by_default 212 | self.first_time_sticky = self.sticky 213 | self.ok_to_clear = False 214 | self.has_traceback = False 215 | self.sticky_ranges = {} # frame --> (start, end) 216 | self.tb_lineno = {} # frame --> lineno where the exception was raised 217 | self.history = [] 218 | self.show_hidden_frames = False 219 | self._hidden_frames = [] 220 | self.stdout = self.ensure_file_can_write_unicode(self.stdout) 221 | self.saved_curframe = None 222 | self.last_cmd = None 223 | 224 | def _runmodule(self, module_name): 225 | import __main__ 226 | import runpy 227 | self._wait_for_mainpyfile = True 228 | self._user_requested_quit = False 229 | mod_name, mod_spec, code = runpy._get_module_details(module_name) 230 | self.mainpyfile = self.canonic(code.co_filename) 231 | __main__.__dict__.clear() 232 | __main__.__dict__.update( 233 | { 234 | "__name__": "__main__", 235 | "__file__": self.mainpyfile, 236 | "__package__": mod_spec.parent, 237 | "__loader__": mod_spec.loader, 238 | "__spec__": mod_spec, 239 | "__builtins__": __builtins__, 240 | } 241 | ) 242 | self.run(code) 243 | 244 | def _runscript(self, filename): 245 | import __main__ 246 | import io 247 | __main__.__dict__.clear() 248 | __main__.__dict__.update( 249 | { 250 | "__name__": "__main__", 251 | "__file__": filename, 252 | "__builtins__": __builtins__, 253 | } 254 | ) 255 | self._wait_for_mainpyfile = True 256 | self.mainpyfile = self.canonic(filename) 257 | self._user_requested_quit = False 258 | with io.open_code(filename) as fp: 259 | statement = ( 260 | "exec(compile(%r, %r, 'exec'))" % (fp.read(), self.mainpyfile) 261 | ) 262 | self.run(statement) 263 | 264 | def ensure_file_can_write_unicode(self, f): 265 | # Wrap with an encoder, but only if not already wrapped. 266 | if (not hasattr(f, "stream") 267 | and getattr(f, "encoding", False) 268 | and f.encoding.lower() != "utf-8"): 269 | f = codecs.getwriter("utf-8")(getattr(f, "buffer", f)) 270 | return f 271 | 272 | def _disable_pytest_capture_maybe(self): 273 | try: 274 | import pytest 275 | import _pytest 276 | pytest.Config 277 | _pytest.config 278 | except (ImportError, AttributeError): 279 | return # pytest is not installed 280 | try: 281 | capman = _pytest.capture.CaptureManager("global") 282 | capman.stop_global_capturing() 283 | except (KeyError, AttributeError, Exception): 284 | pass 285 | 286 | def interaction(self, frame, traceback): 287 | # Restore the previous signal handler at the Pdb+ prompt. 288 | if getattr(pdb.Pdb, "_previous_sigint_handler", None): 289 | try: 290 | signal.signal(signal.SIGINT, pdb.Pdb._previous_sigint_handler) 291 | except ValueError: # ValueError: signal only works in main thread 292 | pass 293 | else: 294 | pdb.Pdb._previous_sigint_handler = None 295 | ret = None 296 | if not isinstance(traceback, BaseException): 297 | ret = self.setup(frame, traceback) 298 | if ret: 299 | self.forget() 300 | return 301 | if self.config.exec_if_unfocused: 302 | pass # This option was removed! 303 | if ( 304 | self.has_traceback 305 | and not traceback 306 | and self.config.exception_caught 307 | ): 308 | # The exception was caught, so no post mortem debug mode. 309 | self.has_traceback = False 310 | self.config.stack_color = self.config.regular_stack_color 311 | self.config.current_line_color = self.config.regular_line_color 312 | if traceback or not self.sticky or self.first_time_sticky: 313 | if traceback: 314 | self.has_traceback = True 315 | self.config.stack_color = self.config.pm_stack_color 316 | self.config.current_line_color = self.config.pm_cur_line_color 317 | if not self.sticky: 318 | print(file=self.stdout) 319 | if not self.first_time_sticky: 320 | self.print_stack_entry(self.stack[self.curindex]) 321 | self.print_hidden_frames_count() 322 | if self.sticky: 323 | if not traceback: 324 | self.stdout.write(CLEARSCREEN) 325 | else: 326 | print(file=self.stdout, end="\n\033[F") 327 | completer = tabcompleter.setup() 328 | completer.config.readline.set_completer(self.complete) 329 | if isinstance(traceback, BaseException): 330 | return super().interaction(frame, traceback) 331 | self.config.before_interaction_hook(self) 332 | # Use _cmdloop on Python3, which catches KeyboardInterrupt. 333 | if hasattr(self, "_cmdloop"): 334 | self._cmdloop() 335 | else: 336 | self.cmdloop() 337 | self.forget() 338 | 339 | def print_hidden_frames_count(self): 340 | n = len(self._hidden_frames) 341 | if n and self.config.show_hidden_frames_count: 342 | plural = n > 1 and "s" or "" 343 | print( 344 | ' %d frame%s hidden (Use "u" and "d" to travel)' 345 | % (n, plural), 346 | file=self.stdout, 347 | ) 348 | 349 | def setup(self, frame, tb): 350 | ret = super().setup(frame, tb) 351 | if not ret: 352 | while tb: 353 | lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti) 354 | self.tb_lineno[tb.tb_frame] = lineno 355 | tb = tb.tb_next 356 | return ret 357 | 358 | def _is_hidden(self, frame): 359 | if not self.config.enable_hidden_frames: 360 | return False 361 | # Decorated code is always considered to be hidden. 362 | consts = frame.f_code.co_consts 363 | if consts and consts[-1] is _HIDE_FRAME: 364 | return True 365 | # Don't hide if this frame contains the initial set_trace. 366 | if frame is getattr(self, "_via_set_trace_frame", None): 367 | return False 368 | if frame.f_globals.get("__unittest"): 369 | return True 370 | if ( 371 | frame.f_locals.get("__tracebackhide__") 372 | or frame.f_globals.get("__tracebackhide__") 373 | ): 374 | return True 375 | 376 | def get_stack(self, f, t): 377 | # Show all the frames except ones that should be hidden. 378 | fullstack, idx = super().get_stack(f, t) 379 | self.fullstack = fullstack 380 | return self.compute_stack(fullstack, idx) 381 | 382 | def compute_stack(self, fullstack, idx=None): 383 | if idx is None: 384 | idx = len(fullstack) - 1 385 | if self.show_hidden_frames: 386 | return fullstack, idx 387 | self._hidden_frames = [] 388 | newstack = [] 389 | for frame, lineno in fullstack: 390 | if self._is_hidden(frame): 391 | self._hidden_frames.append((frame, lineno)) 392 | else: 393 | newstack.append((frame, lineno)) 394 | newidx = idx - len(self._hidden_frames) 395 | return newstack, newidx 396 | 397 | def refresh_stack(self): 398 | self.stack, _ = self.compute_stack(self.fullstack) 399 | # Find the current frame in the new stack. 400 | for i, (frame, _) in enumerate(self.stack): 401 | if frame is self.curframe: 402 | self.curindex = i 403 | break 404 | else: 405 | self.curindex = len(self.stack) - 1 406 | self.curframe = self.stack[-1][0] 407 | self.print_current_stack_entry() 408 | 409 | def forget(self): 410 | if not hasattr(self, "lineno"): 411 | # Only forget if not used with recursive set_trace. 412 | super().forget() 413 | self.raise_lineno = {} 414 | 415 | @classmethod 416 | def _get_all_completions(cls, complete, text): 417 | r = [] 418 | i = 0 419 | while True: 420 | comp = complete(text, i) 421 | if comp is None: 422 | break 423 | i += 1 424 | r.append(comp) 425 | return r 426 | 427 | def complete(self, text, state): 428 | """Handle completions from tabcompleter and the original pdb.""" 429 | if state == 0: 430 | if GLOBAL_PDB: 431 | GLOBAL_PDB._pdbp_completing = True 432 | mydict = self.curframe.f_globals.copy() 433 | mydict.update(self.curframe.f_locals) 434 | completer = Completer(mydict) 435 | self._completions = self._get_all_completions( 436 | completer.complete, text 437 | ) 438 | if not self._completions: 439 | real_pdb = super() 440 | for x in self._get_all_completions(real_pdb.complete, text): 441 | if x not in self._completions: 442 | self._completions.append(x) 443 | if GLOBAL_PDB: 444 | del GLOBAL_PDB._pdbp_completing 445 | # Remove "\t" from tabcompleter if there are pdb completions. 446 | if len(self._completions) > 1 and self._completions[0] == "\t": 447 | self._completions.pop(0) 448 | try: 449 | return self._completions[state] 450 | except IndexError: 451 | return None 452 | 453 | def _init_pygments(self): 454 | if not self.config.use_pygments: 455 | return False 456 | if hasattr(self, "_fmt"): 457 | return True 458 | try: 459 | from pygments.lexers import PythonLexer 460 | from pygments.formatters import TerminalFormatter 461 | from pygments.formatters import Terminal256Formatter 462 | except ImportError: 463 | return False 464 | if hasattr(self.config, "formatter"): 465 | self._fmt = self.config.formatter 466 | else: 467 | if (self.config.use_terminal256formatter 468 | or (self.config.use_terminal256formatter is None 469 | and "256color" in os.environ.get("TERM", ""))): 470 | Formatter = Terminal256Formatter 471 | else: 472 | Formatter = TerminalFormatter 473 | self._fmt = Formatter(bg=self.config.bg, 474 | colorscheme=self.config.colorscheme) 475 | self._lexer = PythonLexer() 476 | return True 477 | 478 | stack_entry_regexp = re.compile(r"(.*?)\(([0-9]+?)\)(.*)", re.DOTALL) 479 | 480 | def format_stack_entry(self, frame_lineno, lprefix=": "): 481 | entry = super().format_stack_entry(frame_lineno, lprefix) 482 | entry = self.try_to_decode(entry) 483 | if self.config.highlight: 484 | match = self.stack_entry_regexp.match(entry) 485 | if match: 486 | filename, lineno, other = match.groups() 487 | other = self.format_source(other.rstrip()).rstrip() 488 | filename = Color.set(self.config.filename_color, filename) 489 | lineno = Color.set(self.config.line_number_color, lineno) 490 | entry = "%s(%s)%s" % (filename, lineno, other) 491 | return entry 492 | 493 | def try_to_decode(self, s): 494 | for encoding in self.config.encodings: 495 | try: 496 | return s.decode(encoding) 497 | except (UnicodeDecodeError, AttributeError): 498 | pass 499 | return s 500 | 501 | def format_source(self, src): 502 | if not self._init_pygments(): 503 | return src 504 | from pygments import highlight 505 | src = self.try_to_decode(src) 506 | return highlight(src, self._lexer, self._fmt) 507 | 508 | def format_line(self, lineno, marker, line): 509 | lineno = "%4d" % lineno 510 | if self.config.highlight: 511 | lineno = Color.set(self.config.line_number_color, lineno) 512 | line = "%s %2s %s" % (lineno, marker, line) 513 | if self.config.highlight and marker == "->": 514 | if self.config.current_line_color: 515 | line = setbgcolor(line, self.config.current_line_color) 516 | elif self.config.highlight and marker == ">>": 517 | if self.config.exc_line_color: 518 | line = setbgcolor(line, self.config.exc_line_color) 519 | return line 520 | 521 | def parseline(self, line): 522 | if line.startswith("!!"): 523 | line = line[2:] 524 | return super().parseline(line) 525 | cmd, arg, newline = super().parseline(line) 526 | if arg and arg.endswith("?"): 527 | if hasattr(self, "do_" + cmd): 528 | cmd, arg = ("help", cmd) 529 | elif arg.endswith("??"): 530 | arg = cmd + arg.split("?")[0] 531 | cmd = "source" 532 | self.do_inspect(arg) 533 | self.stdout.write("%-28s\n" % Color.set(Color.red, "Source:")) 534 | else: 535 | arg = cmd + arg.split("?")[0] 536 | cmd = "inspect" 537 | return cmd, arg, newline 538 | if ( 539 | cmd == "f" 540 | and len(newline) > 1 541 | and (newline[1] == "'" or newline[1] == '"') 542 | ): 543 | return super().parseline("!" + line) 544 | 545 | if ( 546 | cmd 547 | and hasattr(self, "do_" + cmd) 548 | and ( 549 | cmd in self.curframe.f_globals 550 | or cmd in self.curframe.f_locals 551 | or arg.startswith("=") 552 | ) 553 | ): 554 | return super().parseline("!" + line) 555 | 556 | if cmd == "list" and arg.startswith("("): 557 | line = "!" + line 558 | return super().parseline(line) 559 | 560 | return cmd, arg, newline 561 | 562 | def do_inspect(self, arg): 563 | if not arg: 564 | print('Inspect Usage: "inspect "', file=self.stdout) 565 | print( 566 | "Local variables: %r" % self.curframe_locals.keys(), 567 | file=self.stdout, 568 | ) 569 | return 570 | try: 571 | obj = self._getval(arg) 572 | except Exception: 573 | print( 574 | 'See "locals()" or "globals()" for available args!', 575 | file=self.stdout, 576 | ) 577 | return 578 | data = OrderedDict() 579 | data["Type"] = type(obj).__name__ 580 | data["String Form"] = str(obj).strip() 581 | try: 582 | data["Length"] = len(obj) 583 | except TypeError: 584 | pass 585 | try: 586 | data["File"] = inspect.getabsfile(obj) 587 | except TypeError: 588 | pass 589 | if ( 590 | isinstance(obj, type) 591 | and hasattr(obj, "__init__") 592 | and getattr(obj, "__module__") != "__builtin__" 593 | ): 594 | data["Docstring"] = obj.__doc__ 595 | data["Constructor information"] = "" 596 | try: 597 | data[" Definition"] = "%s%s" % (arg, signature(obj)) 598 | except ValueError: 599 | pass 600 | data[" Docstring"] = obj.__init__.__doc__ 601 | else: 602 | try: 603 | data["Definition"] = "%s%s" % (arg, signature(obj)) 604 | except (TypeError, ValueError): 605 | pass 606 | data["Docstring"] = obj.__doc__ 607 | for key, value in data.items(): 608 | formatted_key = Color.set(Color.red, key + ":") 609 | self.stdout.write("%-28s %s\n" % (formatted_key, value)) 610 | 611 | def default(self, line): 612 | self.history.append(line) 613 | return super().default(line) 614 | 615 | def do_help(self, arg): 616 | try: 617 | return super().do_help(arg) 618 | except AttributeError: 619 | print("*** No help for '{command}'".format(command=arg), 620 | file=self.stdout) 621 | do_help.__doc__ = pdb.Pdb.do_help.__doc__ 622 | 623 | def help_hidden_frames(self): 624 | print('Use "u" and "d" to travel up/down the stack.', file=self.stdout) 625 | 626 | def do_longlist(self, arg): 627 | self.last_cmd = self.lastcmd = "longlist" 628 | self.sticky = True 629 | self._print_if_sticky() 630 | do_ll = do_longlist 631 | 632 | def do_jump(self, arg): 633 | self.last_cmd = self.lastcmd = "jump" 634 | if self.curindex + 1 != len(self.stack): 635 | self.error("You can only jump within the bottom frame!") 636 | return 637 | try: 638 | arg = int(arg) 639 | except ValueError: 640 | self.error("The 'jump' command requires a line number!") 641 | else: 642 | try: 643 | self.curframe.f_lineno = arg 644 | self.stack[self.curindex] = self.stack[self.curindex][0], arg 645 | self.print_current_stack_entry() 646 | except ValueError as e: 647 | self.error('Jump failed: %s' % e) 648 | do_j = do_jump 649 | 650 | def _printlonglist(self, linerange=None, fnln=None, nc_fnln=""): 651 | try: 652 | if self.curframe.f_code.co_name == "": 653 | lines, _ = inspect.findsource(self.curframe) 654 | lineno = 1 655 | else: 656 | try: 657 | lines, lineno = inspect.getsourcelines(self.curframe) 658 | except Exception: 659 | print(file=self.stdout) 660 | self.sticky = False 661 | self.print_stack_entry(self.stack[self.curindex]) 662 | self.sticky = True 663 | print(file=self.stdout, end="\n\033[F") 664 | return 665 | except IOError as e: 666 | try: 667 | self.sticky = False 668 | self.print_stack_entry(self.stack[self.curindex]) 669 | self.sticky = True 670 | return 671 | except Exception: 672 | self.sticky = True 673 | print("** (%s) **" % e, file=self.stdout) 674 | return 675 | if linerange: 676 | start, end = linerange 677 | start = max(start, lineno) 678 | end = min(end, lineno + len(lines)) 679 | lines = lines[start - lineno:end - lineno] 680 | lineno = start 681 | self._print_lines_pdbp(lines, lineno, fnln=fnln, nc_fnln=nc_fnln) 682 | 683 | def _print_lines_pdbp( 684 | self, lines, lineno, print_markers=True, fnln=None, nc_fnln="" 685 | ): 686 | dots = "..." 687 | offset = 0 688 | try: 689 | lineno_int = int(lineno) 690 | except Exception: 691 | lineno = 1 692 | lineno_int = 1 693 | if lineno_int == 1: 694 | dots = "" 695 | elif lineno_int > 99999: 696 | dots = "......" 697 | elif lineno_int > 9999: 698 | dots = "....." 699 | elif lineno_int > 999: 700 | dots = "...." 701 | elif lineno_int > 99: 702 | dots = " ..." 703 | elif lineno_int > 9: 704 | dots = " .." 705 | else: 706 | dots = " ." 707 | max_line = int(lineno) + len(lines) - 1 708 | if max_line > 9999: 709 | offset = 1 710 | if max_line > 99999: 711 | offset = 2 712 | exc_lineno = self.tb_lineno.get(self.curframe, None) 713 | lines = [line.replace("\t", " ") 714 | for line in lines] # force tabs to 4 spaces 715 | lines = [line.rstrip() for line in lines] 716 | width, height = get_terminal_size() 717 | width = width - offset 718 | height = height - 1 719 | overflow = 0 720 | height_counter = height 721 | if not self.config.truncate_long_lines: 722 | for line in lines: 723 | if len(line) > width - 9: 724 | overflow += 1 725 | height_counter -= 1 726 | if height_counter <= 0: 727 | break 728 | if self.config.truncate_long_lines: 729 | maxlength = max(width - 9, 16) 730 | lines = [set_line_width(line, maxlength) for line in lines] 731 | else: 732 | maxlength = max(map(get_width, lines)) 733 | if self.config.highlight: 734 | # Fill line with spaces. This is important when a bg color is 735 | # is used for highlighting the current line (via setbgcolor). 736 | tll = self.config.truncate_long_lines 737 | lines = [set_line_width(line, maxlength, tll) for line in lines] 738 | src = self.format_source("\n".join(lines)) 739 | lines = src.splitlines() 740 | if height >= 6: 741 | last_marker_line = max( 742 | self.curframe.f_lineno, 743 | exc_lineno if exc_lineno else 0 744 | ) - lineno 745 | if last_marker_line >= 0: 746 | more_overflow = int(len(nc_fnln) / width) 747 | overflow = overflow + more_overflow 748 | maxlines = last_marker_line + (height * 2 // 3) 749 | maxlines = maxlines - math.ceil(overflow * 1 / 3) 750 | if len(lines) > maxlines: 751 | lines = lines[:maxlines] 752 | lines.append(Color.set("39;49;1", "...")) 753 | self.config.exception_caught = False 754 | for i, line in enumerate(lines): 755 | marker = "" 756 | if lineno == self.curframe.f_lineno and print_markers: 757 | marker = "->" 758 | elif lineno == exc_lineno and print_markers: 759 | marker = ">>" 760 | self.config.exception_caught = True 761 | lines[i] = self.format_line(lineno, marker, line) 762 | lineno += 1 763 | if self.ok_to_clear: 764 | self.stdout.write(CLEARSCREEN) 765 | if fnln: 766 | print(fnln, file=self.stdout) 767 | if int(lineno) > 1: 768 | num_color = self.config.line_number_color 769 | print(Color.set(num_color, dots), file=self.stdout) 770 | else: 771 | print(file=self.stdout) 772 | print("\n".join(lines), file=self.stdout, end="\n\n\033[F") 773 | 774 | def do_list(self, arg): 775 | try: 776 | import linecache 777 | y = 0 778 | if run_from_main: 779 | y = 6 780 | filename = self.curframe.f_code.co_filename 781 | lines = linecache.getlines(filename, self.curframe.f_globals) 782 | if ( 783 | not arg 784 | and ( 785 | (self.last_cmd == "list" and self.lineno >= len(lines) + y) 786 | or self.last_cmd != "list" 787 | or ( 788 | self.saved_curframe != self.curframe 789 | or self.lineno < self.curframe.f_lineno 790 | ) 791 | ) 792 | ): 793 | arg = "." # Go back to the active cursor point 794 | except Exception: 795 | pass 796 | self.last_cmd = self.lastcmd = "list" 797 | self.saved_curframe = self.curframe 798 | oldstdout = self.stdout 799 | self.stdout = StringIO() 800 | super().do_list(arg) 801 | src = self.format_source(self.stdout.getvalue()) 802 | self.stdout = oldstdout 803 | print(src, file=self.stdout, end="\n\033[F") 804 | 805 | do_list.__doc__ = pdb.Pdb.do_list.__doc__ 806 | do_l = do_list 807 | 808 | def do_continue(self, arg): 809 | self.last_cmd = self.lastcmd = "continue" 810 | if arg != "": 811 | self.do_tbreak(arg) 812 | return super().do_continue(arg) 813 | do_continue.__doc__ = pdb.Pdb.do_continue.__doc__ 814 | do_c = do_cont = do_continue 815 | 816 | def do_next(self, arg): 817 | self.last_cmd = self.lastcmd = "next" 818 | return super().do_next(arg) 819 | do_next.__doc__ = pdb.Pdb.do_next.__doc__ 820 | do_n = do_next 821 | 822 | def do_step(self, arg): 823 | self.last_cmd = self.lastcmd = "step" 824 | return super().do_step(arg) 825 | do_step.__doc__ = pdb.Pdb.do_step.__doc__ 826 | do_s = do_step 827 | 828 | def do_until(self, arg): 829 | self.last_cmd = self.lastcmd = "until" 830 | return super().do_until(arg) 831 | do_until.__doc__ = pdb.Pdb.do_until.__doc__ 832 | do_unt = do_until 833 | 834 | def do_p(self, arg): 835 | try: 836 | self.message(repr(self._getval(arg))) 837 | except Exception: 838 | if not arg: 839 | print('Print usage: "p "', file=self.stdout) 840 | print( 841 | "Local variables: %r" % self.curframe_locals.keys(), 842 | file=self.stdout, 843 | ) 844 | return 845 | else: 846 | print( 847 | 'See "locals()" or "globals()" for available args!', 848 | file=self.stdout, 849 | ) 850 | return 851 | do_p.__doc__ = pdb.Pdb.do_p.__doc__ 852 | 853 | def do_pp(self, arg): 854 | width, _ = get_terminal_size() 855 | try: 856 | pprint.pprint(self._getval(arg), self.stdout, width=width) 857 | except Exception: 858 | if not arg: 859 | print('PrettyPrint usage: "pp "', file=self.stdout) 860 | print( 861 | "Local variables: %r" % self.curframe_locals.keys(), 862 | file=self.stdout, 863 | ) 864 | return 865 | else: 866 | print( 867 | 'See "locals()" or "globals()" for available args!', 868 | file=self.stdout, 869 | ) 870 | return 871 | do_pp.__doc__ = pdb.Pdb.do_pp.__doc__ 872 | 873 | def enter_recursive_debugger(self, arg): 874 | """ 875 | Enter a recursive debugger that steps through the code argument 876 | (which is an arbitrary expression or statement to be 877 | executed in the current environment). 878 | """ 879 | sys.settrace(None) 880 | globals = self.curframe.f_globals 881 | locals = self.curframe_locals 882 | p = Pdb(self.completekey, self.stdin, self.stdout) 883 | p.prompt = "(%s) " % self.prompt.strip() 884 | self.message("ENTERING RECURSIVE DEBUGGER") 885 | try: 886 | sys.call_tracing(p.run, (arg, globals, locals)) 887 | except Exception: 888 | self._error_exc() 889 | self.message("LEAVING RECURSIVE DEBUGGER") 890 | sys.settrace(self.trace_dispatch) 891 | self.lastcmd = p.lastcmd 892 | 893 | def do_debug(self, arg): 894 | self.last_cmd = self.lastcmd = "debug" 895 | Config = self.ConfigFactory 896 | 897 | class PdbpWithConfig(self.__class__): 898 | def __init__(self_withcfg, *args, **kwargs): 899 | kwargs.setdefault("Config", Config) 900 | super(PdbpWithConfig, self_withcfg).__init__(*args, **kwargs) 901 | self_withcfg.use_rawinput = self.use_rawinput 902 | do_debug_func = self.enter_recursive_debugger 903 | if sys.version_info < (3, 14): 904 | do_debug_func = super().do_debug 905 | newglobals = do_debug_func.__globals__.copy() 906 | newglobals["Pdb"] = PdbpWithConfig 907 | orig_do_debug = rebind_globals(do_debug_func, newglobals) 908 | try: 909 | return orig_do_debug(self, arg) 910 | except Exception: 911 | exc_info = sys.exc_info()[:2] 912 | msg = traceback.format_exception_only(*exc_info)[-1].strip() 913 | self.error(msg) 914 | do_debug.__doc__ = pdb.Pdb.do_debug.__doc__ 915 | 916 | def do_run(self, arg): 917 | """Restart/Rerun during ``python -m pdbp `` mode.""" 918 | self.last_cmd = self.lastcmd = "run" 919 | if arg: 920 | import shlex 921 | argv0 = sys.argv[0:1] 922 | sys.argv = shlex.split(arg) 923 | sys.argv[:0] = argv0 924 | raise Restart 925 | do_restart = do_run 926 | 927 | def do_interact(self, arg): 928 | ns = self.curframe.f_globals.copy() 929 | ns.update(self.curframe.f_locals) 930 | code.interact("*interactive*", local=ns) 931 | 932 | def do_track(self, arg): 933 | try: 934 | from rpython.translator.tool.reftracker import track 935 | except ImportError: 936 | print( 937 | "** cannot import pypy.translator.tool.reftracker **", 938 | file=self.stdout, 939 | ) 940 | print( 941 | "This command requires pypy to be in the current PYTHONPATH.", 942 | file=self.stdout, 943 | ) 944 | return 945 | try: 946 | val = self._getval(arg) 947 | except Exception: 948 | pass 949 | else: 950 | track(val) 951 | 952 | def _get_display_list(self): 953 | return self.display_list.setdefault(self.curframe, {}) 954 | 955 | def _getval_or_undefined(self, arg): 956 | try: 957 | return eval(arg, self.curframe.f_globals, 958 | self.curframe.f_locals) 959 | except NameError: 960 | return undefined 961 | 962 | def do_display(self, arg): 963 | try: 964 | value = self._getval_or_undefined(arg) 965 | except Exception: 966 | return 967 | self._get_display_list()[arg] = value 968 | 969 | def do_undisplay(self, arg): 970 | try: 971 | del self._get_display_list()[arg] 972 | except KeyError: 973 | print("** %s not in the display list **" % arg, file=self.stdout) 974 | 975 | def __get_return_color(self, s): 976 | frame, lineno = self.stack[self.curindex] 977 | if self.has_traceback or "__exception__" in frame.f_locals: 978 | self.config.last_return_color = self.config.pm_return_value_color 979 | return self.config.last_return_color 980 | the_return_color = None 981 | return_value = s.strip().split("return ")[-1] 982 | if return_value == "None": 983 | the_return_color = self.config.none_return_value_color 984 | elif return_value == "True": 985 | the_return_color = self.config.true_return_value_color 986 | elif return_value in ["False", "", "[]", r"{}"]: 987 | the_return_color = self.config.false_return_value_color 988 | elif len(return_value) > 0 and return_value[0].isdecimal(): 989 | the_return_color = self.config.num_return_value_color 990 | else: 991 | the_return_color = self.config.return_value_color 992 | self.config.last_return_color = the_return_color 993 | return self.config.last_return_color 994 | 995 | def _print_if_sticky(self): 996 | if self.sticky: 997 | if self.first_time_sticky: 998 | self.first_time_sticky = False 999 | self.ok_to_clear = True 1000 | frame, lineno = self.stack[self.curindex] 1001 | filename = self.canonic(frame.f_code.co_filename) 1002 | lno = Color.set(self.config.line_number_color, "%r" % lineno) 1003 | short_filename = filename 1004 | if self.config.shorten_path: 1005 | try: 1006 | home_dir = os.path.expanduser("~") 1007 | if ( 1008 | len(home_dir) > 4 1009 | and filename.startswith(home_dir) 1010 | and filename.count(home_dir) == 1 1011 | ): 1012 | short_filename = filename.replace(home_dir, "~") 1013 | except Exception: 1014 | pass 1015 | fname = Color.set(self.config.filename_color, short_filename) 1016 | fnln = None 1017 | if not self.curindex: 1018 | self.curindex = 0 1019 | colored_index = Color.set(self.config.stack_color, self.curindex) 1020 | fnln = "[%s] > %s(%s)" % (colored_index, fname, lno) 1021 | nc_fnln = "[%s] > %s(%s)" % (self.curindex, filename, lineno) 1022 | sticky_range = self.sticky_ranges.get(self.curframe, None) 1023 | self._printlonglist(sticky_range, fnln=fnln, nc_fnln=nc_fnln) 1024 | needs_extra_line = False 1025 | if "__exception__" in frame.f_locals: 1026 | s = self._format_exc_for_sticky( 1027 | frame.f_locals["__exception__"] 1028 | ) 1029 | if s: 1030 | last_return_color = self.config.last_return_color 1031 | if ( 1032 | last_return_color == self.config.pm_return_value_color 1033 | and not self.config.exception_caught 1034 | ): 1035 | print(s, file=self.stdout) 1036 | needs_extra_line = True 1037 | elif "exc" in frame.f_locals and "msg" in frame.f_locals: 1038 | s = str(frame.f_locals["msg"]).strip() 1039 | e = str(frame.f_locals["exc"]).strip() 1040 | e = e.split("")[0] + ":" 1041 | if s and self.has_traceback: 1042 | if self.config.highlight: 1043 | the_return_color = self.__get_return_color(s) 1044 | s = Color.set(the_return_color, s) 1045 | e = Color.set(the_return_color, e) 1046 | last_return_color = self.config.last_return_color 1047 | lastline = None 1048 | try: 1049 | lastline = inspect.getsourcelines(self.curframe)[0][-1] 1050 | lastline = str(lastline) 1051 | except Exception: 1052 | lastline = "" 1053 | if ( 1054 | last_return_color == self.config.pm_return_value_color 1055 | and not self.config.exception_caught 1056 | and "raise " in lastline 1057 | and "(msg" in lastline.replace(" ", "") 1058 | ): 1059 | print(e, file=self.stdout) 1060 | print(" " + s, file=self.stdout) 1061 | needs_extra_line = True 1062 | elif "msg" in frame.f_locals or "message" in frame.f_locals: 1063 | s = None 1064 | s2 = None 1065 | if "msg" in frame.f_locals: 1066 | s = str(frame.f_locals["msg"]).strip() 1067 | if "message" in frame.f_locals: 1068 | s2 = str(frame.f_locals["message"]).strip() 1069 | if (s or s2) and self.has_traceback: 1070 | if self.config.highlight: 1071 | if s: 1072 | the_return_color = self.__get_return_color(s) 1073 | s = Color.set(the_return_color, s) 1074 | if s2: 1075 | the_return_color_2 = self.__get_return_color(s2) 1076 | s2 = Color.set(the_return_color_2, s2) 1077 | last_return_color = self.config.last_return_color 1078 | lastline = None 1079 | try: 1080 | lastline = inspect.getsourcelines(self.curframe)[0][-1] 1081 | lastline = str(lastline) 1082 | except Exception: 1083 | lastline = "" 1084 | if ( 1085 | last_return_color == self.config.pm_return_value_color 1086 | and not self.config.exception_caught 1087 | and "raise " in lastline 1088 | and s 1089 | and "(msg" in lastline.replace(" ", "") 1090 | ): 1091 | print(s, file=self.stdout) 1092 | needs_extra_line = True 1093 | elif ( 1094 | last_return_color == self.config.pm_return_value_color 1095 | and not self.config.exception_caught 1096 | and "raise " in lastline 1097 | and s2 1098 | and "(message" in lastline.replace(" ", "") 1099 | ): 1100 | print(s2, file=self.stdout) 1101 | needs_extra_line = True 1102 | if "__return__" in frame.f_locals: 1103 | rv = frame.f_locals["__return__"] 1104 | try: 1105 | s = repr(rv) 1106 | except KeyboardInterrupt: 1107 | raise 1108 | except Exception: 1109 | s = "(unprintable return value)" 1110 | s = " return " + s 1111 | if self.config.highlight: 1112 | if ( 1113 | needs_extra_line 1114 | and frame.f_locals["__return__"] is None 1115 | ): 1116 | # There was an Exception. And returning None. 1117 | the_return_color = self.config.exc_line_color 1118 | s = s + " " 1119 | else: 1120 | the_return_color = self.__get_return_color(s) 1121 | s = Color.set(the_return_color, s) 1122 | print(s, file=self.stdout) 1123 | needs_extra_line = True 1124 | if needs_extra_line: 1125 | print(file=self.stdout, end="\n\033[F") 1126 | 1127 | def _format_exc_for_sticky(self, exc): 1128 | if len(exc) != 2: 1129 | return "pdbp: got unexpected __exception__: %r" % (exc,) 1130 | exc_type, exc_value = exc 1131 | s = "" 1132 | try: 1133 | try: 1134 | try: 1135 | module = str(exc_type.__module__) 1136 | module = module.split("")[0] 1137 | if module != "builtins": 1138 | s = module + "." + exc_type.__name__.strip() 1139 | else: 1140 | s = exc_type.__name__.strip() 1141 | except Exception: 1142 | s = exc_type.__name__.strip() 1143 | except AttributeError: 1144 | s = str(exc_type).strip() 1145 | if exc_value is not None: 1146 | s += ": " 1147 | s2 = str(exc_value) 1148 | if s2.startswith("Message:") and s2.count("Message:") == 1: 1149 | s2 = "\n " + s2.split("Message:")[-1].strip() 1150 | s += s2 1151 | except KeyboardInterrupt: 1152 | raise 1153 | except Exception as exc: 1154 | try: 1155 | s += "(unprintable exception: %r)" % (exc,) 1156 | except Exception: 1157 | s += "(unprintable exception)" 1158 | if self.config.highlight: 1159 | the_return_color = self.__get_return_color(s) 1160 | s = Color.set(the_return_color, s) 1161 | return s 1162 | 1163 | def do_sticky(self, arg): 1164 | """Toggle sticky mode. Usage: sticky [start end]""" 1165 | if arg: 1166 | try: 1167 | start, end = map(int, arg.split()) 1168 | except ValueError: 1169 | print("** Error when parsing argument: %s **" % arg, 1170 | file=self.stdout) 1171 | return 1172 | self.sticky = True 1173 | self.sticky_ranges[self.curframe] = start, end + 1 1174 | else: 1175 | self.sticky = not self.sticky 1176 | self.sticky_range = None 1177 | if self.sticky: 1178 | self._print_if_sticky() 1179 | else: 1180 | print(file=self.stdout) 1181 | self.print_stack_entry(self.stack[self.curindex]) 1182 | print(file=self.stdout, end="\n\033[F") 1183 | 1184 | def do_truncate(self, arg): 1185 | # Toggle line truncation. Usage: "truncate" / "trun". 1186 | # (Changes only appear when "sticky" mode is active.) 1187 | # When enabled, all lines take on the screen width. 1188 | self.config.truncate_long_lines = not self.config.truncate_long_lines 1189 | self.print_current_stack_entry() 1190 | do_trun = do_truncate 1191 | 1192 | def print_stack_trace(self, count=None): 1193 | if count is None: 1194 | stack_to_print = self.stack 1195 | elif count == 0: 1196 | stack_to_print = [self.stack[self.curindex]] 1197 | elif count < 0: 1198 | stack_to_print = self.stack[:-count] 1199 | else: 1200 | stack_to_print = self.stack[-count:] 1201 | try: 1202 | for frame_lineno in stack_to_print: 1203 | self.print_stack_entry(frame_lineno) 1204 | except KeyboardInterrupt: 1205 | pass 1206 | 1207 | def print_stack_entry( 1208 | self, frame_lineno, prompt_prefix=pdb.line_prefix, frame_index=None 1209 | ): 1210 | if self.sticky: 1211 | return 1212 | frame_index = frame_index if frame_index is not None else self.curindex 1213 | frame, lineno = frame_lineno 1214 | colored_index = Color.set(self.config.stack_color, frame_index) 1215 | if frame is self.curframe: 1216 | indicator = " >" 1217 | color = self.config.regular_line_color 1218 | if self.has_traceback: 1219 | color = self.config.exc_line_color 1220 | if frame_index == len(self.stack) - 1: 1221 | color = self.config.pm_cur_line_color 1222 | ind = setbgcolor(indicator, color) 1223 | print("[%s]%s" % (colored_index, ind), file=self.stdout, end=" ") 1224 | else: 1225 | print("[%s] " % colored_index, file=self.stdout, end=" ") 1226 | stack_entry = self.format_stack_entry(frame_lineno, prompt_prefix) 1227 | print(stack_entry, file=self.stdout) 1228 | if not self.sticky: 1229 | print(file=self.stdout, end="\n\033[F") 1230 | if ( 1231 | "\n-> except " in stack_entry or "\n-> except:" in stack_entry 1232 | ): 1233 | self.config.exception_caught = True 1234 | 1235 | def print_current_stack_entry(self): 1236 | if self.sticky: 1237 | self._print_if_sticky() 1238 | else: 1239 | print(file=self.stdout) 1240 | self.print_stack_entry(self.stack[self.curindex]) 1241 | print(file=self.stdout, end="\n\033[F") 1242 | 1243 | def preloop(self): 1244 | self._print_if_sticky() 1245 | display_list = self._get_display_list() 1246 | for expr, oldvalue in display_list.items(): 1247 | newvalue = self._getval_or_undefined(expr) 1248 | if newvalue is not oldvalue or newvalue != oldvalue: 1249 | display_list[expr] = newvalue 1250 | print("%s: %r --> %r" % (expr, oldvalue, newvalue), 1251 | file=self.stdout) 1252 | 1253 | def _get_position_of_arg(self, arg): 1254 | try: 1255 | obj = self._getval(arg) 1256 | except Exception: 1257 | return None, None, None 1258 | if isinstance(obj, str): 1259 | return obj, 1, None 1260 | try: 1261 | filename = inspect.getabsfile(obj) 1262 | lines, lineno = inspect.getsourcelines(obj) 1263 | except (IOError, TypeError) as e: 1264 | print("** Error: %s **" % e, file=self.stdout) 1265 | return None, None, None 1266 | return filename, lineno, lines 1267 | 1268 | def do_source(self, arg): 1269 | _, lineno, lines = self._get_position_of_arg(arg) 1270 | if lineno is None: 1271 | return 1272 | try: 1273 | frame = self.curframe 1274 | filename = self.canonic(frame.f_code.co_filename) 1275 | nc_fnln = "[%s] > %s(%s)" % (self.curindex, filename, lineno) 1276 | self._print_lines_pdbp( 1277 | lines, lineno, print_markers=False, nc_fnln=nc_fnln 1278 | ) 1279 | except Exception: 1280 | self._print_lines_pdbp(lines, lineno, print_markers=False) 1281 | 1282 | def do_frame(self, arg): 1283 | try: 1284 | arg = int(arg) 1285 | except (ValueError, TypeError): 1286 | print( 1287 | '*** Expected a number, got "{0}"'.format(arg), 1288 | file=self.stdout 1289 | ) 1290 | return 1291 | if arg < 0 or arg >= len(self.stack): 1292 | print("*** Out of range", file=self.stdout) 1293 | else: 1294 | self.curindex = arg 1295 | self.curframe = self.stack[self.curindex][0] 1296 | if sys.version_info < (3, 14): 1297 | self.curframe_locals = self.curframe.f_locals 1298 | self.print_current_stack_entry() 1299 | self.lineno = None 1300 | do_f = do_frame 1301 | 1302 | def do_up(self, arg="1"): 1303 | self.last_cmd = self.lastcmd = "up" 1304 | arg = "1" if arg == "" else arg 1305 | try: 1306 | arg = int(arg) 1307 | except (ValueError, TypeError): 1308 | print( 1309 | '*** Expected a number, got "{0}"'.format(arg), 1310 | file=self.stdout 1311 | ) 1312 | return 1313 | if self.curindex - arg < 0: 1314 | print("*** Oldest frame", file=self.stdout) 1315 | else: 1316 | self.curindex = self.curindex - arg 1317 | self.curframe = self.stack[self.curindex][0] 1318 | if sys.version_info < (3, 14): 1319 | self.curframe_locals = self.curframe.f_locals 1320 | self.print_current_stack_entry() 1321 | self.lineno = None 1322 | do_up.__doc__ = pdb.Pdb.do_up.__doc__ 1323 | do_u = do_up 1324 | 1325 | def do_down(self, arg="1"): 1326 | self.last_cmd = self.lastcmd = "down" 1327 | arg = "1" if arg == "" else arg 1328 | try: 1329 | arg = int(arg) 1330 | except (ValueError, TypeError): 1331 | print( 1332 | '*** Expected a number, got "{0}"'.format(arg), 1333 | file=self.stdout 1334 | ) 1335 | return 1336 | if self.curindex + arg >= len(self.stack): 1337 | print("*** Newest frame", file=self.stdout) 1338 | else: 1339 | self.curindex = self.curindex + arg 1340 | self.curframe = self.stack[self.curindex][0] 1341 | if sys.version_info < (3, 14): 1342 | self.curframe_locals = self.curframe.f_locals 1343 | self.print_current_stack_entry() 1344 | self.lineno = None 1345 | do_down.__doc__ = pdb.Pdb.do_down.__doc__ 1346 | do_d = do_down 1347 | 1348 | def do_where(self, arg): 1349 | self.last_cmd = self.lastcmd = "where" 1350 | self.sticky = False 1351 | print(file=self.stdout) 1352 | self.print_stack_trace() 1353 | do_w = do_where 1354 | do_bt = do_where 1355 | 1356 | def _open_editor(self, editor, lineno, filename): 1357 | filename = filename.replace('"', '\\"') 1358 | os.system('%s "%s"' % (editor, filename)) 1359 | 1360 | def _get_current_position(self): 1361 | frame = self.curframe 1362 | lineno = frame.f_lineno 1363 | filename = os.path.abspath(frame.f_code.co_filename) 1364 | return filename, lineno 1365 | 1366 | def do_edit(self, arg): 1367 | "Open an editor visiting the current file at the current line" 1368 | if arg == "": 1369 | filename, lineno = self._get_current_position() 1370 | else: 1371 | filename, lineno, _ = self._get_position_of_arg(arg) 1372 | if filename is None: 1373 | return 1374 | match = re.match(r".*<\d+-codegen (.*):(\d+)>", filename) 1375 | if match: 1376 | filename = match.group(1) 1377 | lineno = int(match.group(2)) 1378 | editor = self.config.editor 1379 | self._open_editor(editor, lineno, filename) 1380 | 1381 | def _get_history(self): 1382 | return [s for s in self.history if not side_effects_free.match(s)] 1383 | 1384 | def _get_history_text(self): 1385 | import linecache 1386 | line = linecache.getline(self.start_filename, self.start_lineno) 1387 | nspaces = len(line) - len(line.lstrip()) 1388 | indent = " " * nspaces 1389 | history = [indent + s for s in self._get_history()] 1390 | return "\n".join(history) + "\n" 1391 | 1392 | def set_trace(self, frame=None): 1393 | """Remember starting frame. Used with pytest.""" 1394 | if frame is None: 1395 | frame = sys._getframe().f_back 1396 | self._via_set_trace_frame = frame 1397 | return super().set_trace(frame) 1398 | 1399 | def is_skipped_module(self, module_name): 1400 | if module_name is None: 1401 | return False 1402 | return super().is_skipped_module(module_name) 1403 | 1404 | def error(self, msg): 1405 | """Override/enhance default error method to display tracebacks.""" 1406 | print("***", msg, file=self.stdout) 1407 | 1408 | if not self.config.show_traceback_on_error: 1409 | return 1410 | 1411 | etype, evalue, tb = sys.exc_info() 1412 | if tb and tb.tb_frame.f_code.co_name == "default": 1413 | tb = tb.tb_next 1414 | if tb and tb.tb_frame.f_code.co_filename == "": 1415 | tb = tb.tb_next 1416 | if tb: 1417 | self._remove_bdb_context(evalue) 1418 | tb_limit = self.config.show_traceback_on_error_limit 1419 | fmt_exc = traceback.format_exception( 1420 | etype, evalue, tb, limit=tb_limit 1421 | ) 1422 | # Remove the last line (exception string again). 1423 | if len(fmt_exc) > 1 and fmt_exc[-1][0] != " ": 1424 | fmt_exc.pop() 1425 | print("".join(fmt_exc).rstrip(), file=self.stdout) 1426 | 1427 | @staticmethod 1428 | def _remove_bdb_context(evalue): 1429 | removed_bdb_context = evalue 1430 | while removed_bdb_context.__context__: 1431 | ctx = removed_bdb_context.__context__ 1432 | if ( 1433 | isinstance(ctx, AttributeError) 1434 | and ctx.__traceback__.tb_frame.f_code.co_name == "onecmd" 1435 | ): 1436 | removed_bdb_context.__context__ = None 1437 | break 1438 | removed_bdb_context = removed_bdb_context.__context__ 1439 | 1440 | 1441 | if hasattr(pdb, "_usage"): 1442 | _usage = pdb._usage 1443 | 1444 | # Copy some functions from pdb.py, but rebind the global dictionary. 1445 | for name in "run runeval runctx runcall pm main".split(): 1446 | func = getattr(pdb, name) 1447 | globals()[name] = rebind_globals(func, globals()) 1448 | del name, func 1449 | 1450 | 1451 | def post_mortem(t=None, Pdb=Pdb): 1452 | if t is None: 1453 | t = sys.exc_info()[2] 1454 | assert t is not None, "post_mortem outside of exception context" 1455 | p = Pdb() 1456 | p.reset() 1457 | p.interaction(None, t) 1458 | 1459 | 1460 | GLOBAL_PDB = None 1461 | 1462 | 1463 | def set_trace(frame=None, header=None, Pdb=Pdb, **kwds): 1464 | global GLOBAL_PDB 1465 | if GLOBAL_PDB and hasattr(GLOBAL_PDB, "_pdbp_completing"): 1466 | return 1467 | if frame is None: 1468 | frame = sys._getframe().f_back 1469 | if GLOBAL_PDB: 1470 | pdb = GLOBAL_PDB 1471 | sys.settrace(None) 1472 | else: 1473 | filename = frame.f_code.co_filename 1474 | lineno = frame.f_lineno 1475 | pdb = Pdb(start_lineno=lineno, start_filename=filename, **kwds) 1476 | GLOBAL_PDB = pdb 1477 | if header is not None: 1478 | pdb.message(header) 1479 | pdb.set_trace(frame) 1480 | 1481 | 1482 | def cleanup(): 1483 | global GLOBAL_PDB 1484 | GLOBAL_PDB = None 1485 | 1486 | 1487 | def xpm(Pdb=Pdb): 1488 | """ 1489 | Enter a post-mortem pdb related to the exception just catched. 1490 | (Used inside an except clause.) 1491 | """ 1492 | info = sys.exc_info() 1493 | print(traceback.format_exc()) 1494 | post_mortem(info[2], Pdb) 1495 | 1496 | 1497 | def enable(): 1498 | global set_trace 1499 | set_trace = enable.set_trace 1500 | 1501 | 1502 | enable.set_trace = set_trace 1503 | 1504 | 1505 | def disable(): 1506 | global set_trace 1507 | set_trace = disable.set_trace 1508 | 1509 | 1510 | disable.set_trace = lambda frame=None, Pdb=Pdb: None 1511 | 1512 | 1513 | def set_tracex(): 1514 | print("PDB!") 1515 | 1516 | 1517 | set_tracex._dont_inline_ = True 1518 | 1519 | _HIDE_FRAME = object() 1520 | 1521 | 1522 | def hideframe(func): 1523 | c = func.__code__ 1524 | new_co_consts = c.co_consts + (_HIDE_FRAME,) 1525 | if hasattr(c, "replace"): 1526 | c = c.replace(co_consts=new_co_consts) 1527 | else: 1528 | c = types.CodeType( 1529 | c.co_argcount, c.co_kwonlyargcount, c.co_nlocals, c.co_stacksize, 1530 | c.co_flags, c.co_code, 1531 | c.co_consts + (_HIDE_FRAME,), 1532 | c.co_names, c.co_varnames, c.co_filename, 1533 | c.co_name, c.co_firstlineno, c.co_lnotab, 1534 | c.co_freevars, c.co_cellvars, 1535 | ) 1536 | func.__code__ = c 1537 | return func 1538 | 1539 | 1540 | def always(obj, value): 1541 | return True 1542 | 1543 | 1544 | def break_on_setattr(attrname, condition=always, Pdb=Pdb): 1545 | def decorator(cls): 1546 | old___setattr__ = cls.__setattr__ 1547 | 1548 | @hideframe 1549 | def __setattr__(self, attr, value): 1550 | if attr == attrname and condition(self, value): 1551 | frame = sys._getframe().f_back 1552 | pdb_ = Pdb() 1553 | pdb_.set_trace(frame) 1554 | pdb_.stopframe = frame 1555 | pdb_.interaction(frame, None) 1556 | old___setattr__(self, attr, value) 1557 | cls.__setattr__ = __setattr__ 1558 | return cls 1559 | return decorator 1560 | 1561 | 1562 | import pdb # noqa 1563 | pdb.Pdb = Pdb 1564 | pdb.Color = Color 1565 | pdb.DefaultConfig = DefaultConfig 1566 | pdb.OrderedDict = OrderedDict 1567 | pdb.Completer = Completer 1568 | pdb.CLEARSCREEN = CLEARSCREEN 1569 | pdb.GLOBAL_PDB = GLOBAL_PDB 1570 | pdb.ConfigurableClass = ConfigurableClass 1571 | pdb.side_effects_free = side_effects_free 1572 | pdb.rebind_globals = rebind_globals 1573 | pdb.lasti2lineno = lasti2lineno 1574 | pdb.tabcompleter = tabcompleter 1575 | pdb.post_mortem = post_mortem 1576 | pdb.set_tracex = set_tracex 1577 | pdb.setbgcolor = setbgcolor 1578 | pdb.set_trace = set_trace 1579 | pdb.signature = signature 1580 | pdb.Undefined = Undefined 1581 | pdb.cleanup = cleanup 1582 | pdb.xpm = xpm 1583 | 1584 | 1585 | def print_pdb_continue_line(): 1586 | width, height = get_terminal_size() 1587 | pdb_continue = " PDB continue " 1588 | border_line = ">>>>>>>>%s>>>>>>>>" % pdb_continue 1589 | try: 1590 | terminal_size = width 1591 | if terminal_size < 30: 1592 | terminal_size = 30 1593 | border_len = terminal_size - len(pdb_continue) 1594 | border_left_len = int(border_len / 2) 1595 | border_right_len = int(border_len - border_left_len) 1596 | border_left = ">" * border_left_len 1597 | border_right = ">" * border_right_len 1598 | border_line = (border_left + pdb_continue + border_right) 1599 | except Exception: 1600 | pass 1601 | print("\n" + border_line + "\n") 1602 | 1603 | 1604 | def main(): 1605 | import getopt 1606 | opts, args = getopt.getopt(sys.argv[1:], "mhc:", ["help", "command="]) 1607 | if not args: 1608 | print(_usage) 1609 | sys.exit(2) 1610 | commands = [] 1611 | run_as_module = False 1612 | for opt, optarg in opts: 1613 | if opt in ["-h", "--help"]: 1614 | print(_usage) 1615 | sys.exit() 1616 | elif opt in ["-c", "--command"]: 1617 | commands.append(optarg) 1618 | elif opt in ["-m"]: 1619 | run_as_module = True 1620 | mainpyfile = args[0] 1621 | if not run_as_module and not os.path.exists(mainpyfile): 1622 | print("Error: %s does not exist!" % mainpyfile) 1623 | sys.exit(1) 1624 | sys.argv[:] = args 1625 | if not run_as_module: 1626 | mainpyfile = os.path.realpath(mainpyfile) 1627 | sys.path[0] = os.path.dirname(mainpyfile) 1628 | pdb = Pdb() 1629 | pdb.rcLines.extend(commands) 1630 | stay_in_pdb = True 1631 | while stay_in_pdb: 1632 | try: 1633 | if run_as_module: 1634 | pdb._runmodule(mainpyfile) 1635 | else: 1636 | pdb._runscript(mainpyfile) 1637 | if pdb._user_requested_quit: 1638 | break 1639 | print_pdb_continue_line() 1640 | stay_in_pdb = False 1641 | except Restart: 1642 | print("Restarting", mainpyfile, "with arguments:") 1643 | print("\t" + " ".join(sys.argv[1:])) 1644 | stay_in_pdb = True 1645 | except SystemExit: 1646 | print("The program exited via sys.exit(). Exit status:", end=" ") 1647 | print(sys.exc_info()[1]) 1648 | stay_in_pdb = False 1649 | except SyntaxError: 1650 | try: 1651 | traceback.print_exc() 1652 | except Exception: 1653 | pass 1654 | sys.exit(1) 1655 | stay_in_pdb = False 1656 | except Exception: 1657 | try: 1658 | traceback.print_exc() 1659 | except Exception: 1660 | pass 1661 | t = sys.exc_info()[2] 1662 | pdb.interaction(None, t) 1663 | print_pdb_continue_line() 1664 | stay_in_pdb = False 1665 | 1666 | 1667 | if __name__ == "__main__": 1668 | run_from_main = True 1669 | import pdbp 1670 | pdbp.main() 1671 | --------------------------------------------------------------------------------