Repository: cansarigol/pdbr
Branch: master
Commit: a85df2d320fd
Files: 37
Total size: 59.0 KB
Directory structure:
gitextract_rmp4r7u5/
├── .github/
│ └── workflows/
│ ├── release.yml
│ └── tests.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .vscode/
│ └── settings.json
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── noxfile.py
├── pdbr/
│ ├── __init__.py
│ ├── __main__.py
│ ├── _cm.py
│ ├── _console_layout.py
│ ├── _pdbr.py
│ ├── cli.py
│ ├── helpers.py
│ ├── middlewares/
│ │ ├── __init__.py
│ │ ├── django.py
│ │ └── starlette.py
│ ├── runner.py
│ └── utils.py
├── pyproject.toml
├── runtests.py
├── scripts/
│ ├── lint
│ └── test
├── setup.cfg
└── tests/
├── __init__.py
├── conftest.py
├── test_api.py
├── test_config.py
├── test_magic.py
├── test_pdbr.py
└── tests_django/
├── __init__.py
├── test_settings.py
├── tests.py
└── urls.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
build-n-publish:
name: Build and publish to PyPI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10.16
uses: actions/setup-python@v4
with:
python-version: '3.10.16'
- name: Install poetry
run: python -m pip install poetry --user
- name: Build
run: poetry build
- name: Publish to PyPI
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_PASSWORD }}
run: poetry publish
================================================
FILE: .github/workflows/tests.yml
================================================
name: Test
on:
push:
branches: ["master"]
pull_request:
branches: ["master"]
jobs:
check:
name: "Check"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install pre-commit
run: |
pip install --upgrade pre-commit
- name: Run check
run: |
pre-commit run --all-files
test:
name: "Tests"
runs-on: ${{ matrix.platform }}
needs: check
strategy:
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install nox
run: |
pip install --upgrade nox
- name: Run tests
run: |
nox --sessions test django_test
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# lint
.ruff_cache
================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-merge-conflict
- id: check-symlinks
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-check-blanket-noqa
- id: python-use-type-annotations
- repo: https://github.com/jendrikseipp/vulture
rev: v2.14
hooks:
- id: vulture
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.9.3'
hooks:
- id: ruff
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black
language_version: python3
================================================
FILE: .vscode/settings.json
================================================
{
"makefile.extensionOutputFolder": "./.vscode"
}
================================================
FILE: Dockerfile
================================================
FROM python:3.7.9
ENV PYTHONUNBUFFERED=0
RUN pip install pip \
&& pip install nox \
&& pip install pre-commit
WORKDIR /pdbr
COPY . .
RUN pre-commit run --all-files
RUN nox --sessions test django_test
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Can Sarıgöl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
lint:
sh scripts/lint
test:
sh scripts/test
celery:
celery -A tasks worker --loglevel=info
build:
docker build -t pdbr .
act:
act -r -j test --container-architecture linux/amd64
================================================
FILE: README.md
================================================
# pdbr
[](https://pypi.org/project/pdbr/) [](https://pypi.org/project/pdbr/) [](https://github.com/cansarigol/pdbr/actions?query=workflow%3ATest) [](https://results.pre-commit.ci/latest/github/cansarigol/pdbr/master)
`pdbr` is intended to make the PDB results more colorful. it uses [Rich](https://github.com/willmcgugan/rich) library to carry out that.
## Installing
Install with `pip` or your favorite PyPi package manager.
```
pip install pdbr
```
## Breakpoint
In order to use ```breakpoint()```, set **PYTHONBREAKPOINT** with "pdbr.set_trace"
```python
import os
os.environ["PYTHONBREAKPOINT"] = "pdbr.set_trace"
```
or just import pdbr
```python
import pdbr
```
## New commands
### (i)nspect / inspectall | ia
[rich.inspect](https://rich.readthedocs.io/en/latest/introduction.html?s=03#rich-inspector)
### search | src
Search a phrase in the current frame.
In order to repeat the last one, type **/** character as arg.
### sql
Display value in sql format. Don't forget to install [sqlparse](https://github.com/andialbrecht/sqlparse) package.

It can be used for Django model queries as follows.
```
>>> sql str(Users.objects.all().query)
```

### (syn)tax
[ val,lexer ] Display [lexer](https://pygments.org/docs/lexers/).
### (v)ars
Get the local variables list as table.
### varstree | vt
Get the local variables list as tree.

## Config
Config is specified in **setup.cfg** and can be local or global. Local config (current working directory) has precedence over global (default) one. Global config must be located at `$XDG_CONFIG_HOME/pdbr/setup.cfg`.
### Style
In order to use Rich's traceback, style, and theme:
```
[pdbr]
style = yellow
use_traceback = True
theme = friendly
```
Also custom `Console` object can be assigned to the `set_trace`.
```python
import pdbr
from rich.console import Console
from rich.style import Style
from rich.theme import Theme
custom_theme = Theme({
"info": "dim cyan",
"warning": "magenta",
"danger": "bold red",
})
custom_style = Style(
color="magenta",
bgcolor="yellow",
italic=True,
)
console = Console(theme=custom_theme, style=custom_style)
pdbr.set_trace(console=console)
```
### History
**store_history** setting is used to keep and reload history, even the prompt is closed and opened again:
```
[pdbr]
...
store_history=.pdbr_history
```
By default, history is stored globally in `~/.pdbr_history`.
### Context
The **context** setting is used to specify the number of lines of source code context to show when displaying stacktrace information.
```
[pdbr]
...
context=10
```
This setting is only available when using `pdbr` with `IPython`.
## Celery
In order to use **Celery** remote debugger with pdbr, use ```celery_set_trace``` as below sample. For more information see the [Celery user guide](https://docs.celeryproject.org/en/stable/userguide/debugging.html).
```python
from celery import Celery
app = Celery('tasks', broker='pyamqp://guest@localhost//')
@app.task
def add(x, y):
import pdbr; pdbr.celery_set_trace()
return x + y
```
#### Telnet
Instead of using `telnet` or `nc`, in terms of using pdbr style, `pdbr_telnet` command can be used.

Also in order to activate history and be able to use arrow keys, install and use [rlwrap](https://github.com/hanslub42/rlwrap) package.
```
rlwrap -H '~/.pdbr_history' pdbr_telnet localhost 6899
```
## IPython
`pdbr` integrates with [IPython](https://ipython.readthedocs.io/).
This makes [`%magics`](https://ipython.readthedocs.io/en/stable/interactive/magics.html) available, for example:
```python
(Pdbr) %timeit range(100)
104 ns ± 2.05 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
```
To enable `IPython` features, install it separately, or like below:
```
pip install pdbr[ipython]
```
## pytest
In order to use `pdbr` with pytest `--pdb` flag, add `addopts` setting in your pytest.ini.
```
[pytest]
addopts: --pdbcls=pdbr:RichPdb
```
## sys.excepthook
The `sys.excepthook` is a Python system hook that provides a way to customize the behavior when an unhandled exception occurs. Since `pdbr` use automatic traceback handler feature of `rich`, formatting exception print is not necessary if `pdbr` module is already imported.
In order to use post-mortem or perform other debugging features of `pdbr`, override `sys.excepthook` with a function that will act as your custom excepthook:
```python
import sys
import pdbr
def custom_excepthook(exc_type, exc_value, exc_traceback):
pdbr.post_mortem(exc_traceback, exc_value)
# [Optional] call the original excepthook as well
sys.__excepthook__(exc_type, exc_value, exc_traceback)
sys.excepthook = custom_excepthook
```
Now, whenever an unhandled exception occurs, `pdbr` will be triggered, allowing you to debug the issue interactively.
## Context Decorator
`pdbr_context` and `apdbr_context` (`asyncio` corresponding) can be used as **with statement** or **decorator**. It calls `post_mortem` if `traceback` is not none.
```python
from pdbr import apdbr_context, pdbr_context
@pdbr_context()
def foo():
...
def bar():
with pdbr_context():
...
@apdbr_context()
async def foo():
...
async def bar():
async with apdbr_context():
...
```

## Django DiscoverRunner
To being activated the pdb in Django test, change `TEST_RUNNER` like below. Unlike Django (since you are not allowed to use for smaller versions than 3), pdbr runner can be used for version 1.8 and subsequent versions.
```
TEST_RUNNER = "pdbr.runner.PdbrDiscoverRunner"
```

## Middlewares
### Starlette
```python
from fastapi import FastAPI
from pdbr.middlewares.starlette import PdbrMiddleware
app = FastAPI()
app.add_middleware(PdbrMiddleware, debug=True)
@app.get("/")
async def main():
1 / 0
return {"message": "Hello World"}
```
### Django
In order to catch the problematic codes with post mortem, place the middleware class.
```
MIDDLEWARE = (
...
"pdbr.middlewares.django.PdbrMiddleware",
)
```

## Shell
Running `pdbr` command in terminal starts an `IPython` terminal app instance. Unlike default `TerminalInteractiveShell`, the new shell uses pdbr as debugger class instead of `ipdb`.
#### %debug magic sample

### As a Script
If `pdbr` command is used with an argument, it is invoked as a script and [debugger-commands](https://docs.python.org/3/library/pdb.html#debugger-commands) can be used with it.
```python
# equivalent code: `python -m pdbr -c 'b 5' my_test.py`
pdbr -c 'b 5' my_test.py
>>> Breakpoint 1 at /my_test.py:5
> /my_test.py(3)<module>()
1
2
----> 3 def test():
4 foo = "foo"
1 5 bar = "bar"
(Pdbr)
```
### Terminal
#### Django shell sample

## Vscode user snippet
To create or edit your own snippets, select **User Snippets** under **File > Preferences** (**Code > Preferences** on macOS), and then select **python.json**.
Place the below snippet in json file for **pdbr**.
```
{
...
"pdbr": {
"prefix": "pdbr",
"body": "import pdbr; pdbr.set_trace()",
"description": "Code snippet for pdbr debug"
},
}
```
For **Celery** debug.
```
{
...
"rdbr": {
"prefix": "rdbr",
"body": "import pdbr; pdbr.celery_set_trace()",
"description": "Code snippet for Celery pdbr debug"
},
}
```
## Samples



### Traceback

================================================
FILE: noxfile.py
================================================
import nox
nox.options.stop_on_first_error = True
@nox.session
def test(session, reuse_venv=True):
session.install(
".",
"pytest",
"pytest-cov",
"rich",
"prompt_toolkit",
"IPython",
)
session.run(
"pytest",
"--cov-report",
"term-missing",
"--cov=pdbr",
"--capture=no",
"--disable-warnings",
"tests",
)
@nox.session
@nox.parametrize("django", ["3.2", "4.2"])
def django_test(session, django, reuse_venv=True):
session.install(f"django=={django}", "rich", "pytest")
session.run("python", "runtests.py")
================================================
FILE: pdbr/__init__.py
================================================
from pdbr.__main__ import RichPdb, celery_set_trace, pm, post_mortem, run, set_trace
from pdbr._cm import apdbr_context, pdbr_context
__all__ = [
"set_trace",
"run",
"pm",
"post_mortem",
"celery_set_trace",
"RichPdb",
"pdbr_context",
"apdbr_context",
]
================================================
FILE: pdbr/__main__.py
================================================
import os
import pdb
import sys
from .utils import _pdbr_cls, _rdbr_cls
os.environ["PYTHONBREAKPOINT"] = "pdbr.set_trace"
RichPdb = _pdbr_cls(return_instance=False, show_layouts=False)
def set_trace(*, console=None, header=None, context=None, show_layouts=False):
pdb_cls = _pdbr_cls(console=console, context=context, show_layouts=show_layouts)
if header is not None:
pdb_cls.message(header)
pdb_cls.set_trace(sys._getframe().f_back)
def run(statement, globals=None, locals=None):
RichPdb().run(statement, globals, locals)
def post_mortem(traceback=None, value=None):
_, sys_value, sys_traceback = sys.exc_info()
value = value or sys_value
traceback = traceback or sys_traceback
if traceback is None:
raise ValueError(
"A valid traceback must be passed if no exception is being handled"
)
p = RichPdb()
p.reset()
if value:
p.error(value)
p.interaction(None, traceback)
def pm():
post_mortem(sys.last_traceback)
def celery_set_trace(frame=None):
pdb_cls = _rdbr_cls()
if frame is None:
frame = sys._getframe().f_back
return pdb_cls.set_trace(frame)
def main():
pdb.Pdb = RichPdb
pdb.main()
if __name__ == "__main__":
main()
================================================
FILE: pdbr/_cm.py
================================================
from contextlib import ContextDecorator
from functools import wraps
from pdbr.__main__ import post_mortem
class pdbr_context(ContextDecorator):
def __init__(self, suppress_exc=True, debug=True):
self.suppress_exc = suppress_exc
self.debug = debug
def __enter__(self):
return self
def __exit__(self, _, exc_value, exc_traceback):
if exc_traceback and self.debug:
post_mortem(exc_traceback, exc_value)
return self.suppress_exc
return False
class AsyncContextDecorator(ContextDecorator):
def __call__(self, func):
@wraps(func)
async def inner(*args, **kwds):
async with self._recreate_cm():
return await func(*args, **kwds)
return inner
class apdbr_context(AsyncContextDecorator):
def __init__(self, suppress_exc=True, debug=True):
self.suppress_exc = suppress_exc
self.debug = debug
async def __aenter__(self):
return self
async def __aexit__(self, _, exc_value, exc_traceback):
if exc_traceback and self.debug:
post_mortem(exc_traceback, exc_value)
return self.suppress_exc
return False
================================================
FILE: pdbr/_console_layout.py
================================================
from rich.containers import Lines
from rich.errors import NotRenderableError
from rich.layout import Layout
from rich.panel import Panel
class ConsoleLayoutMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class ConsoleLayout(metaclass=ConsoleLayoutMeta):
def __init__(self, console):
self.console = console
self.layout = self._prep_layout()
def _prep_layout(self):
layout = Layout()
right_body = Layout(name="right_body", ratio=1)
layout.split(
Layout(name="left_body", ratio=2),
right_body,
splitter="row",
)
right_body.split(
Layout(name="up_footer", ratio=2), Layout(name="bottom_footer", ratio=1)
)
return layout
def print(self, message, code, stack_trace, vars, **kwargs):
try:
self.layout["left_body"].update(code)
self.layout["up_footer"].update(Panel(vars, title="Locals"))
self.layout["bottom_footer"].update(
Panel(Lines(stack_trace), title="Stack", style="white on blue")
)
self.console.print(self.layout, **kwargs)
self.console.print(message, **kwargs)
except NotRenderableError:
self.console.print(message, **kwargs)
================================================
FILE: pdbr/_pdbr.py
================================================
import inspect
import io
import re
from pathlib import Path
from pdb import Pdb
from rich import box, markup
from rich._inspect import Inspect
from rich.console import Console
from rich.panel import Panel
from rich.pretty import pprint
from rich.syntax import DEFAULT_THEME, Syntax
from rich.table import Table
from rich.text import Text
from rich.theme import Theme
from rich.tree import Tree
from pdbr._console_layout import ConsoleLayout
try:
from IPython.terminal.interactiveshell import TerminalInteractiveShell
TerminalInteractiveShell.simple_prompt = False
except ImportError:
pass
WITHOUT_LAYOUT_COMMANDS = (
"where",
"w",
)
ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
class AsciiStdout(io.TextIOWrapper):
pass
def rich_pdb_klass(
base, is_celery=False, console=None, context=None, show_layouts=True
):
class RichPdb(base):
_style = None
_theme = None
_history_file = None
_ipython_history_file = None
_latest_search_arg = ""
def __init__(
self,
completekey="tab",
stdin=None,
stdout=None,
skip=None,
nosigint=False,
readrc=True,
):
init_kwargs = (
{"out": stdout}
if is_celery
else {
"completekey": completekey,
"stdin": stdin,
"stdout": stdout,
"skip": skip,
"nosigint": nosigint,
"readrc": readrc,
}
)
if console is not None:
self._console = console
if context is not None:
if base == Pdb:
raise ValueError("Context can only be used with IPython")
init_kwargs["context"] = context
super().__init__(**init_kwargs)
self.prompt = "(Pdbr) "
def pt_init(self, pt_session_options=None):
from prompt_toolkit.history import FileHistory
if self._ipython_history_file:
history_file = FileHistory(self._ipython_history_file)
self.shell.debugger_history = history_file
# In order to fix the error for ipython 8.x
self.debugger_history = history_file
func = super().pt_init
func_args = inspect.getfullargspec(super().pt_init).args
if "pt_session_options" in func_args:
func(pt_session_options)
else:
func()
@property
def console(self):
if not hasattr(self, "_console"):
self._console = Console(
file=(
AsciiStdout(buffer=self.stdout.buffer, encoding="ascii")
if is_celery
else self.stdout
),
theme=Theme(
{"info": "dim cyan", "warning": "magenta", "danger": "bold red"}
),
style=self._style,
force_terminal=True,
force_interactive=True,
)
return self._console
def do_help(self, arg):
super().do_help(arg)
if not arg:
self._print(
Panel(
"Visit "
"[bold][link=https://github.com/cansarigol/pdbr]"
"https://github.com/cansarigol/pdbr[/link][/]"
" for more!"
),
style="warning",
print_layout=False,
)
do_help.__doc__ = base.do_help.__doc__
do_h = do_help
def _get_syntax_for_list(self, line_range=None):
if not line_range:
first = max(1, self.curframe.f_lineno - 5)
line_range = first, first + 10
filename = self.curframe.f_code.co_filename
highlight_lines = {self.curframe.f_lineno}
return Syntax.from_path(
filename,
line_numbers=True,
theme=self._theme or DEFAULT_THEME,
line_range=line_range,
highlight_lines=highlight_lines,
)
def _get_variables(self):
try:
return [
(k, str(v), str(type(v)))
for k, v in self.curframe.f_locals.items()
if not k.startswith("__") and k != "pdbr"
]
except AttributeError:
return []
def do_l(self, arg):
"""l
List 11 lines source code for the current file.
"""
try:
self._print(self._get_syntax_for_list(), print_layout=False)
except BaseException:
self.error("could not get source code")
def do_longlist(self, arg):
"""longlist | ll
List the whole source code for the current function or frame.
"""
try:
lines, lineno = self._getsourcelines(self.curframe)
last = lineno + len(lines)
self._print(
self._get_syntax_for_list((lineno, last)), print_layout=False
)
except BaseException:
self.error("could not get source code")
do_ll = do_longlist
def do_source(self, arg):
"""source expression
Try to get source code for the given object and display it.
"""
try:
obj = self._getval(arg)
lines, lineno = self._getsourcelines(obj)
last = lineno + len(lines)
self._print(
self._get_syntax_for_list((lineno, last)), print_layout=False
)
except BaseException as err:
self.error(err)
def do_search(self, arg):
"""search | src
Search a phrase in the current frame.
In order to repeat the last one, type `/` character as arg.
"""
if not arg or (arg == "/" and not self._latest_search_arg):
self.error("Search failed: arg is missing")
return
if arg == "/":
arg = self._latest_search_arg
else:
self._latest_search_arg = arg
lines, lineno = self._getsourcelines(self.curframe)
indexes = [index for index, line in enumerate(lines, lineno) if arg in line]
if len(indexes) > 0:
bigger_indexes = [
index for index in indexes if index > self.curframe.f_lineno
]
next_line = bigger_indexes[0] if bigger_indexes else indexes[0]
return super().do_jump(next_line)
else:
self.error(f"Search failed: '{arg}' not found")
do_src = do_search
def _getsourcelines(self, obj):
lines, lineno = inspect.getsourcelines(obj)
lineno = max(1, lineno)
return lines, lineno
def get_varstable(self):
variables = self._get_variables()
if not variables:
return
table = Table(title="List of local variables", box=box.MINIMAL)
table.add_column("Variable", style="cyan")
table.add_column("Value", style="magenta")
table.add_column("Type", style="green")
[
table.add_row(variable, value, _type)
for variable, value, _type in variables
]
return table
def do_v(self, arg):
"""v(ars)
List of local variables
"""
self._print(self.get_varstable(), print_layout=False)
def get_varstree(self):
variables = self._get_variables()
if not variables:
return
tree_key = ""
type_tree = None
tree = Tree("Variables")
for variable, value, _type in sorted(
variables, key=lambda item: (item[2], item[0])
):
if tree_key != _type:
if tree_key != "":
tree.add(type_tree, style="bold green")
type_tree = Tree(_type)
tree_key = _type
type_tree.add(f"{variable}: {value}", style="magenta")
if type_tree:
tree.add(type_tree, style="bold green")
return tree
def do_varstree(self, arg):
"""varstree | vt
List of local variables in Rich.Tree
"""
self._print(self.get_varstree(), print_layout=False)
do_vt = do_varstree
def do_inspect(self, arg, all=False):
"""(i)nspect
Display the data / methods / docs for any Python object.
"""
try:
self._print(
Inspect(self._getval(arg), methods=True, all=all),
print_layout=False,
)
except BaseException:
pass
def do_inspectall(self, arg):
"""inspectall | ia
Inspect with all to see all attributes.
"""
self.do_inspect(arg, all=True)
do_i = do_inspect
do_ia = do_inspectall
def do_pp(self, arg):
"""pp expression
Rich pretty print.
"""
try:
pprint(self._getval(arg), console=self.console)
except BaseException:
pass
def do_syntax(self, arg):
"""syn(tax)[ val,lexer ]
Display lexer. https://pygments.org/docs/lexers/
"""
try:
val, lexer = arg.split(",")
val = val.strip()
lexer = lexer.strip()
val = Syntax(
self._getval(val),
self._getval(lexer),
theme=self._theme or DEFAULT_THEME,
)
self._print(val)
except BaseException:
pass
do_syn = do_syntax
def do_sql(self, arg):
"""sql
Display value in sql format.
"""
try:
import sqlparse
val = sqlparse.format(
self._getval(arg), reindent=True, keyword_case="upper"
)
self._print(val)
except ModuleNotFoundError as error:
raise type(error)("Install sqlparse to see sql format.") from error
def displayhook(self, obj):
if obj is not None:
self._print(obj if isinstance(obj, (dict, list)) else repr(obj))
def error(self, msg):
self._print(msg, prefix="***", style="danger", print_layout=False)
def _format_stack_entry(self, frame_lineno):
stack_entry = Pdb.format_stack_entry(self, frame_lineno, "\n")
return stack_entry.replace(str(Path.cwd().absolute()), "")
def stack_trace(self):
stacks = []
try:
for frame_lineno in self.stack:
frame, _ = frame_lineno
if frame is self.curframe:
prefix = "-> "
else:
prefix = " "
stack_entry = self._format_stack_entry(frame_lineno)
first_line, _ = stack_entry.splitlines()
text_body = Text(stack_entry)
text_prefix = Text(prefix)
text_body.stylize("bold", len(first_line), len(stack_entry))
text_prefix.stylize("bold")
stacks.append(Text.assemble(text_prefix, text_body))
except KeyboardInterrupt:
pass
return reversed(stacks)
def message(self, msg):
"this is used by the upstream PDB class"
self._print(msg)
def precmd(self, line):
# Python 3.13+: Ctrl-D comes as literal "EOF"
if line is None or line == "EOF":
return "continue"
if line.endswith("??"):
line = "pinfo2 " + line[:-2]
elif line.endswith("?"):
line = "pinfo " + line[:-1]
elif line.startswith("%"):
if line.startswith("%%"):
self.error(
"Cell magics (multiline) are not yet supported. "
"Use a single '%' instead."
)
return self.run_magic(line[1:])
return super().precmd(line)
def _print(self, val, prefix=None, style=None, print_layout=True):
if val == "--Return--":
return
if isinstance(val, str) and ("[0m" in val or "[/" in val):
val = markup.render(val)
kwargs = {"style": str(style)} if style else {}
args = (prefix, val) if prefix else (val,)
if (
show_layouts
and print_layout
and self.lastcmd not in WITHOUT_LAYOUT_COMMANDS
):
self._print_layout(*args, **kwargs)
else:
self.console.print(*args, **kwargs)
def _print_layout(self, val, **kwargs):
ConsoleLayout(self.console).print(
val,
code=self._get_syntax_for_list(),
stack_trace=self.stack_trace(**kwargs),
vars=self.get_varstree(),
**kwargs,
)
def print_stack_entry(self, frame_lineno, prompt_prefix="\n-> "):
def print_syntax(*args):
# Remove color format.
self._print(
Syntax(
ANSI_ESCAPE.sub("", self.format_stack_entry(*args)),
"python",
theme=self._theme or DEFAULT_THEME,
),
print_layout=False,
)
if is_celery:
Pdb.print_stack_entry(self, frame_lineno, prompt_prefix)
elif base == Pdb:
print_syntax(frame_lineno, prompt_prefix)
else:
print_syntax(frame_lineno, "")
# vds: >>
frame, lineno = frame_lineno
filename = frame.f_code.co_filename
self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
# vds: <<
def print_stack_trace(self, count):
"""
Use pdb stack trace due to hide hidden frames
IPython is not using traceback count (for only python3.14),
"""
Pdb.print_stack_trace(self, count=count)
def run_magic(self, line) -> str:
"""
Parses the line and runs the appropriate magic function.
Assumes that the line is without a leading '%'.
"""
magic_name, arg, line = self.parseline(line)
if hasattr(self, f"do_{magic_name}"):
# We want to use do_{magic_name} methods if defined.
# This is indeed the case with do_pdef, do_pdoc etc,
# which are defined by our base class (IPython.core.debugger.Pdb).
result = getattr(self, f"do_{magic_name}")(arg)
else:
magic_fn = self.shell.find_line_magic(magic_name)
if not magic_fn:
self.error(f"Line Magic %{magic_name} not found")
return ""
if magic_name in ("time", "timeit"):
result = magic_fn(
arg,
local_ns={**self.curframe_locals, **self.curframe.f_globals},
)
else:
result = magic_fn(arg)
if result:
result = str(result)
self._print(result)
return ""
return RichPdb
================================================
FILE: pdbr/cli.py
================================================
import sys
from telnetlib import Telnet
from rich.file_proxy import FileProxy
from pdbr.helpers import run_ipython_shell
def shell():
import getopt
_, args = getopt.getopt(sys.argv[1:], "mhc:", ["command="])
if not args:
run_ipython_shell()
else:
from pdbr.__main__ import main
main()
def telnet():
from pdbr.__main__ import RichPdb
pdb_cls = RichPdb()
if len(sys.argv) < 3:
pdb_cls.error("Usage : pdbr_telnet hostname port")
sys.exit()
class MyTelnet(Telnet):
def fill_rawq(self):
"""
exactly the same with Telnet.fill_rawq,
buffer size is just changed from 50 to 1024.
"""
if self.irawq >= len(self.rawq):
self.rawq = b""
self.irawq = 0
buf = self.sock.recv(1024)
self.msg("recv %r", buf)
self.eof = not buf
self.rawq = self.rawq + buf
console = pdb_cls.console
sys.stdout = FileProxy(console, sys.stdout)
sys.stderr = FileProxy(console, sys.stderr)
try:
host = sys.argv[1]
port = int(sys.argv[2])
with MyTelnet(host, port) as tn:
tn.interact()
except BaseException as e:
pdb_cls.error(e)
sys.exit()
================================================
FILE: pdbr/helpers.py
================================================
import sys
from pdbr.__main__ import RichPdb
def run_ipython_shell():
try:
from IPython.terminal.interactiveshell import TerminalInteractiveShell
from IPython.terminal.ipapp import TerminalIPythonApp
from prompt_toolkit.history import FileHistory
from traitlets import Type
TerminalInteractiveShell.simple_prompt = False
except ModuleNotFoundError as error:
raise type(error)(
"In order to use pdbr shell, install IPython with pdbr[ipython]"
) from error
class PdbrTerminalInteractiveShell(TerminalInteractiveShell):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if RichPdb._ipython_history_file:
self.debugger_history = FileHistory(RichPdb._ipython_history_file)
@property
def debugger_cls(self):
return RichPdb
class PdbrTerminalIPythonApp(TerminalIPythonApp):
interactive_shell_class = Type(
klass=object, # use default_value otherwise which only allow subclasses.
default_value=PdbrTerminalInteractiveShell,
help=(
"Class to use to instantiate the TerminalInteractiveShell object. "
"Useful for custom Frontends"
),
).tag(config=True)
app = PdbrTerminalIPythonApp.instance()
app.initialize()
sys.exit(app.start())
================================================
FILE: pdbr/middlewares/__init__.py
================================================
================================================
FILE: pdbr/middlewares/django.py
================================================
import sys
from django.conf import settings
from django.core.exceptions import MiddlewareNotUsed
from pdbr.__main__ import post_mortem
class PdbrMiddleware:
def __init__(self, get_response):
if not settings.DEBUG:
raise MiddlewareNotUsed()
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception): # noqa: F841
post_mortem(sys.exc_info()[2])
================================================
FILE: pdbr/middlewares/starlette.py
================================================
from starlette.middleware.errors import ServerErrorMiddleware
from pdbr._cm import apdbr_context
class PdbrMiddleware(ServerErrorMiddleware):
async def __call__(self, scope, receive, send) -> None:
async with apdbr_context(suppress_exc=False, debug=self.debug):
await super().__call__(scope, receive, send)
================================================
FILE: pdbr/runner.py
================================================
import unittest
from django.test.runner import DebugSQLTextTestResult, DiscoverRunner
from pdbr.__main__ import RichPdb, post_mortem
class PDBRDebugResult(unittest.TextTestResult):
_pdbr = RichPdb()
def addError(self, test, err):
super().addError(test, err)
self._print(test, err)
def addFailure(self, test, err):
super().addFailure(test, err)
self._print(test, err)
def _print(self, test, err):
self.buffer = False
self._pdbr.message(f"\n{test}")
self._pdbr.error("%s: %s", err[0].__name__, err[1])
post_mortem(err[2])
class PdbrDiscoverRunner(DiscoverRunner):
def get_resultclass(self):
if self.debug_sql:
return DebugSQLTextTestResult
return PDBRDebugResult
================================================
FILE: pdbr/utils.py
================================================
import atexit
import configparser
import os
from pathlib import Path
from pdbr._pdbr import rich_pdb_klass
try:
import readline
except ImportError:
try:
from pyreadline3 import Readline
readline = Readline()
except ModuleNotFoundError:
readline = None
except AttributeError:
readline = None
def set_history_file(history_file):
"""
This is just for Pdb,
For Ipython, look at RichPdb.pt_init
"""
if readline is None:
return
try:
readline.read_history_file(history_file)
readline.set_history_length(1000)
except FileNotFoundError:
pass
except OSError:
pass
atexit.register(readline.write_history_file, history_file)
def set_traceback(theme):
from rich.traceback import install
install(theme=theme)
def read_config():
style = None
theme = None
store_history = ".pdbr_history"
context = None
config = configparser.ConfigParser()
config.sections()
setup_filename = "setup.cfg"
xdg_config_home = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
global_config_path = xdg_config_home / "pdbr" / setup_filename
cwd_config_path = Path.cwd() / setup_filename
config_path = cwd_config_path.exists() and cwd_config_path or global_config_path
config.read(config_path)
if "pdbr" in config:
if "style" in config["pdbr"]:
style = config["pdbr"]["style"]
if "theme" in config["pdbr"]:
theme = config["pdbr"]["theme"]
if "use_traceback" in config["pdbr"]:
if config["pdbr"]["use_traceback"].lower() == "true":
set_traceback(theme)
else:
set_traceback(theme)
if "store_history" in config["pdbr"]:
store_history = config["pdbr"]["store_history"]
if "context" in config["pdbr"]:
context = config["pdbr"]["context"]
history_file = str(Path.home() / store_history)
set_history_file(history_file)
ipython_history_file = f"{history_file}_ipython"
return style, theme, history_file, ipython_history_file, context
def debugger_cls(
klass=None, console=None, context=None, is_celery=False, show_layouts=True
):
if klass is None:
try:
from IPython.terminal.debugger import TerminalPdb
klass = TerminalPdb
except ImportError:
from pdb import Pdb
klass = Pdb
style, theme, history_file, ipython_history_file, config_context = read_config()
RichPdb = rich_pdb_klass(
klass,
console=console,
context=context if context is not None else config_context,
is_celery=is_celery,
show_layouts=show_layouts,
)
RichPdb._style = style
RichPdb._theme = theme
RichPdb._history_file = history_file
RichPdb._ipython_history_file = ipython_history_file
return RichPdb
def _pdbr_cls(console=None, context=None, return_instance=True, show_layouts=True):
klass = debugger_cls(console=console, context=context, show_layouts=show_layouts)
if return_instance:
return klass()
return klass
def _rdbr_cls(return_instance=True):
try:
from celery.contrib import rdb
rdb.BANNER = """\
{self.ident}: Type `pdbr_telnet {self.host} {self.port}` to connect
{self.ident}: Waiting for client...
"""
except ModuleNotFoundError as error:
raise type(error)("In order to install celery, use pdbr[celery]") from error
klass = debugger_cls(klass=rdb.Rdb, is_celery=True, show_layouts=False)
if return_instance:
return klass()
return klass
================================================
FILE: pyproject.toml
================================================
[tool.poetry]
name = "pdbr"
version = "0.9.7"
description = "Pdb with Rich library."
authors = ["Can Sarigol <ertugrulsarigol@gmail.com>"]
packages = [
{ include = "pdbr" }
]
readme = "README.md"
homepage = "https://github.com/cansarigol/pdbr"
repository = "https://github.com/cansarigol/pdbr"
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Operating System :: Microsoft :: Windows",
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
[tool.poetry.dependencies]
python = "^3.7.9"
rich = "*"
ipython = {version = "*", optional = true}
pyreadline3 = {version = "^3.4.1", markers = "sys_platform == 'win32'"}
[tool.poetry.extras]
ipython = ["ipython"]
[tool.poetry.scripts]
pdbr = 'pdbr.cli:shell'
pdbr_telnet = 'pdbr.cli:telnet'
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.5"
nox = "^2024.4.15"
[build-system]
requires = ["poetry-core>=1.2.0"]
build-backend = "poetry.core.masonry.api"
[tool.vulture]
make_whitelist = true
min_confidence = 80
paths = ["pdbr", "tests"]
sort_by_size = true
verbose = false
[project]
name = "pdbr"
version = "0.9.7"
[tool.setuptools]
py-modules = []
[tool.ruff]
line-length = 88
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"PIE", # flake8-pie
"ERA", # eradicate
]
================================================
FILE: runtests.py
================================================
import os
import sys
import django
from django.conf import settings
from django.test.utils import get_runner
if __name__ == "__main__":
os.environ["DJANGO_SETTINGS_MODULE"] = "tests.tests_django.test_settings"
django.setup()
TestRunner = get_runner(settings)
test_runner = TestRunner()
failures = test_runner.run_tests(["tests"])
sys.exit(bool(failures))
================================================
FILE: scripts/lint
================================================
#!/bin/sh -e
export SOURCE_FILES="pdbr tests noxfile.py"
ruff check $SOURCE_FILES --fix
black $SOURCE_FILES
================================================
FILE: scripts/test
================================================
#!/bin/sh -e
pre-commit run --all-files
poetry run nox --sessions test django_test
================================================
FILE: setup.cfg
================================================
[tool:pytest]
addopts = --capture=no --disable-warnings
[pdbr]
use_traceback= True
style=dim
store_history=.pdbr_history
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/conftest.py
================================================
"""
Add '--skip-slow' cmdline option to skip tests that are marked with @pytest.mark.slow.
"""
import pytest
def pytest_addoption(parser):
parser.addoption(
"--skip-slow", action="store_true", default=False, help="Skip slow tests"
)
def pytest_collection_modifyitems(config, items):
if not config.getoption("--skip-slow"):
return
skip_slow = pytest.mark.skip(reason="Specified --skip-slow")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)
================================================
FILE: tests/test_api.py
================================================
import pdbr
def test_api_attr():
assert pdbr.__all__ == [
"set_trace",
"run",
"pm",
"post_mortem",
"celery_set_trace",
"RichPdb",
"pdbr_context",
"apdbr_context",
]
================================================
FILE: tests/test_config.py
================================================
import os
from pathlib import Path
from tempfile import TemporaryDirectory
import pytest
from pdbr.utils import read_config
root_dir = Path(__file__).parents[1]
@pytest.fixture
def dummy_global_config():
XDG_CONFIG_HOME = Path.home() / ".config"
pdbr_dir = XDG_CONFIG_HOME / "pdbr"
pdbr_dir.mkdir(exist_ok=True, parents=True)
setup_file = pdbr_dir / "setup.cfg"
backup_file = pdbr_dir / (setup_file.stem + ".cfg.bak")
if setup_file.exists():
setup_file.rename(backup_file)
with open(setup_file, "wt") as f:
f.writelines(["[pdbr]\n", "theme = ansi_light"])
yield setup_file
setup_file.unlink()
if backup_file.exists():
backup_file.rename(setup_file)
def test_global_config(dummy_global_config):
assert dummy_global_config.exists()
tmpdir = TemporaryDirectory()
os.chdir(tmpdir.name)
# Second element of tuple is theme
assert read_config()[1] == "ansi_light"
os.chdir(root_dir)
def test_local_config():
tmpdir = TemporaryDirectory()
os.chdir(tmpdir.name)
setup_file = Path(tmpdir.name) / "setup.cfg"
with open(setup_file, "wt") as f:
f.writelines(["[pdbr]\n", "theme = ansi_dark"])
assert read_config()[1] == "ansi_dark"
os.chdir(root_dir)
def test_read_config():
pdbr_history = str(Path.home() / ".pdbr_history")
pdbr_history_ipython = str(Path.home() / ".pdbr_history_ipython")
assert read_config() == ("dim", None, pdbr_history, pdbr_history_ipython, None)
================================================
FILE: tests/test_magic.py
================================================
import inspect
import re
import sys
from pathlib import Path
import pytest
from rich.console import Console
from rich.theme import Theme
from pdbr._pdbr import rich_pdb_klass
NUMBER_RE = r"[\d.e+_,-]+" # Matches 1e+03, 1.0e-03, 1_000, 1,000
TAG_RE = re.compile(r"\x1b[\[\]]+[\dDClhJt;?]+m?")
def untag(s):
"""Not perfect, but does the job.
>>> untag('\x1b[0mfoo\x1b[0m\x1b[0;34m(\x1b[0m\x1b[0marg\x1b[0m\x1b[0;34m)\x1b[0m'
>>> '\x1b[0;34m\x1b[0m\x1b[0;34m\x1b[0m\x1b[0m')
'foo(arg)'
"""
s = s.replace("\x07", "")
s = s.replace("\x1b[?2004l", "")
return TAG_RE.sub("", s)
def unquote(s):
"""
>>> unquote('"foo"')
'foo'
>>> unquote('"foo"bar')
'"foo"bar'
"""
for quote in ('"', "'"):
if s.startswith(quote) and s.endswith(quote):
return s[1:-1]
return s
TMP_FILE_CONTENT = '''def foo(arg):
"""Foo docstring"""
pass
'''
def import_tmp_file(rpdb, tmp_path: Path, file_content=TMP_FILE_CONTENT) -> Path:
"""Creates a temporary file, writes `file_content` to it and makes pdbr import it"""
tmp_file = tmp_path / "foo.py"
tmp_file.write_text(file_content)
rpdb.precmd(f'import sys; sys.path.append("{tmp_file.parent.absolute()}")')
rpdb.precmd(f"from {tmp_file.stem} import foo")
return tmp_file
@pytest.fixture
def pdbr_child_process(tmp_path):
"""
Spawn a pdbr prompt in a child process.
"""
from pexpect import spawn
file = tmp_path / "foo.py"
file.write_text("import pdbr;breakpoint()")
child = spawn(
str(Path(sys.executable)),
[str(file)],
encoding="utf-8",
)
child.expect("breakpoint")
child.timeout = 10
return child
@pytest.fixture
def RichIPdb():
"""
In contrast to the normal RichPdb in test_pdbr.py which inherits from
built-in pdb.Pdb, this one inherits from IPython's TerminalPdb, which holds
a 'shell' attribute that is a IPython TerminalInteractiveShell.
This is required for the magic commands to work (and happens automatically
when the user runs pdbr when IPython is importable).
"""
from IPython.terminal.debugger import TerminalPdb
currentframe = inspect.currentframe()
def rich_ipdb_klass(*args, **kwargs):
ripdb = rich_pdb_klass(TerminalPdb, show_layouts=False)(*args, **kwargs)
# Set frame and stack related self-attributes
ripdb.botframe = currentframe.f_back
ripdb.setup(currentframe.f_back, None)
# Set the console's file to stdout so that we can capture the output
_console = Console(
file=kwargs.get("stdout", sys.stdout),
theme=Theme(
{"info": "dim cyan", "warning": "magenta", "danger": "bold red"}
),
)
ripdb._console = _console
return ripdb
return rich_ipdb_klass
@pytest.mark.skipif(sys.platform.startswith("win"), reason="pexpect")
@pytest.mark.slow
class TestPdbrChildProcess:
def test_time(self, pdbr_child_process):
pdbr_child_process.sendline("from time import sleep")
pdbr_child_process.sendline("%time sleep(0.1)")
pdbr_child_process.expect(re.compile("CPU times: .+"))
pdbr_child_process.expect("Wall time: .+")
def test_timeit(self, pdbr_child_process):
pdbr_child_process.sendline("%timeit -n 1 -r 1 pass")
pdbr_child_process.expect_exact("std. dev. of 1 run, 1 loop each)")
@pytest.mark.skipif(sys.platform.startswith("win"), reason="pexpect")
class TestPdbrMagic:
def test_onecmd_time_line_magic(self, capsys, RichIPdb):
RichIPdb().precmd("%time pass")
captured = capsys.readouterr()
output = captured.out
assert re.search(
f"CPU times: user {NUMBER_RE} [mµn]s, "
f"sys: {NUMBER_RE} [mµn]s, "
f"total: {NUMBER_RE} [mµn]s\n"
f"Wall time: {NUMBER_RE} [mµn]s",
output,
)
def test_onecmd_unsupported_cell_magic(self, capsys, RichIPdb):
RichIPdb().precmd("%%time pass")
captured = capsys.readouterr()
output = captured.out
error = (
"Cell magics (multiline) are not yet supported. Use a single '%' instead."
)
assert output == "*** " + error + "\n"
cmd = "%%time"
stop = RichIPdb().precmd(cmd)
captured_output = capsys.readouterr().out
assert not stop
RichIPdb().error(error)
cell_magics_error = capsys.readouterr().out
assert cell_magics_error == captured_output
def test_onecmd_lsmagic_line_magic(self, capsys, RichIPdb):
RichIPdb().precmd("%lsmagic")
captured = capsys.readouterr()
output = captured.out
assert re.search(
"Available line magics:\n%alias +%alias_magic +%autoawait.*%%writefile",
output,
re.DOTALL,
)
def test_no_zombie_lastcmd(self, capsys, RichIPdb):
rpdb = RichIPdb(stdout=sys.stdout)
rpdb.precmd("print('SHOULD_NOT_BE_IN_%pwd_OUTPUT')")
captured = capsys.readouterr()
assert captured.out.endswith(
"SHOULD_NOT_BE_IN_%pwd_OUTPUT\n"
) # Starts with colors and prompt
rpdb.precmd("%pwd")
captured = capsys.readouterr()
assert captured.out.endswith(Path.cwd().absolute().as_posix() + "\n")
assert "SHOULD_NOT_BE_IN_%pwd_OUTPUT" not in captured.out
def test_IPython_Pdb_magics_implementation(self, tmp_path, capsys, RichIPdb):
"""
We test do_{magic} methods that are concretely implemented by
IPython.core.debugger.Pdb, and don't default to IPython's
'InteractiveShell.run_line_magic()' like the other magics.
"""
from IPython.utils.text import dedent
rpdb = RichIPdb(stdout=sys.stdout)
tmp_file = import_tmp_file(rpdb, tmp_path)
# pdef
rpdb.do_pdef("foo")
do_pdef_foo_output = capsys.readouterr().out
untagged = untag(do_pdef_foo_output).strip()
assert untagged.endswith("foo(arg)"), untagged
rpdb.precmd("%pdef foo")
magic_pdef_foo_output = capsys.readouterr().out
untagged = untag(magic_pdef_foo_output).strip()
assert untagged.endswith("foo(arg)"), untagged
# pdoc
rpdb.precmd("%pdoc foo")
magic_pdef_foo_output = capsys.readouterr().out
untagged = untag(magic_pdef_foo_output).strip()
expected_docstring = dedent(
"""Class docstring:
Foo docstring
Call docstring:
Call self as a function."""
)
assert untagged == expected_docstring, untagged
# pfile
rpdb.precmd("%pfile foo")
magic_pfile_foo_output = capsys.readouterr().out
untagged = untag(magic_pfile_foo_output).strip()
tmp_file_content = Path(tmp_file).read_text().strip()
assert untagged == tmp_file_content
# pinfo
rpdb.precmd("%pinfo foo")
magic_pinfo_foo_output = capsys.readouterr().out
untagged = untag(magic_pinfo_foo_output).strip()
expected_pinfo = dedent(
f"""Signature: foo(arg)
Docstring: Foo docstring
File: {tmp_file.absolute()}
Type: function"""
)
assert untagged == expected_pinfo, untagged
# pinfo2
rpdb.precmd("%pinfo2 foo")
magic_pinfo2_foo_output = capsys.readouterr().out
untagged = untag(magic_pinfo2_foo_output).strip()
expected_pinfo2 = re.compile(
dedent(
rf"""Signature: foo\(arg\)
Source:\s*
%s
File: {tmp_file.absolute()}
Type: function"""
)
% re.escape(tmp_file_content)
)
assert expected_pinfo2.fullmatch(untagged), untagged
# psource
rpdb.precmd("%psource foo")
magic_psource_foo_output = capsys.readouterr().out
untagged = untag(magic_psource_foo_output).strip()
expected_psource = 'def foo(arg):\n """Foo docstring"""\n pass'
assert untagged == expected_psource, untagged
def test_expr_questionmark_pinfo(self, tmp_path, capsys, RichIPdb):
from IPython.utils.text import dedent
rpdb = RichIPdb(stdout=sys.stdout)
tmp_file = import_tmp_file(rpdb, tmp_path)
# pinfo
rpdb.precmd(rpdb.precmd("foo?"))
magic_foo_qmark_output = capsys.readouterr().out
untagged = untag(magic_foo_qmark_output).strip()
expected_pinfo_path = (
f"/private/var/folders/.*/{tmp_file.name}"
if sys.platform == "darwin"
else f"/tmp/.*/{tmp_file.name}"
)
expected_pinfo = re.compile(
dedent(
rf""".*Signature: foo\(arg\)
Docstring: Foo docstring
File: {expected_pinfo_path}
Type: function"""
)
)
assert expected_pinfo.fullmatch(untagged), f"untagged = {untagged!r}"
# pinfo2
rpdb.precmd(rpdb.precmd("foo??"))
magic_foo_qmark2_output = capsys.readouterr().out
rpdb.precmd(rpdb.precmd("%pinfo2 foo"))
magic_pinfo2_foo_output = capsys.readouterr().out
assert magic_pinfo2_foo_output == magic_foo_qmark2_output
def test_filesystem_magics(self, capsys, RichIPdb):
cwd = Path.cwd().absolute().as_posix()
rpdb = RichIPdb(stdout=sys.stdout)
rpdb.precmd("%pwd")
pwd_output = capsys.readouterr().out.strip()
assert pwd_output == cwd
rpdb.precmd("import os; os.getcwd()")
pwd_output = unquote(capsys.readouterr().out.strip())
assert pwd_output == cwd
new_dir = str(Path.cwd().absolute().parent)
rpdb.precmd(f"%cd {new_dir}")
cd_output = untag(capsys.readouterr().out.strip())
assert cd_output.endswith(new_dir)
rpdb.precmd("%pwd")
pwd_output = capsys.readouterr().out.strip()
assert pwd_output == new_dir
rpdb.precmd("import os; os.getcwd()")
pwd_output = unquote(capsys.readouterr().out.strip())
assert pwd_output == new_dir
================================================
FILE: tests/test_pdbr.py
================================================
import inspect
import pdb
import pytest
from pdbr._pdbr import rich_pdb_klass
@pytest.fixture
def RichPdb(*args, **kwargs):
currentframe = inspect.currentframe()
def wrapper():
rpdb = rich_pdb_klass(pdb.Pdb, show_layouts=False)(*args, **kwargs)
# Set frame and stack related self-attributes
rpdb.botframe = currentframe.f_back
rpdb.setup(currentframe.f_back, None)
return rpdb
return wrapper
def test_prompt(RichPdb):
assert RichPdb().prompt == "(Pdbr) "
def test_print(capsys, RichPdb):
RichPdb()._print("msg")
captured = capsys.readouterr()
assert captured.out == "msg\n"
def test_print_error(capsys, RichPdb):
RichPdb().error("error")
captured = capsys.readouterr()
assert captured.out == "\x1b[1;31m*** error\x1b[0m\n"
def test_print_with_style(capsys, RichPdb):
RichPdb()._print("msg", style="yellow")
captured = capsys.readouterr()
assert captured.out == "\x1b[33mmsg\x1b[0m\n"
def test_print_without_escape_tag(capsys, RichPdb):
RichPdb()._print("[blue]msg[/]")
captured = capsys.readouterr()
assert captured.out == "\x1b[34mmsg\x1b[0m\n"
def test_print_array(capsys, RichPdb):
RichPdb()._print("[[8]]")
captured = capsys.readouterr()
assert (
captured.out == "\x1b[1m[\x1b[0m\x1b[1m[\x1b[0m\x1b[1;36m8"
"\x1b[0m\x1b[1m]\x1b[0m\x1b[1m]\x1b[0m\n"
)
def test_onecmd(capsys, RichPdb):
rpdb = RichPdb()
cmd = 'print("msg")'
stop = rpdb.precmd(cmd)
captured = capsys.readouterr()
assert not stop
assert captured.out == "msg\n"
================================================
FILE: tests/tests_django/__init__.py
================================================
================================================
FILE: tests/tests_django/test_settings.py
================================================
from pathlib import Path
BASE_DIR = Path(__file__).absolute().parents[1]
SECRET_KEY = "fake-key"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": str(BASE_DIR / "db.sqlite3"),
}
}
INSTALLED_APPS = ("tests.tests_django",)
TEST_RUNNER = "pdbr.runner.PdbrDiscoverRunner"
ROOT_URLCONF = "tests.tests_django.urls"
MIDDLEWARE = ["pdbr.middlewares.django.PdbrMiddleware"]
================================================
FILE: tests/tests_django/tests.py
================================================
from django.test import TestCase
class DjangoTest(TestCase):
def test_runner(self):
self.assertEqual("foo", "foo")
def test_middleware(self):
response = self.client.get("")
self.assertEqual(response.status_code, 200)
================================================
FILE: tests/tests_django/urls.py
================================================
from django.http import HttpResponse
from django.urls import path
urlpatterns = [
path("", lambda request: HttpResponse()),
]
gitextract_rmp4r7u5/
├── .github/
│ └── workflows/
│ ├── release.yml
│ └── tests.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .vscode/
│ └── settings.json
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── noxfile.py
├── pdbr/
│ ├── __init__.py
│ ├── __main__.py
│ ├── _cm.py
│ ├── _console_layout.py
│ ├── _pdbr.py
│ ├── cli.py
│ ├── helpers.py
│ ├── middlewares/
│ │ ├── __init__.py
│ │ ├── django.py
│ │ └── starlette.py
│ ├── runner.py
│ └── utils.py
├── pyproject.toml
├── runtests.py
├── scripts/
│ ├── lint
│ └── test
├── setup.cfg
└── tests/
├── __init__.py
├── conftest.py
├── test_api.py
├── test_config.py
├── test_magic.py
├── test_pdbr.py
└── tests_django/
├── __init__.py
├── test_settings.py
├── tests.py
└── urls.py
SYMBOL INDEX (81 symbols across 17 files)
FILE: noxfile.py
function test (line 7) | def test(session, reuse_venv=True):
function django_test (line 29) | def django_test(session, django, reuse_venv=True):
FILE: pdbr/__main__.py
function set_trace (line 12) | def set_trace(*, console=None, header=None, context=None, show_layouts=F...
function run (line 19) | def run(statement, globals=None, locals=None):
function post_mortem (line 23) | def post_mortem(traceback=None, value=None):
function pm (line 40) | def pm():
function celery_set_trace (line 44) | def celery_set_trace(frame=None):
function main (line 51) | def main():
FILE: pdbr/_cm.py
class pdbr_context (line 7) | class pdbr_context(ContextDecorator):
method __init__ (line 8) | def __init__(self, suppress_exc=True, debug=True):
method __enter__ (line 12) | def __enter__(self):
method __exit__ (line 15) | def __exit__(self, _, exc_value, exc_traceback):
class AsyncContextDecorator (line 22) | class AsyncContextDecorator(ContextDecorator):
method __call__ (line 23) | def __call__(self, func):
class apdbr_context (line 32) | class apdbr_context(AsyncContextDecorator):
method __init__ (line 33) | def __init__(self, suppress_exc=True, debug=True):
method __aenter__ (line 37) | async def __aenter__(self):
method __aexit__ (line 40) | async def __aexit__(self, _, exc_value, exc_traceback):
FILE: pdbr/_console_layout.py
class ConsoleLayoutMeta (line 7) | class ConsoleLayoutMeta(type):
method __call__ (line 10) | def __call__(cls, *args, **kwargs):
class ConsoleLayout (line 17) | class ConsoleLayout(metaclass=ConsoleLayoutMeta):
method __init__ (line 18) | def __init__(self, console):
method _prep_layout (line 22) | def _prep_layout(self):
method print (line 37) | def print(self, message, code, stack_trace, vars, **kwargs):
FILE: pdbr/_pdbr.py
class AsciiStdout (line 34) | class AsciiStdout(io.TextIOWrapper):
function rich_pdb_klass (line 38) | def rich_pdb_klass(
FILE: pdbr/cli.py
function shell (line 9) | def shell():
function telnet (line 22) | def telnet():
FILE: pdbr/helpers.py
function run_ipython_shell (line 6) | def run_ipython_shell():
FILE: pdbr/middlewares/django.py
class PdbrMiddleware (line 9) | class PdbrMiddleware:
method __init__ (line 10) | def __init__(self, get_response):
method __call__ (line 15) | def __call__(self, request):
method process_exception (line 18) | def process_exception(self, request, exception): # noqa: F841
FILE: pdbr/middlewares/starlette.py
class PdbrMiddleware (line 6) | class PdbrMiddleware(ServerErrorMiddleware):
method __call__ (line 7) | async def __call__(self, scope, receive, send) -> None:
FILE: pdbr/runner.py
class PDBRDebugResult (line 8) | class PDBRDebugResult(unittest.TextTestResult):
method addError (line 11) | def addError(self, test, err):
method addFailure (line 15) | def addFailure(self, test, err):
method _print (line 19) | def _print(self, test, err):
class PdbrDiscoverRunner (line 26) | class PdbrDiscoverRunner(DiscoverRunner):
method get_resultclass (line 27) | def get_resultclass(self):
FILE: pdbr/utils.py
function set_history_file (line 21) | def set_history_file(history_file):
function set_traceback (line 39) | def set_traceback(theme):
function read_config (line 45) | def read_config():
function debugger_cls (line 87) | def debugger_cls(
function _pdbr_cls (line 116) | def _pdbr_cls(console=None, context=None, return_instance=True, show_lay...
function _rdbr_cls (line 123) | def _rdbr_cls(return_instance=True):
FILE: tests/conftest.py
function pytest_addoption (line 8) | def pytest_addoption(parser):
function pytest_collection_modifyitems (line 14) | def pytest_collection_modifyitems(config, items):
FILE: tests/test_api.py
function test_api_attr (line 4) | def test_api_attr():
FILE: tests/test_config.py
function dummy_global_config (line 13) | def dummy_global_config():
function test_global_config (line 34) | def test_global_config(dummy_global_config):
function test_local_config (line 45) | def test_local_config():
function test_read_config (line 57) | def test_read_config():
FILE: tests/test_magic.py
function untag (line 17) | def untag(s):
function unquote (line 28) | def unquote(s):
function import_tmp_file (line 47) | def import_tmp_file(rpdb, tmp_path: Path, file_content=TMP_FILE_CONTENT)...
function pdbr_child_process (line 58) | def pdbr_child_process(tmp_path):
function RichIPdb (line 78) | def RichIPdb():
class TestPdbrChildProcess (line 110) | class TestPdbrChildProcess:
method test_time (line 111) | def test_time(self, pdbr_child_process):
method test_timeit (line 117) | def test_timeit(self, pdbr_child_process):
class TestPdbrMagic (line 123) | class TestPdbrMagic:
method test_onecmd_time_line_magic (line 124) | def test_onecmd_time_line_magic(self, capsys, RichIPdb):
method test_onecmd_unsupported_cell_magic (line 136) | def test_onecmd_unsupported_cell_magic(self, capsys, RichIPdb):
method test_onecmd_lsmagic_line_magic (line 152) | def test_onecmd_lsmagic_line_magic(self, capsys, RichIPdb):
method test_no_zombie_lastcmd (line 163) | def test_no_zombie_lastcmd(self, capsys, RichIPdb):
method test_IPython_Pdb_magics_implementation (line 175) | def test_IPython_Pdb_magics_implementation(self, tmp_path, capsys, Ric...
method test_expr_questionmark_pinfo (line 250) | def test_expr_questionmark_pinfo(self, tmp_path, capsys, RichIPdb):
method test_filesystem_magics (line 282) | def test_filesystem_magics(self, capsys, RichIPdb):
FILE: tests/test_pdbr.py
function RichPdb (line 10) | def RichPdb(*args, **kwargs):
function test_prompt (line 23) | def test_prompt(RichPdb):
function test_print (line 27) | def test_print(capsys, RichPdb):
function test_print_error (line 33) | def test_print_error(capsys, RichPdb):
function test_print_with_style (line 39) | def test_print_with_style(capsys, RichPdb):
function test_print_without_escape_tag (line 45) | def test_print_without_escape_tag(capsys, RichPdb):
function test_print_array (line 51) | def test_print_array(capsys, RichPdb):
function test_onecmd (line 60) | def test_onecmd(capsys, RichPdb):
FILE: tests/tests_django/tests.py
class DjangoTest (line 4) | class DjangoTest(TestCase):
method test_runner (line 5) | def test_runner(self):
method test_middleware (line 8) | def test_middleware(self):
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (65K chars).
[
{
"path": ".github/workflows/release.yml",
"chars": 560,
"preview": "name: Release\n\non:\n push:\n tags:\n - '*'\n workflow_dispatch:\n\njobs:\n build-n-publish:\n name: Build and publ"
},
{
"path": ".github/workflows/tests.yml",
"chars": 1047,
"preview": "name: Test\n\non:\n push:\n branches: [\"master\"]\n pull_request:\n branches: [\"master\"]\n\njobs:\n check:\n name: \"Che"
},
{
"path": ".gitignore",
"chars": 2055,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": ".pre-commit-config.yaml",
"chars": 779,
"preview": "repos:\n - repo: https://github.com/pre-commit/pre-commit-hooks\n rev: v5.0.0\n hooks:\n - id: check-added-large"
},
{
"path": ".vscode/settings.json",
"chars": 54,
"preview": "{\n \"makefile.extensionOutputFolder\": \"./.vscode\"\n}\n"
},
{
"path": "Dockerfile",
"chars": 206,
"preview": "FROM python:3.7.9\n\nENV PYTHONUNBUFFERED=0\n\nRUN pip install pip \\\n && pip install nox \\\n && pip install pre-commit\n\nWORKD"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2020 Can Sarıgöl\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "Makefile",
"chars": 187,
"preview": "lint:\n\tsh scripts/lint\n\ntest:\n\tsh scripts/test\n\ncelery:\n\tcelery -A tasks worker --loglevel=info\n\nbuild:\n\tdocker build -t"
},
{
"path": "README.md",
"chars": 7916,
"preview": "# pdbr\n\n[](https://pypi.org/project/pdbr/) [:\n session.instal"
},
{
"path": "pdbr/__init__.py",
"chars": 286,
"preview": "from pdbr.__main__ import RichPdb, celery_set_trace, pm, post_mortem, run, set_trace\nfrom pdbr._cm import apdbr_context,"
},
{
"path": "pdbr/__main__.py",
"chars": 1270,
"preview": "import os\nimport pdb\nimport sys\n\nfrom .utils import _pdbr_cls, _rdbr_cls\n\nos.environ[\"PYTHONBREAKPOINT\"] = \"pdbr.set_tra"
},
{
"path": "pdbr/_cm.py",
"chars": 1206,
"preview": "from contextlib import ContextDecorator\nfrom functools import wraps\n\nfrom pdbr.__main__ import post_mortem\n\n\nclass pdbr_"
},
{
"path": "pdbr/_console_layout.py",
"chars": 1485,
"preview": "from rich.containers import Lines\nfrom rich.errors import NotRenderableError\nfrom rich.layout import Layout\nfrom rich.pa"
},
{
"path": "pdbr/_pdbr.py",
"chars": 16375,
"preview": "import inspect\nimport io\nimport re\nfrom pathlib import Path\nfrom pdb import Pdb\n\nfrom rich import box, markup\nfrom rich."
},
{
"path": "pdbr/cli.py",
"chars": 1306,
"preview": "import sys\nfrom telnetlib import Telnet\n\nfrom rich.file_proxy import FileProxy\n\nfrom pdbr.helpers import run_ipython_she"
},
{
"path": "pdbr/helpers.py",
"chars": 1421,
"preview": "import sys\n\nfrom pdbr.__main__ import RichPdb\n\n\ndef run_ipython_shell():\n try:\n from IPython.terminal.interact"
},
{
"path": "pdbr/middlewares/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pdbr/middlewares/django.py",
"chars": 492,
"preview": "import sys\n\nfrom django.conf import settings\nfrom django.core.exceptions import MiddlewareNotUsed\n\nfrom pdbr.__main__ im"
},
{
"path": "pdbr/middlewares/starlette.py",
"chars": 334,
"preview": "from starlette.middleware.errors import ServerErrorMiddleware\n\nfrom pdbr._cm import apdbr_context\n\n\nclass PdbrMiddleware"
},
{
"path": "pdbr/runner.py",
"chars": 782,
"preview": "import unittest\n\nfrom django.test.runner import DebugSQLTextTestResult, DiscoverRunner\n\nfrom pdbr.__main__ import RichPd"
},
{
"path": "pdbr/utils.py",
"chars": 3653,
"preview": "import atexit\nimport configparser\nimport os\nfrom pathlib import Path\n\nfrom pdbr._pdbr import rich_pdb_klass\n\ntry:\n im"
},
{
"path": "pyproject.toml",
"chars": 1769,
"preview": "[tool.poetry]\nname = \"pdbr\"\nversion = \"0.9.7\"\ndescription = \"Pdb with Rich library.\"\nauthors = [\"Can Sarigol <ertugrulsa"
},
{
"path": "runtests.py",
"chars": 381,
"preview": "import os\nimport sys\n\nimport django\nfrom django.conf import settings\nfrom django.test.utils import get_runner\n\nif __name"
},
{
"path": "scripts/lint",
"chars": 110,
"preview": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"pdbr tests noxfile.py\"\n\nruff check $SOURCE_FILES --fix\nblack $SOURCE_FILES\n"
},
{
"path": "scripts/test",
"chars": 84,
"preview": "#!/bin/sh -e\npre-commit run --all-files\n\npoetry run nox --sessions test django_test\n"
},
{
"path": "setup.cfg",
"chars": 122,
"preview": "[tool:pytest]\naddopts = --capture=no --disable-warnings\n\n[pdbr]\nuse_traceback= True\nstyle=dim\nstore_history=.pdbr_histor"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/conftest.py",
"chars": 526,
"preview": "\"\"\"\nAdd '--skip-slow' cmdline option to skip tests that are marked with @pytest.mark.slow.\n\"\"\"\n\nimport pytest\n\n\ndef pyte"
},
{
"path": "tests/test_api.py",
"chars": 239,
"preview": "import pdbr\n\n\ndef test_api_attr():\n assert pdbr.__all__ == [\n \"set_trace\",\n \"run\",\n \"pm\",\n "
},
{
"path": "tests/test_config.py",
"chars": 1511,
"preview": "import os\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\n\nimport pytest\n\nfrom pdbr.utils import read_c"
},
{
"path": "tests/test_magic.py",
"chars": 10185,
"preview": "import inspect\nimport re\nimport sys\nfrom pathlib import Path\n\nimport pytest\nfrom rich.console import Console\nfrom rich.t"
},
{
"path": "tests/test_pdbr.py",
"chars": 1609,
"preview": "import inspect\nimport pdb\n\nimport pytest\n\nfrom pdbr._pdbr import rich_pdb_klass\n\n\n@pytest.fixture\ndef RichPdb(*args, **k"
},
{
"path": "tests/tests_django/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/tests_django/test_settings.py",
"chars": 421,
"preview": "from pathlib import Path\n\nBASE_DIR = Path(__file__).absolute().parents[1]\n\nSECRET_KEY = \"fake-key\"\nDATABASES = {\n \"de"
},
{
"path": "tests/tests_django/tests.py",
"chars": 252,
"preview": "from django.test import TestCase\n\n\nclass DjangoTest(TestCase):\n def test_runner(self):\n self.assertEqual(\"foo\""
},
{
"path": "tests/tests_django/urls.py",
"chars": 131,
"preview": "from django.http import HttpResponse\nfrom django.urls import path\n\nurlpatterns = [\n path(\"\", lambda request: HttpResp"
}
]
About this extraction
This page contains the full source code of the cansarigol/pdbr GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (59.0 KB), approximately 15.8k tokens, and a symbol index with 81 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.