├── 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 | 
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://pypi.python.org/pypi/pdbp)
2 |
3 | 
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 |
87 |
88 |
89 | ### Post Mortem Debug Mode:
90 |
91 |
92 |
93 |
94 | ### The ``where`` / ``w`` command, which displays the current stack:
95 |
96 |
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 |
107 |
108 | > **Non-Sticky Mode:**
109 |
110 |
111 |
112 | --------
113 |
114 | ### Tab completion:
115 |
116 |
117 |
118 | --------
119 |
120 | ### Multi-layer highlighting in the same stack:
121 |
122 |
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 |
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 |
--------------------------------------------------------------------------------