[
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - '*'\n  workflow_dispatch:\n\njobs:\n  build-n-publish:\n    name: Build and publish to PyPI\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up Python 3.10.16\n      uses: actions/setup-python@v4\n      with:\n        python-version: '3.10.16'\n    - name: Install poetry\n      run: python -m pip install poetry --user\n    - name: Build\n      run: poetry build\n    - name: Publish to PyPI\n      env:\n        POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_PASSWORD }}\n      run: poetry publish\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Test\n\non:\n  push:\n    branches: [\"master\"]\n  pull_request:\n    branches: [\"master\"]\n\njobs:\n  check:\n    name: \"Check\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-python@v4\n        with:\n          python-version: '3.10'\n      - name: Install pre-commit\n        run: |\n          pip install --upgrade pre-commit\n      - name: Run check\n        run: |\n          pre-commit run --all-files\n\n  test:\n    name: \"Tests\"\n    runs-on: ${{ matrix.platform }}\n    needs: check\n\n    strategy:\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n        python-version: [\"3.8\", \"3.9\", \"3.10\", \"3.11\"]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install nox\n        run: |\n          pip install --upgrade nox\n      - name: Run tests\n        run: |\n          nox --sessions test django_test\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# lint\n.ruff_cache\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v5.0.0\n    hooks:\n      - id: check-added-large-files\n      - id: check-case-conflict\n      - id: check-merge-conflict\n      - id: check-symlinks\n      - id: check-toml\n      - id: end-of-file-fixer\n      - id: trailing-whitespace\n  - repo: https://github.com/pre-commit/pygrep-hooks\n    rev: v1.10.0\n    hooks:\n      - id: python-check-blanket-noqa\n      - id: python-use-type-annotations\n  - repo: https://github.com/jendrikseipp/vulture\n    rev: v2.14\n    hooks:\n      - id: vulture\n  - repo: https://github.com/astral-sh/ruff-pre-commit\n    rev: 'v0.9.3'\n    hooks:\n      - id: ruff\n  - repo: https://github.com/psf/black\n    rev: 24.10.0\n    hooks:\n      - id: black\n        language_version: python3\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"makefile.extensionOutputFolder\": \"./.vscode\"\n}\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM python:3.7.9\n\nENV PYTHONUNBUFFERED=0\n\nRUN pip install pip \\\n && pip install nox \\\n && pip install pre-commit\n\nWORKDIR /pdbr\nCOPY . .\n\nRUN pre-commit run --all-files\nRUN nox --sessions test django_test\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Can Sarıgöl\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "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 pdbr .\n\nact:\n\tact -r -j test --container-architecture linux/amd64\n"
  },
  {
    "path": "README.md",
    "content": "# pdbr\n\n[![PyPI version](https://badge.fury.io/py/pdbr.svg)](https://pypi.org/project/pdbr/) [![Python Version](https://img.shields.io/pypi/pyversions/pdbr.svg)](https://pypi.org/project/pdbr/) [![](https://github.com/cansarigol/pdbr/workflows/Test/badge.svg)](https://github.com/cansarigol/pdbr/actions?query=workflow%3ATest) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/cansarigol/pdbr/master.svg)](https://results.pre-commit.ci/latest/github/cansarigol/pdbr/master)\n\n\n`pdbr` is intended to make the PDB results more colorful. it uses [Rich](https://github.com/willmcgugan/rich) library to carry out that.\n\n\n## Installing\n\nInstall with `pip` or your favorite PyPi package manager.\n\n```\npip install pdbr\n```\n\n\n## Breakpoint\n\nIn order to use ```breakpoint()```, set **PYTHONBREAKPOINT** with \"pdbr.set_trace\"\n\n```python\nimport os\n\nos.environ[\"PYTHONBREAKPOINT\"] = \"pdbr.set_trace\"\n```\n\nor just import pdbr\n\n```python\nimport pdbr\n```\n\n## New commands\n### (i)nspect / inspectall | ia\n[rich.inspect](https://rich.readthedocs.io/en/latest/introduction.html?s=03#rich-inspector)\n### search | src\nSearch a phrase in the current frame.\nIn order to repeat the last one, type **/** character as arg.\n### sql\nDisplay value in sql format. Don't forget to install [sqlparse](https://github.com/andialbrecht/sqlparse) package.\n![](/images/image13.png)\n\nIt can be used for Django model queries as follows.\n```\n>>> sql str(Users.objects.all().query)\n```\n![](/images/image14.png)\n### (syn)tax\n[ val,lexer ] Display [lexer](https://pygments.org/docs/lexers/).\n### (v)ars\nGet the local variables list as table.\n### varstree | vt\nGet the local variables list as tree.\n\n![](/images/image5.png)\n\n## Config\nConfig 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`.\n\n### Style\nIn order to use Rich's traceback, style, and theme:\n\n```\n[pdbr]\nstyle = yellow\nuse_traceback = True\ntheme = friendly\n```\n\nAlso custom `Console` object can be assigned to the `set_trace`.\n```python\nimport pdbr\n\nfrom rich.console import Console\nfrom rich.style import Style\nfrom rich.theme import Theme\n\ncustom_theme = Theme({\n    \"info\": \"dim cyan\",\n    \"warning\": \"magenta\",\n    \"danger\": \"bold red\",\n})\ncustom_style = Style(\n    color=\"magenta\",\n    bgcolor=\"yellow\",\n    italic=True,\n)\nconsole = Console(theme=custom_theme, style=custom_style)\n\npdbr.set_trace(console=console)\n```\n### History\n**store_history** setting is used to keep and reload history, even the prompt is closed and opened again:\n```\n[pdbr]\n...\nstore_history=.pdbr_history\n```\n\nBy default, history is stored globally in `~/.pdbr_history`.\n\n### Context\nThe **context** setting is used to specify the number of lines of source code context to show when displaying stacktrace information.\n```\n[pdbr]\n...\ncontext=10\n```\nThis setting is only available when using `pdbr` with `IPython`.\n\n## Celery\nIn 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).\n\n```python\nfrom celery import Celery\n\napp = Celery('tasks', broker='pyamqp://guest@localhost//')\n\n@app.task\ndef add(x, y):\n\n    import pdbr; pdbr.celery_set_trace()\n\n    return x + y\n\n```\n#### Telnet\nInstead of using `telnet` or `nc`, in terms of using pdbr style, `pdbr_telnet` command can be used.\n![](/images/image6.png)\n\nAlso in order to activate history and be able to use arrow keys, install and use [rlwrap](https://github.com/hanslub42/rlwrap) package.\n\n```\nrlwrap -H '~/.pdbr_history' pdbr_telnet localhost 6899\n```\n\n## IPython\n\n`pdbr` integrates with [IPython](https://ipython.readthedocs.io/).\n\nThis makes [`%magics`](https://ipython.readthedocs.io/en/stable/interactive/magics.html) available, for example:\n\n```python\n(Pdbr) %timeit range(100)\n104 ns ± 2.05 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)\n```\n\nTo enable `IPython` features, install it separately, or like below:\n\n```\npip install pdbr[ipython]\n```\n\n## pytest\nIn order to use `pdbr` with pytest `--pdb` flag, add `addopts` setting in your pytest.ini.\n\n```\n[pytest]\naddopts: --pdbcls=pdbr:RichPdb\n```\n\n## sys.excepthook\nThe `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.\n\nIn 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:\n```python\nimport sys\nimport pdbr\n\ndef custom_excepthook(exc_type, exc_value, exc_traceback):\n    pdbr.post_mortem(exc_traceback, exc_value)\n\n    # [Optional] call the original excepthook as well\n    sys.__excepthook__(exc_type, exc_value, exc_traceback)\n\nsys.excepthook = custom_excepthook\n```\nNow, whenever an unhandled exception occurs, `pdbr` will be triggered, allowing you to debug the issue interactively.\n\n## Context Decorator\n`pdbr_context` and `apdbr_context` (`asyncio` corresponding) can be used as **with statement** or **decorator**. It calls `post_mortem` if `traceback` is not none.\n\n```python\nfrom pdbr import apdbr_context, pdbr_context\n\n@pdbr_context()\ndef foo():\n    ...\n\ndef bar():\n    with pdbr_context():\n        ...\n\n\n@apdbr_context()\nasync def foo():\n    ...\n\nasync def bar():\n    async with apdbr_context():\n        ...\n```\n\n![](/images/image12.png)\n## Django DiscoverRunner\nTo 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.\n\n```\nTEST_RUNNER = \"pdbr.runner.PdbrDiscoverRunner\"\n```\n![](/images/image10.png)\n## Middlewares\n### Starlette\n```python\nfrom fastapi import FastAPI\nfrom pdbr.middlewares.starlette import PdbrMiddleware\n\napp = FastAPI()\n\napp.add_middleware(PdbrMiddleware, debug=True)\n\n\n@app.get(\"/\")\nasync def main():\n    1 / 0\n    return {\"message\": \"Hello World\"}\n```\n### Django\nIn order to catch the problematic codes with post mortem, place the middleware class.\n\n```\nMIDDLEWARE = (\n    ...\n    \"pdbr.middlewares.django.PdbrMiddleware\",\n)\n```\n![](/images/image11.png)\n## Shell\nRunning `pdbr` command in terminal starts an `IPython` terminal app instance. Unlike default `TerminalInteractiveShell`, the new shell uses pdbr as debugger class instead of `ipdb`.\n#### %debug magic sample\n![](/images/image9.png)\n### As a Script\nIf `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.\n```python\n# equivalent code: `python -m pdbr -c 'b 5' my_test.py`\npdbr -c 'b 5' my_test.py\n\n>>> Breakpoint 1 at /my_test.py:5\n> /my_test.py(3)<module>()\n      1\n      2\n----> 3 def test():\n      4         foo = \"foo\"\n1     5         bar = \"bar\"\n\n(Pdbr)\n\n```\n### Terminal\n#### Django shell sample\n![](/images/image7.png)\n\n## Vscode user snippet\n\nTo create or edit your own snippets, select **User Snippets** under **File > Preferences** (**Code > Preferences** on macOS), and then select **python.json**.\n\nPlace the below snippet in json file for **pdbr**.\n\n```\n{\n  ...\n  \"pdbr\": {\n        \"prefix\": \"pdbr\",\n        \"body\": \"import pdbr; pdbr.set_trace()\",\n        \"description\": \"Code snippet for pdbr debug\"\n    },\n}\n```\n\nFor **Celery** debug.\n\n```\n{\n  ...\n  \"rdbr\": {\n        \"prefix\": \"rdbr\",\n        \"body\": \"import pdbr; pdbr.celery_set_trace()\",\n        \"description\": \"Code snippet for Celery pdbr debug\"\n    },\n}\n```\n\n## Samples\n![](/images/image1.png)\n\n![](/images/image3.png)\n\n![](/images/image4.png)\n\n### Traceback\n![](/images/image2.png)\n"
  },
  {
    "path": "noxfile.py",
    "content": "import nox\n\nnox.options.stop_on_first_error = True\n\n\n@nox.session\ndef test(session, reuse_venv=True):\n    session.install(\n        \".\",\n        \"pytest\",\n        \"pytest-cov\",\n        \"rich\",\n        \"prompt_toolkit\",\n        \"IPython\",\n    )\n    session.run(\n        \"pytest\",\n        \"--cov-report\",\n        \"term-missing\",\n        \"--cov=pdbr\",\n        \"--capture=no\",\n        \"--disable-warnings\",\n        \"tests\",\n    )\n\n\n@nox.session\n@nox.parametrize(\"django\", [\"3.2\", \"4.2\"])\ndef django_test(session, django, reuse_venv=True):\n    session.install(f\"django=={django}\", \"rich\", \"pytest\")\n    session.run(\"python\", \"runtests.py\")\n"
  },
  {
    "path": "pdbr/__init__.py",
    "content": "from pdbr.__main__ import RichPdb, celery_set_trace, pm, post_mortem, run, set_trace\nfrom pdbr._cm import apdbr_context, pdbr_context\n\n__all__ = [\n    \"set_trace\",\n    \"run\",\n    \"pm\",\n    \"post_mortem\",\n    \"celery_set_trace\",\n    \"RichPdb\",\n    \"pdbr_context\",\n    \"apdbr_context\",\n]\n"
  },
  {
    "path": "pdbr/__main__.py",
    "content": "import os\nimport pdb\nimport sys\n\nfrom .utils import _pdbr_cls, _rdbr_cls\n\nos.environ[\"PYTHONBREAKPOINT\"] = \"pdbr.set_trace\"\n\nRichPdb = _pdbr_cls(return_instance=False, show_layouts=False)\n\n\ndef set_trace(*, console=None, header=None, context=None, show_layouts=False):\n    pdb_cls = _pdbr_cls(console=console, context=context, show_layouts=show_layouts)\n    if header is not None:\n        pdb_cls.message(header)\n    pdb_cls.set_trace(sys._getframe().f_back)\n\n\ndef run(statement, globals=None, locals=None):\n    RichPdb().run(statement, globals, locals)\n\n\ndef post_mortem(traceback=None, value=None):\n    _, sys_value, sys_traceback = sys.exc_info()\n    value = value or sys_value\n    traceback = traceback or sys_traceback\n\n    if traceback is None:\n        raise ValueError(\n            \"A valid traceback must be passed if no exception is being handled\"\n        )\n\n    p = RichPdb()\n    p.reset()\n    if value:\n        p.error(value)\n    p.interaction(None, traceback)\n\n\ndef pm():\n    post_mortem(sys.last_traceback)\n\n\ndef celery_set_trace(frame=None):\n    pdb_cls = _rdbr_cls()\n    if frame is None:\n        frame = sys._getframe().f_back\n    return pdb_cls.set_trace(frame)\n\n\ndef main():\n    pdb.Pdb = RichPdb\n    pdb.main()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "pdbr/_cm.py",
    "content": "from contextlib import ContextDecorator\nfrom functools import wraps\n\nfrom pdbr.__main__ import post_mortem\n\n\nclass pdbr_context(ContextDecorator):\n    def __init__(self, suppress_exc=True, debug=True):\n        self.suppress_exc = suppress_exc\n        self.debug = debug\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, _, exc_value, exc_traceback):\n        if exc_traceback and self.debug:\n            post_mortem(exc_traceback, exc_value)\n            return self.suppress_exc\n        return False\n\n\nclass AsyncContextDecorator(ContextDecorator):\n    def __call__(self, func):\n        @wraps(func)\n        async def inner(*args, **kwds):\n            async with self._recreate_cm():\n                return await func(*args, **kwds)\n\n        return inner\n\n\nclass apdbr_context(AsyncContextDecorator):\n    def __init__(self, suppress_exc=True, debug=True):\n        self.suppress_exc = suppress_exc\n        self.debug = debug\n\n    async def __aenter__(self):\n        return self\n\n    async def __aexit__(self, _, exc_value, exc_traceback):\n        if exc_traceback and self.debug:\n            post_mortem(exc_traceback, exc_value)\n            return self.suppress_exc\n        return False\n"
  },
  {
    "path": "pdbr/_console_layout.py",
    "content": "from rich.containers import Lines\nfrom rich.errors import NotRenderableError\nfrom rich.layout import Layout\nfrom rich.panel import Panel\n\n\nclass ConsoleLayoutMeta(type):\n    _instances = {}\n\n    def __call__(cls, *args, **kwargs):\n        if cls not in cls._instances:\n            instance = super().__call__(*args, **kwargs)\n            cls._instances[cls] = instance\n        return cls._instances[cls]\n\n\nclass ConsoleLayout(metaclass=ConsoleLayoutMeta):\n    def __init__(self, console):\n        self.console = console\n        self.layout = self._prep_layout()\n\n    def _prep_layout(self):\n        layout = Layout()\n        right_body = Layout(name=\"right_body\", ratio=1)\n\n        layout.split(\n            Layout(name=\"left_body\", ratio=2),\n            right_body,\n            splitter=\"row\",\n        )\n\n        right_body.split(\n            Layout(name=\"up_footer\", ratio=2), Layout(name=\"bottom_footer\", ratio=1)\n        )\n        return layout\n\n    def print(self, message, code, stack_trace, vars, **kwargs):\n        try:\n            self.layout[\"left_body\"].update(code)\n            self.layout[\"up_footer\"].update(Panel(vars, title=\"Locals\"))\n\n            self.layout[\"bottom_footer\"].update(\n                Panel(Lines(stack_trace), title=\"Stack\", style=\"white on blue\")\n            )\n\n            self.console.print(self.layout, **kwargs)\n            self.console.print(message, **kwargs)\n        except NotRenderableError:\n            self.console.print(message, **kwargs)\n"
  },
  {
    "path": "pdbr/_pdbr.py",
    "content": "import inspect\nimport io\nimport re\nfrom pathlib import Path\nfrom pdb import Pdb\n\nfrom rich import box, markup\nfrom rich._inspect import Inspect\nfrom rich.console import Console\nfrom rich.panel import Panel\nfrom rich.pretty import pprint\nfrom rich.syntax import DEFAULT_THEME, Syntax\nfrom rich.table import Table\nfrom rich.text import Text\nfrom rich.theme import Theme\nfrom rich.tree import Tree\n\nfrom pdbr._console_layout import ConsoleLayout\n\ntry:\n    from IPython.terminal.interactiveshell import TerminalInteractiveShell\n\n    TerminalInteractiveShell.simple_prompt = False\nexcept ImportError:\n    pass\n\nWITHOUT_LAYOUT_COMMANDS = (\n    \"where\",\n    \"w\",\n)\nANSI_ESCAPE = re.compile(r\"\\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])\")\n\n\nclass AsciiStdout(io.TextIOWrapper):\n    pass\n\n\ndef rich_pdb_klass(\n    base, is_celery=False, console=None, context=None, show_layouts=True\n):\n    class RichPdb(base):\n        _style = None\n        _theme = None\n        _history_file = None\n        _ipython_history_file = None\n        _latest_search_arg = \"\"\n\n        def __init__(\n            self,\n            completekey=\"tab\",\n            stdin=None,\n            stdout=None,\n            skip=None,\n            nosigint=False,\n            readrc=True,\n        ):\n            init_kwargs = (\n                {\"out\": stdout}\n                if is_celery\n                else {\n                    \"completekey\": completekey,\n                    \"stdin\": stdin,\n                    \"stdout\": stdout,\n                    \"skip\": skip,\n                    \"nosigint\": nosigint,\n                    \"readrc\": readrc,\n                }\n            )\n            if console is not None:\n                self._console = console\n            if context is not None:\n                if base == Pdb:\n                    raise ValueError(\"Context can only be used with IPython\")\n                init_kwargs[\"context\"] = context\n            super().__init__(**init_kwargs)\n\n            self.prompt = \"(Pdbr) \"\n\n        def pt_init(self, pt_session_options=None):\n            from prompt_toolkit.history import FileHistory\n\n            if self._ipython_history_file:\n                history_file = FileHistory(self._ipython_history_file)\n                self.shell.debugger_history = history_file\n                # In order to fix the error for ipython 8.x\n                self.debugger_history = history_file\n\n            func = super().pt_init\n            func_args = inspect.getfullargspec(super().pt_init).args\n            if \"pt_session_options\" in func_args:\n                func(pt_session_options)\n            else:\n                func()\n\n        @property\n        def console(self):\n            if not hasattr(self, \"_console\"):\n                self._console = Console(\n                    file=(\n                        AsciiStdout(buffer=self.stdout.buffer, encoding=\"ascii\")\n                        if is_celery\n                        else self.stdout\n                    ),\n                    theme=Theme(\n                        {\"info\": \"dim cyan\", \"warning\": \"magenta\", \"danger\": \"bold red\"}\n                    ),\n                    style=self._style,\n                    force_terminal=True,\n                    force_interactive=True,\n                )\n            return self._console\n\n        def do_help(self, arg):\n            super().do_help(arg)\n            if not arg:\n                self._print(\n                    Panel(\n                        \"Visit \"\n                        \"[bold][link=https://github.com/cansarigol/pdbr]\"\n                        \"https://github.com/cansarigol/pdbr[/link][/]\"\n                        \" for more!\"\n                    ),\n                    style=\"warning\",\n                    print_layout=False,\n                )\n\n        do_help.__doc__ = base.do_help.__doc__\n        do_h = do_help\n\n        def _get_syntax_for_list(self, line_range=None):\n            if not line_range:\n                first = max(1, self.curframe.f_lineno - 5)\n                line_range = first, first + 10\n            filename = self.curframe.f_code.co_filename\n            highlight_lines = {self.curframe.f_lineno}\n\n            return Syntax.from_path(\n                filename,\n                line_numbers=True,\n                theme=self._theme or DEFAULT_THEME,\n                line_range=line_range,\n                highlight_lines=highlight_lines,\n            )\n\n        def _get_variables(self):\n            try:\n                return [\n                    (k, str(v), str(type(v)))\n                    for k, v in self.curframe.f_locals.items()\n                    if not k.startswith(\"__\") and k != \"pdbr\"\n                ]\n            except AttributeError:\n                return []\n\n        def do_l(self, arg):\n            \"\"\"l\n            List 11 lines source code for the current file.\n            \"\"\"\n            try:\n                self._print(self._get_syntax_for_list(), print_layout=False)\n            except BaseException:\n                self.error(\"could not get source code\")\n\n        def do_longlist(self, arg):\n            \"\"\"longlist | ll\n            List the whole source code for the current function or frame.\n            \"\"\"\n            try:\n                lines, lineno = self._getsourcelines(self.curframe)\n                last = lineno + len(lines)\n                self._print(\n                    self._get_syntax_for_list((lineno, last)), print_layout=False\n                )\n            except BaseException:\n                self.error(\"could not get source code\")\n\n        do_ll = do_longlist\n\n        def do_source(self, arg):\n            \"\"\"source expression\n            Try to get source code for the given object and display it.\n            \"\"\"\n            try:\n                obj = self._getval(arg)\n                lines, lineno = self._getsourcelines(obj)\n                last = lineno + len(lines)\n                self._print(\n                    self._get_syntax_for_list((lineno, last)), print_layout=False\n                )\n            except BaseException as err:\n                self.error(err)\n\n        def do_search(self, arg):\n            \"\"\"search | src\n            Search a phrase in the current frame.\n            In order to repeat the last one, type `/` character as arg.\n            \"\"\"\n            if not arg or (arg == \"/\" and not self._latest_search_arg):\n                self.error(\"Search failed: arg is missing\")\n                return\n\n            if arg == \"/\":\n                arg = self._latest_search_arg\n            else:\n                self._latest_search_arg = arg\n\n            lines, lineno = self._getsourcelines(self.curframe)\n            indexes = [index for index, line in enumerate(lines, lineno) if arg in line]\n\n            if len(indexes) > 0:\n                bigger_indexes = [\n                    index for index in indexes if index > self.curframe.f_lineno\n                ]\n                next_line = bigger_indexes[0] if bigger_indexes else indexes[0]\n                return super().do_jump(next_line)\n            else:\n                self.error(f\"Search failed: '{arg}' not found\")\n\n        do_src = do_search\n\n        def _getsourcelines(self, obj):\n            lines, lineno = inspect.getsourcelines(obj)\n            lineno = max(1, lineno)\n            return lines, lineno\n\n        def get_varstable(self):\n            variables = self._get_variables()\n            if not variables:\n                return\n            table = Table(title=\"List of local variables\", box=box.MINIMAL)\n\n            table.add_column(\"Variable\", style=\"cyan\")\n            table.add_column(\"Value\", style=\"magenta\")\n            table.add_column(\"Type\", style=\"green\")\n            [\n                table.add_row(variable, value, _type)\n                for variable, value, _type in variables\n            ]\n            return table\n\n        def do_v(self, arg):\n            \"\"\"v(ars)\n            List of local variables\n            \"\"\"\n            self._print(self.get_varstable(), print_layout=False)\n\n        def get_varstree(self):\n            variables = self._get_variables()\n            if not variables:\n                return\n            tree_key = \"\"\n            type_tree = None\n            tree = Tree(\"Variables\")\n\n            for variable, value, _type in sorted(\n                variables, key=lambda item: (item[2], item[0])\n            ):\n                if tree_key != _type:\n                    if tree_key != \"\":\n                        tree.add(type_tree, style=\"bold green\")\n                    type_tree = Tree(_type)\n                    tree_key = _type\n                type_tree.add(f\"{variable}: {value}\", style=\"magenta\")\n            if type_tree:\n                tree.add(type_tree, style=\"bold green\")\n            return tree\n\n        def do_varstree(self, arg):\n            \"\"\"varstree | vt\n            List of local variables in Rich.Tree\n            \"\"\"\n            self._print(self.get_varstree(), print_layout=False)\n\n        do_vt = do_varstree\n\n        def do_inspect(self, arg, all=False):\n            \"\"\"(i)nspect\n            Display the data / methods / docs for any Python object.\n            \"\"\"\n            try:\n                self._print(\n                    Inspect(self._getval(arg), methods=True, all=all),\n                    print_layout=False,\n                )\n            except BaseException:\n                pass\n\n        def do_inspectall(self, arg):\n            \"\"\"inspectall | ia\n            Inspect with all to see all attributes.\n            \"\"\"\n            self.do_inspect(arg, all=True)\n\n        do_i = do_inspect\n        do_ia = do_inspectall\n\n        def do_pp(self, arg):\n            \"\"\"pp expression\n            Rich pretty print.\n            \"\"\"\n            try:\n                pprint(self._getval(arg), console=self.console)\n            except BaseException:\n                pass\n\n        def do_syntax(self, arg):\n            \"\"\"syn(tax)[ val,lexer ]\n            Display lexer. https://pygments.org/docs/lexers/\n            \"\"\"\n            try:\n                val, lexer = arg.split(\",\")\n                val = val.strip()\n                lexer = lexer.strip()\n                val = Syntax(\n                    self._getval(val),\n                    self._getval(lexer),\n                    theme=self._theme or DEFAULT_THEME,\n                )\n                self._print(val)\n            except BaseException:\n                pass\n\n        do_syn = do_syntax\n\n        def do_sql(self, arg):\n            \"\"\"sql\n            Display value in sql format.\n            \"\"\"\n            try:\n                import sqlparse\n\n                val = sqlparse.format(\n                    self._getval(arg), reindent=True, keyword_case=\"upper\"\n                )\n                self._print(val)\n            except ModuleNotFoundError as error:\n                raise type(error)(\"Install sqlparse to see sql format.\") from error\n\n        def displayhook(self, obj):\n            if obj is not None:\n                self._print(obj if isinstance(obj, (dict, list)) else repr(obj))\n\n        def error(self, msg):\n            self._print(msg, prefix=\"***\", style=\"danger\", print_layout=False)\n\n        def _format_stack_entry(self, frame_lineno):\n            stack_entry = Pdb.format_stack_entry(self, frame_lineno, \"\\n\")\n            return stack_entry.replace(str(Path.cwd().absolute()), \"\")\n\n        def stack_trace(self):\n            stacks = []\n            try:\n                for frame_lineno in self.stack:\n                    frame, _ = frame_lineno\n                    if frame is self.curframe:\n                        prefix = \"-> \"\n                    else:\n                        prefix = \"  \"\n\n                    stack_entry = self._format_stack_entry(frame_lineno)\n                    first_line, _ = stack_entry.splitlines()\n                    text_body = Text(stack_entry)\n                    text_prefix = Text(prefix)\n                    text_body.stylize(\"bold\", len(first_line), len(stack_entry))\n                    text_prefix.stylize(\"bold\")\n                    stacks.append(Text.assemble(text_prefix, text_body))\n            except KeyboardInterrupt:\n                pass\n            return reversed(stacks)\n\n        def message(self, msg):\n            \"this is used by the upstream PDB class\"\n            self._print(msg)\n\n        def precmd(self, line):\n            # Python 3.13+: Ctrl-D comes as literal \"EOF\"\n            if line is None or line == \"EOF\":\n                return \"continue\"\n\n            if line.endswith(\"??\"):\n                line = \"pinfo2 \" + line[:-2]\n            elif line.endswith(\"?\"):\n                line = \"pinfo \" + line[:-1]\n            elif line.startswith(\"%\"):\n                if line.startswith(\"%%\"):\n                    self.error(\n                        \"Cell magics (multiline) are not yet supported. \"\n                        \"Use a single '%' instead.\"\n                    )\n                return self.run_magic(line[1:])\n\n            return super().precmd(line)\n\n        def _print(self, val, prefix=None, style=None, print_layout=True):\n            if val == \"--Return--\":\n                return\n\n            if isinstance(val, str) and (\"[0m\" in val or \"[/\" in val):\n                val = markup.render(val)\n\n            kwargs = {\"style\": str(style)} if style else {}\n            args = (prefix, val) if prefix else (val,)\n            if (\n                show_layouts\n                and print_layout\n                and self.lastcmd not in WITHOUT_LAYOUT_COMMANDS\n            ):\n                self._print_layout(*args, **kwargs)\n            else:\n                self.console.print(*args, **kwargs)\n\n        def _print_layout(self, val, **kwargs):\n            ConsoleLayout(self.console).print(\n                val,\n                code=self._get_syntax_for_list(),\n                stack_trace=self.stack_trace(**kwargs),\n                vars=self.get_varstree(),\n                **kwargs,\n            )\n\n        def print_stack_entry(self, frame_lineno, prompt_prefix=\"\\n-> \"):\n            def print_syntax(*args):\n                # Remove color format.\n                self._print(\n                    Syntax(\n                        ANSI_ESCAPE.sub(\"\", self.format_stack_entry(*args)),\n                        \"python\",\n                        theme=self._theme or DEFAULT_THEME,\n                    ),\n                    print_layout=False,\n                )\n\n            if is_celery:\n                Pdb.print_stack_entry(self, frame_lineno, prompt_prefix)\n            elif base == Pdb:\n                print_syntax(frame_lineno, prompt_prefix)\n            else:\n                print_syntax(frame_lineno, \"\")\n\n                # vds: >>\n                frame, lineno = frame_lineno\n                filename = frame.f_code.co_filename\n                self.shell.hooks.synchronize_with_editor(filename, lineno, 0)\n                # vds: <<\n\n        def print_stack_trace(self, count):\n            \"\"\"\n            Use pdb stack trace due to hide hidden frames\n            IPython is not using traceback count (for only python3.14),\n            \"\"\"\n            Pdb.print_stack_trace(self, count=count)\n\n        def run_magic(self, line) -> str:\n            \"\"\"\n            Parses the line and runs the appropriate magic function.\n            Assumes that the line is without a leading '%'.\n            \"\"\"\n            magic_name, arg, line = self.parseline(line)\n            if hasattr(self, f\"do_{magic_name}\"):\n                # We want to use do_{magic_name} methods if defined.\n                # This is indeed the case with do_pdef, do_pdoc etc,\n                # which are defined by our base class (IPython.core.debugger.Pdb).\n                result = getattr(self, f\"do_{magic_name}\")(arg)\n            else:\n                magic_fn = self.shell.find_line_magic(magic_name)\n                if not magic_fn:\n                    self.error(f\"Line Magic %{magic_name} not found\")\n                    return \"\"\n                if magic_name in (\"time\", \"timeit\"):\n                    result = magic_fn(\n                        arg,\n                        local_ns={**self.curframe_locals, **self.curframe.f_globals},\n                    )\n                else:\n                    result = magic_fn(arg)\n            if result:\n                result = str(result)\n                self._print(result)\n            return \"\"\n\n    return RichPdb\n"
  },
  {
    "path": "pdbr/cli.py",
    "content": "import sys\nfrom telnetlib import Telnet\n\nfrom rich.file_proxy import FileProxy\n\nfrom pdbr.helpers import run_ipython_shell\n\n\ndef shell():\n    import getopt\n\n    _, args = getopt.getopt(sys.argv[1:], \"mhc:\", [\"command=\"])\n\n    if not args:\n        run_ipython_shell()\n    else:\n        from pdbr.__main__ import main\n\n        main()\n\n\ndef telnet():\n    from pdbr.__main__ import RichPdb\n\n    pdb_cls = RichPdb()\n    if len(sys.argv) < 3:\n        pdb_cls.error(\"Usage : pdbr_telnet hostname port\")\n        sys.exit()\n\n    class MyTelnet(Telnet):\n        def fill_rawq(self):\n            \"\"\"\n            exactly the same with Telnet.fill_rawq,\n            buffer size is just changed from 50 to 1024.\n            \"\"\"\n            if self.irawq >= len(self.rawq):\n                self.rawq = b\"\"\n                self.irawq = 0\n            buf = self.sock.recv(1024)\n            self.msg(\"recv %r\", buf)\n            self.eof = not buf\n            self.rawq = self.rawq + buf\n\n    console = pdb_cls.console\n    sys.stdout = FileProxy(console, sys.stdout)\n    sys.stderr = FileProxy(console, sys.stderr)\n    try:\n        host = sys.argv[1]\n        port = int(sys.argv[2])\n        with MyTelnet(host, port) as tn:\n            tn.interact()\n    except BaseException as e:\n        pdb_cls.error(e)\n        sys.exit()\n"
  },
  {
    "path": "pdbr/helpers.py",
    "content": "import sys\n\nfrom pdbr.__main__ import RichPdb\n\n\ndef run_ipython_shell():\n    try:\n        from IPython.terminal.interactiveshell import TerminalInteractiveShell\n        from IPython.terminal.ipapp import TerminalIPythonApp\n        from prompt_toolkit.history import FileHistory\n        from traitlets import Type\n\n        TerminalInteractiveShell.simple_prompt = False\n    except ModuleNotFoundError as error:\n        raise type(error)(\n            \"In order to use pdbr shell, install IPython with pdbr[ipython]\"\n        ) from error\n\n    class PdbrTerminalInteractiveShell(TerminalInteractiveShell):\n        def __init__(self, *args, **kwargs):\n            super().__init__(*args, **kwargs)\n\n            if RichPdb._ipython_history_file:\n                self.debugger_history = FileHistory(RichPdb._ipython_history_file)\n\n        @property\n        def debugger_cls(self):\n            return RichPdb\n\n    class PdbrTerminalIPythonApp(TerminalIPythonApp):\n        interactive_shell_class = Type(\n            klass=object,  # use default_value otherwise which only allow subclasses.\n            default_value=PdbrTerminalInteractiveShell,\n            help=(\n                \"Class to use to instantiate the TerminalInteractiveShell object. \"\n                \"Useful for custom Frontends\"\n            ),\n        ).tag(config=True)\n\n    app = PdbrTerminalIPythonApp.instance()\n    app.initialize()\n    sys.exit(app.start())\n"
  },
  {
    "path": "pdbr/middlewares/__init__.py",
    "content": ""
  },
  {
    "path": "pdbr/middlewares/django.py",
    "content": "import sys\n\nfrom django.conf import settings\nfrom django.core.exceptions import MiddlewareNotUsed\n\nfrom pdbr.__main__ import post_mortem\n\n\nclass PdbrMiddleware:\n    def __init__(self, get_response):\n        if not settings.DEBUG:\n            raise MiddlewareNotUsed()\n        self.get_response = get_response\n\n    def __call__(self, request):\n        return self.get_response(request)\n\n    def process_exception(self, request, exception):  # noqa: F841\n        post_mortem(sys.exc_info()[2])\n"
  },
  {
    "path": "pdbr/middlewares/starlette.py",
    "content": "from starlette.middleware.errors import ServerErrorMiddleware\n\nfrom pdbr._cm import apdbr_context\n\n\nclass PdbrMiddleware(ServerErrorMiddleware):\n    async def __call__(self, scope, receive, send) -> None:\n        async with apdbr_context(suppress_exc=False, debug=self.debug):\n            await super().__call__(scope, receive, send)\n"
  },
  {
    "path": "pdbr/runner.py",
    "content": "import unittest\n\nfrom django.test.runner import DebugSQLTextTestResult, DiscoverRunner\n\nfrom pdbr.__main__ import RichPdb, post_mortem\n\n\nclass PDBRDebugResult(unittest.TextTestResult):\n    _pdbr = RichPdb()\n\n    def addError(self, test, err):\n        super().addError(test, err)\n        self._print(test, err)\n\n    def addFailure(self, test, err):\n        super().addFailure(test, err)\n        self._print(test, err)\n\n    def _print(self, test, err):\n        self.buffer = False\n        self._pdbr.message(f\"\\n{test}\")\n        self._pdbr.error(\"%s: %s\", err[0].__name__, err[1])\n        post_mortem(err[2])\n\n\nclass PdbrDiscoverRunner(DiscoverRunner):\n    def get_resultclass(self):\n        if self.debug_sql:\n            return DebugSQLTextTestResult\n        return PDBRDebugResult\n"
  },
  {
    "path": "pdbr/utils.py",
    "content": "import atexit\nimport configparser\nimport os\nfrom pathlib import Path\n\nfrom pdbr._pdbr import rich_pdb_klass\n\ntry:\n    import readline\nexcept ImportError:\n    try:\n        from pyreadline3 import Readline\n\n        readline = Readline()\n    except ModuleNotFoundError:\n        readline = None\nexcept AttributeError:\n    readline = None\n\n\ndef set_history_file(history_file):\n    \"\"\"\n    This is just for Pdb,\n    For Ipython, look at RichPdb.pt_init\n    \"\"\"\n    if readline is None:\n        return\n    try:\n        readline.read_history_file(history_file)\n        readline.set_history_length(1000)\n    except FileNotFoundError:\n        pass\n    except OSError:\n        pass\n\n    atexit.register(readline.write_history_file, history_file)\n\n\ndef set_traceback(theme):\n    from rich.traceback import install\n\n    install(theme=theme)\n\n\ndef read_config():\n    style = None\n    theme = None\n    store_history = \".pdbr_history\"\n    context = None\n\n    config = configparser.ConfigParser()\n    config.sections()\n\n    setup_filename = \"setup.cfg\"\n    xdg_config_home = Path(os.getenv(\"XDG_CONFIG_HOME\", Path.home() / \".config\"))\n    global_config_path = xdg_config_home / \"pdbr\" / setup_filename\n    cwd_config_path = Path.cwd() / setup_filename\n    config_path = cwd_config_path.exists() and cwd_config_path or global_config_path\n\n    config.read(config_path)\n    if \"pdbr\" in config:\n        if \"style\" in config[\"pdbr\"]:\n            style = config[\"pdbr\"][\"style\"]\n\n        if \"theme\" in config[\"pdbr\"]:\n            theme = config[\"pdbr\"][\"theme\"]\n\n        if \"use_traceback\" in config[\"pdbr\"]:\n            if config[\"pdbr\"][\"use_traceback\"].lower() == \"true\":\n                set_traceback(theme)\n        else:\n            set_traceback(theme)\n\n        if \"store_history\" in config[\"pdbr\"]:\n            store_history = config[\"pdbr\"][\"store_history\"]\n\n        if \"context\" in config[\"pdbr\"]:\n            context = config[\"pdbr\"][\"context\"]\n\n    history_file = str(Path.home() / store_history)\n    set_history_file(history_file)\n    ipython_history_file = f\"{history_file}_ipython\"\n\n    return style, theme, history_file, ipython_history_file, context\n\n\ndef debugger_cls(\n    klass=None, console=None, context=None, is_celery=False, show_layouts=True\n):\n    if klass is None:\n        try:\n            from IPython.terminal.debugger import TerminalPdb\n\n            klass = TerminalPdb\n        except ImportError:\n            from pdb import Pdb\n\n            klass = Pdb\n\n    style, theme, history_file, ipython_history_file, config_context = read_config()\n    RichPdb = rich_pdb_klass(\n        klass,\n        console=console,\n        context=context if context is not None else config_context,\n        is_celery=is_celery,\n        show_layouts=show_layouts,\n    )\n    RichPdb._style = style\n    RichPdb._theme = theme\n    RichPdb._history_file = history_file\n    RichPdb._ipython_history_file = ipython_history_file\n\n    return RichPdb\n\n\ndef _pdbr_cls(console=None, context=None, return_instance=True, show_layouts=True):\n    klass = debugger_cls(console=console, context=context, show_layouts=show_layouts)\n    if return_instance:\n        return klass()\n    return klass\n\n\ndef _rdbr_cls(return_instance=True):\n    try:\n        from celery.contrib import rdb\n\n        rdb.BANNER = \"\"\"\\\n{self.ident}: Type `pdbr_telnet {self.host} {self.port}` to connect\n\n{self.ident}: Waiting for client...\n\"\"\"\n    except ModuleNotFoundError as error:\n        raise type(error)(\"In order to install celery, use pdbr[celery]\") from error\n\n    klass = debugger_cls(klass=rdb.Rdb, is_celery=True, show_layouts=False)\n    if return_instance:\n        return klass()\n    return klass\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"pdbr\"\nversion = \"0.9.7\"\ndescription = \"Pdb with Rich library.\"\nauthors = [\"Can Sarigol <ertugrulsarigol@gmail.com>\"]\npackages = [\n    { include = \"pdbr\" }\n]\nreadme = \"README.md\"\nhomepage = \"https://github.com/cansarigol/pdbr\"\nrepository = \"https://github.com/cansarigol/pdbr\"\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Intended Audience :: Developers\",\n    \"Operating System :: Microsoft :: Windows\",\n    \"Operating System :: MacOS\",\n    \"Operating System :: POSIX :: Linux\",\n    \"Programming Language :: Python :: 3.8\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n]\n\n[tool.poetry.dependencies]\npython = \"^3.7.9\"\nrich = \"*\"\nipython = {version = \"*\", optional = true}\npyreadline3 = {version = \"^3.4.1\", markers = \"sys_platform == 'win32'\"}\n\n[tool.poetry.extras]\nipython = [\"ipython\"]\n\n[tool.poetry.scripts]\npdbr = 'pdbr.cli:shell'\npdbr_telnet = 'pdbr.cli:telnet'\n\n[tool.poetry.group.dev.dependencies]\nruff = \"^0.6.5\"\nnox = \"^2024.4.15\"\n\n[build-system]\nrequires = [\"poetry-core>=1.2.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\n[tool.vulture]\nmake_whitelist = true\nmin_confidence = 80\npaths = [\"pdbr\", \"tests\"]\nsort_by_size = true\nverbose = false\n\n[project]\nname = \"pdbr\"\nversion = \"0.9.7\"\n\n[tool.setuptools]\npy-modules = []\n\n[tool.ruff]\nline-length = 88\n\n[tool.ruff.lint]\nselect = [\n    \"E\",  # pycodestyle errors\n    \"W\",  # pycodestyle warnings\n    \"F\",  # pyflakes\n    \"I\",   # isort\n    \"B\",   # flake8-bugbear\n    \"C4\",  # flake8-comprehensions\n    \"PIE\", # flake8-pie\n    \"ERA\", # eradicate\n]\n"
  },
  {
    "path": "runtests.py",
    "content": "import os\nimport sys\n\nimport django\nfrom django.conf import settings\nfrom django.test.utils import get_runner\n\nif __name__ == \"__main__\":\n    os.environ[\"DJANGO_SETTINGS_MODULE\"] = \"tests.tests_django.test_settings\"\n    django.setup()\n    TestRunner = get_runner(settings)\n    test_runner = TestRunner()\n    failures = test_runner.run_tests([\"tests\"])\n    sys.exit(bool(failures))\n"
  },
  {
    "path": "scripts/lint",
    "content": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"pdbr tests noxfile.py\"\n\nruff check $SOURCE_FILES --fix\nblack $SOURCE_FILES\n"
  },
  {
    "path": "scripts/test",
    "content": "#!/bin/sh -e\npre-commit run --all-files\n\npoetry run nox --sessions test django_test\n"
  },
  {
    "path": "setup.cfg",
    "content": "[tool:pytest]\naddopts = --capture=no --disable-warnings\n\n[pdbr]\nuse_traceback= True\nstyle=dim\nstore_history=.pdbr_history\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/conftest.py",
    "content": "\"\"\"\nAdd '--skip-slow' cmdline option to skip tests that are marked with @pytest.mark.slow.\n\"\"\"\n\nimport pytest\n\n\ndef pytest_addoption(parser):\n    parser.addoption(\n        \"--skip-slow\", action=\"store_true\", default=False, help=\"Skip slow tests\"\n    )\n\n\ndef pytest_collection_modifyitems(config, items):\n    if not config.getoption(\"--skip-slow\"):\n        return\n    skip_slow = pytest.mark.skip(reason=\"Specified --skip-slow\")\n    for item in items:\n        if \"slow\" in item.keywords:\n            item.add_marker(skip_slow)\n"
  },
  {
    "path": "tests/test_api.py",
    "content": "import pdbr\n\n\ndef test_api_attr():\n    assert pdbr.__all__ == [\n        \"set_trace\",\n        \"run\",\n        \"pm\",\n        \"post_mortem\",\n        \"celery_set_trace\",\n        \"RichPdb\",\n        \"pdbr_context\",\n        \"apdbr_context\",\n    ]\n"
  },
  {
    "path": "tests/test_config.py",
    "content": "import os\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\n\nimport pytest\n\nfrom pdbr.utils import read_config\n\nroot_dir = Path(__file__).parents[1]\n\n\n@pytest.fixture\ndef dummy_global_config():\n    XDG_CONFIG_HOME = Path.home() / \".config\"\n    pdbr_dir = XDG_CONFIG_HOME / \"pdbr\"\n    pdbr_dir.mkdir(exist_ok=True, parents=True)\n    setup_file = pdbr_dir / \"setup.cfg\"\n    backup_file = pdbr_dir / (setup_file.stem + \".cfg.bak\")\n\n    if setup_file.exists():\n        setup_file.rename(backup_file)\n\n    with open(setup_file, \"wt\") as f:\n        f.writelines([\"[pdbr]\\n\", \"theme = ansi_light\"])\n\n    yield setup_file\n\n    setup_file.unlink()\n\n    if backup_file.exists():\n        backup_file.rename(setup_file)\n\n\ndef test_global_config(dummy_global_config):\n    assert dummy_global_config.exists()\n\n    tmpdir = TemporaryDirectory()\n    os.chdir(tmpdir.name)\n\n    # Second element of tuple is theme\n    assert read_config()[1] == \"ansi_light\"\n    os.chdir(root_dir)\n\n\ndef test_local_config():\n    tmpdir = TemporaryDirectory()\n    os.chdir(tmpdir.name)\n    setup_file = Path(tmpdir.name) / \"setup.cfg\"\n\n    with open(setup_file, \"wt\") as f:\n        f.writelines([\"[pdbr]\\n\", \"theme = ansi_dark\"])\n\n    assert read_config()[1] == \"ansi_dark\"\n    os.chdir(root_dir)\n\n\ndef test_read_config():\n    pdbr_history = str(Path.home() / \".pdbr_history\")\n    pdbr_history_ipython = str(Path.home() / \".pdbr_history_ipython\")\n\n    assert read_config() == (\"dim\", None, pdbr_history, pdbr_history_ipython, None)\n"
  },
  {
    "path": "tests/test_magic.py",
    "content": "import inspect\nimport re\nimport sys\nfrom pathlib import Path\n\nimport pytest\nfrom rich.console import Console\nfrom rich.theme import Theme\n\nfrom pdbr._pdbr import rich_pdb_klass\n\nNUMBER_RE = r\"[\\d.e+_,-]+\"  # Matches 1e+03, 1.0e-03, 1_000, 1,000\n\nTAG_RE = re.compile(r\"\\x1b[\\[\\]]+[\\dDClhJt;?]+m?\")\n\n\ndef untag(s):\n    \"\"\"Not perfect, but does the job.\n    >>> untag('\\x1b[0mfoo\\x1b[0m\\x1b[0;34m(\\x1b[0m\\x1b[0marg\\x1b[0m\\x1b[0;34m)\\x1b[0m'\n    >>>       '\\x1b[0;34m\\x1b[0m\\x1b[0;34m\\x1b[0m\\x1b[0m')\n    'foo(arg)'\n    \"\"\"\n    s = s.replace(\"\\x07\", \"\")\n    s = s.replace(\"\\x1b[?2004l\", \"\")\n    return TAG_RE.sub(\"\", s)\n\n\ndef unquote(s):\n    \"\"\"\n    >>> unquote('\"foo\"')\n    'foo'\n    >>> unquote('\"foo\"bar')\n    '\"foo\"bar'\n    \"\"\"\n    for quote in ('\"', \"'\"):\n        if s.startswith(quote) and s.endswith(quote):\n            return s[1:-1]\n    return s\n\n\nTMP_FILE_CONTENT = '''def foo(arg):\n    \"\"\"Foo docstring\"\"\"\n    pass\n    '''\n\n\ndef import_tmp_file(rpdb, tmp_path: Path, file_content=TMP_FILE_CONTENT) -> Path:\n    \"\"\"Creates a temporary file, writes `file_content` to it and makes pdbr import it\"\"\"\n    tmp_file = tmp_path / \"foo.py\"\n    tmp_file.write_text(file_content)\n\n    rpdb.precmd(f'import sys; sys.path.append(\"{tmp_file.parent.absolute()}\")')\n    rpdb.precmd(f\"from {tmp_file.stem} import foo\")\n    return tmp_file\n\n\n@pytest.fixture\ndef pdbr_child_process(tmp_path):\n    \"\"\"\n    Spawn a pdbr prompt in a child process.\n    \"\"\"\n    from pexpect import spawn\n\n    file = tmp_path / \"foo.py\"\n    file.write_text(\"import pdbr;breakpoint()\")\n\n    child = spawn(\n        str(Path(sys.executable)),\n        [str(file)],\n        encoding=\"utf-8\",\n    )\n    child.expect(\"breakpoint\")\n    child.timeout = 10\n    return child\n\n\n@pytest.fixture\ndef RichIPdb():\n    \"\"\"\n    In contrast to the normal RichPdb in test_pdbr.py which inherits from\n    built-in pdb.Pdb, this one inherits from IPython's TerminalPdb, which holds\n    a 'shell' attribute that is a IPython TerminalInteractiveShell.\n    This is required for the magic commands to work (and happens automatically\n    when the user runs pdbr when IPython is importable).\n    \"\"\"\n    from IPython.terminal.debugger import TerminalPdb\n\n    currentframe = inspect.currentframe()\n\n    def rich_ipdb_klass(*args, **kwargs):\n        ripdb = rich_pdb_klass(TerminalPdb, show_layouts=False)(*args, **kwargs)\n        # Set frame and stack related self-attributes\n        ripdb.botframe = currentframe.f_back\n        ripdb.setup(currentframe.f_back, None)\n        # Set the console's file to stdout so that we can capture the output\n        _console = Console(\n            file=kwargs.get(\"stdout\", sys.stdout),\n            theme=Theme(\n                {\"info\": \"dim cyan\", \"warning\": \"magenta\", \"danger\": \"bold red\"}\n            ),\n        )\n        ripdb._console = _console\n        return ripdb\n\n    return rich_ipdb_klass\n\n\n@pytest.mark.skipif(sys.platform.startswith(\"win\"), reason=\"pexpect\")\n@pytest.mark.slow\nclass TestPdbrChildProcess:\n    def test_time(self, pdbr_child_process):\n        pdbr_child_process.sendline(\"from time import sleep\")\n        pdbr_child_process.sendline(\"%time sleep(0.1)\")\n        pdbr_child_process.expect(re.compile(\"CPU times: .+\"))\n        pdbr_child_process.expect(\"Wall time: .+\")\n\n    def test_timeit(self, pdbr_child_process):\n        pdbr_child_process.sendline(\"%timeit -n 1 -r 1 pass\")\n        pdbr_child_process.expect_exact(\"std. dev. of 1 run, 1 loop each)\")\n\n\n@pytest.mark.skipif(sys.platform.startswith(\"win\"), reason=\"pexpect\")\nclass TestPdbrMagic:\n    def test_onecmd_time_line_magic(self, capsys, RichIPdb):\n        RichIPdb().precmd(\"%time pass\")\n        captured = capsys.readouterr()\n        output = captured.out\n        assert re.search(\n            f\"CPU times: user {NUMBER_RE} [mµn]s, \"\n            f\"sys: {NUMBER_RE} [mµn]s, \"\n            f\"total: {NUMBER_RE} [mµn]s\\n\"\n            f\"Wall time: {NUMBER_RE} [mµn]s\",\n            output,\n        )\n\n    def test_onecmd_unsupported_cell_magic(self, capsys, RichIPdb):\n        RichIPdb().precmd(\"%%time pass\")\n        captured = capsys.readouterr()\n        output = captured.out\n        error = (\n            \"Cell magics (multiline) are not yet supported. Use a single '%' instead.\"\n        )\n        assert output == \"*** \" + error + \"\\n\"\n        cmd = \"%%time\"\n        stop = RichIPdb().precmd(cmd)\n        captured_output = capsys.readouterr().out\n        assert not stop\n        RichIPdb().error(error)\n        cell_magics_error = capsys.readouterr().out\n        assert cell_magics_error == captured_output\n\n    def test_onecmd_lsmagic_line_magic(self, capsys, RichIPdb):\n        RichIPdb().precmd(\"%lsmagic\")\n        captured = capsys.readouterr()\n        output = captured.out\n\n        assert re.search(\n            \"Available line magics:\\n%alias +%alias_magic +%autoawait.*%%writefile\",\n            output,\n            re.DOTALL,\n        )\n\n    def test_no_zombie_lastcmd(self, capsys, RichIPdb):\n        rpdb = RichIPdb(stdout=sys.stdout)\n        rpdb.precmd(\"print('SHOULD_NOT_BE_IN_%pwd_OUTPUT')\")\n        captured = capsys.readouterr()\n        assert captured.out.endswith(\n            \"SHOULD_NOT_BE_IN_%pwd_OUTPUT\\n\"\n        )  # Starts with colors and prompt\n        rpdb.precmd(\"%pwd\")\n        captured = capsys.readouterr()\n        assert captured.out.endswith(Path.cwd().absolute().as_posix() + \"\\n\")\n        assert \"SHOULD_NOT_BE_IN_%pwd_OUTPUT\" not in captured.out\n\n    def test_IPython_Pdb_magics_implementation(self, tmp_path, capsys, RichIPdb):\n        \"\"\"\n        We test do_{magic} methods that are concretely implemented by\n        IPython.core.debugger.Pdb, and don't default to IPython's\n        'InteractiveShell.run_line_magic()' like the other magics.\n        \"\"\"\n        from IPython.utils.text import dedent\n\n        rpdb = RichIPdb(stdout=sys.stdout)\n        tmp_file = import_tmp_file(rpdb, tmp_path)\n\n        # pdef\n        rpdb.do_pdef(\"foo\")\n        do_pdef_foo_output = capsys.readouterr().out\n        untagged = untag(do_pdef_foo_output).strip()\n        assert untagged.endswith(\"foo(arg)\"), untagged\n        rpdb.precmd(\"%pdef foo\")\n        magic_pdef_foo_output = capsys.readouterr().out\n        untagged = untag(magic_pdef_foo_output).strip()\n        assert untagged.endswith(\"foo(arg)\"), untagged\n\n        # pdoc\n        rpdb.precmd(\"%pdoc foo\")\n        magic_pdef_foo_output = capsys.readouterr().out\n        untagged = untag(magic_pdef_foo_output).strip()\n        expected_docstring = dedent(\n            \"\"\"Class docstring:\n            Foo docstring\n        Call docstring:\n            Call self as a function.\"\"\"\n        )\n        assert untagged == expected_docstring, untagged\n\n        # pfile\n        rpdb.precmd(\"%pfile foo\")\n        magic_pfile_foo_output = capsys.readouterr().out\n        untagged = untag(magic_pfile_foo_output).strip()\n        tmp_file_content = Path(tmp_file).read_text().strip()\n        assert untagged == tmp_file_content\n\n        # pinfo\n        rpdb.precmd(\"%pinfo foo\")\n        magic_pinfo_foo_output = capsys.readouterr().out\n        untagged = untag(magic_pinfo_foo_output).strip()\n        expected_pinfo = dedent(\n            f\"\"\"Signature: foo(arg)\n        Docstring: Foo docstring\n        File:      {tmp_file.absolute()}\n        Type:      function\"\"\"\n        )\n        assert untagged == expected_pinfo, untagged\n\n        # pinfo2\n        rpdb.precmd(\"%pinfo2 foo\")\n        magic_pinfo2_foo_output = capsys.readouterr().out\n        untagged = untag(magic_pinfo2_foo_output).strip()\n        expected_pinfo2 = re.compile(\n            dedent(\n                rf\"\"\"Signature: foo\\(arg\\)\n        Source:\\s*\n        %s\n        File:      {tmp_file.absolute()}\n        Type:      function\"\"\"\n            )\n            % re.escape(tmp_file_content)\n        )\n        assert expected_pinfo2.fullmatch(untagged), untagged\n\n        # psource\n        rpdb.precmd(\"%psource foo\")\n        magic_psource_foo_output = capsys.readouterr().out\n        untagged = untag(magic_psource_foo_output).strip()\n        expected_psource = 'def foo(arg):\\n    \"\"\"Foo docstring\"\"\"\\n    pass'\n        assert untagged == expected_psource, untagged\n\n    def test_expr_questionmark_pinfo(self, tmp_path, capsys, RichIPdb):\n        from IPython.utils.text import dedent\n\n        rpdb = RichIPdb(stdout=sys.stdout)\n        tmp_file = import_tmp_file(rpdb, tmp_path)\n        # pinfo\n        rpdb.precmd(rpdb.precmd(\"foo?\"))\n        magic_foo_qmark_output = capsys.readouterr().out\n        untagged = untag(magic_foo_qmark_output).strip()\n\n        expected_pinfo_path = (\n            f\"/private/var/folders/.*/{tmp_file.name}\"\n            if sys.platform == \"darwin\"\n            else f\"/tmp/.*/{tmp_file.name}\"\n        )\n        expected_pinfo = re.compile(\n            dedent(\n                rf\"\"\".*Signature: foo\\(arg\\)\n        Docstring: Foo docstring\n        File:      {expected_pinfo_path}\n        Type:      function\"\"\"\n            )\n        )\n        assert expected_pinfo.fullmatch(untagged), f\"untagged = {untagged!r}\"\n\n        # pinfo2\n        rpdb.precmd(rpdb.precmd(\"foo??\"))\n        magic_foo_qmark2_output = capsys.readouterr().out\n        rpdb.precmd(rpdb.precmd(\"%pinfo2 foo\"))\n        magic_pinfo2_foo_output = capsys.readouterr().out\n        assert magic_pinfo2_foo_output == magic_foo_qmark2_output\n\n    def test_filesystem_magics(self, capsys, RichIPdb):\n        cwd = Path.cwd().absolute().as_posix()\n        rpdb = RichIPdb(stdout=sys.stdout)\n        rpdb.precmd(\"%pwd\")\n        pwd_output = capsys.readouterr().out.strip()\n        assert pwd_output == cwd\n        rpdb.precmd(\"import os; os.getcwd()\")\n        pwd_output = unquote(capsys.readouterr().out.strip())\n        assert pwd_output == cwd\n\n        new_dir = str(Path.cwd().absolute().parent)\n        rpdb.precmd(f\"%cd {new_dir}\")\n        cd_output = untag(capsys.readouterr().out.strip())\n        assert cd_output.endswith(new_dir)\n        rpdb.precmd(\"%pwd\")\n        pwd_output = capsys.readouterr().out.strip()\n        assert pwd_output == new_dir\n        rpdb.precmd(\"import os; os.getcwd()\")\n        pwd_output = unquote(capsys.readouterr().out.strip())\n        assert pwd_output == new_dir\n"
  },
  {
    "path": "tests/test_pdbr.py",
    "content": "import inspect\nimport pdb\n\nimport pytest\n\nfrom pdbr._pdbr import rich_pdb_klass\n\n\n@pytest.fixture\ndef RichPdb(*args, **kwargs):\n    currentframe = inspect.currentframe()\n\n    def wrapper():\n        rpdb = rich_pdb_klass(pdb.Pdb, show_layouts=False)(*args, **kwargs)\n        # Set frame and stack related self-attributes\n        rpdb.botframe = currentframe.f_back\n        rpdb.setup(currentframe.f_back, None)\n        return rpdb\n\n    return wrapper\n\n\ndef test_prompt(RichPdb):\n    assert RichPdb().prompt == \"(Pdbr) \"\n\n\ndef test_print(capsys, RichPdb):\n    RichPdb()._print(\"msg\")\n    captured = capsys.readouterr()\n    assert captured.out == \"msg\\n\"\n\n\ndef test_print_error(capsys, RichPdb):\n    RichPdb().error(\"error\")\n    captured = capsys.readouterr()\n    assert captured.out == \"\\x1b[1;31m*** error\\x1b[0m\\n\"\n\n\ndef test_print_with_style(capsys, RichPdb):\n    RichPdb()._print(\"msg\", style=\"yellow\")\n    captured = capsys.readouterr()\n    assert captured.out == \"\\x1b[33mmsg\\x1b[0m\\n\"\n\n\ndef test_print_without_escape_tag(capsys, RichPdb):\n    RichPdb()._print(\"[blue]msg[/]\")\n    captured = capsys.readouterr()\n    assert captured.out == \"\\x1b[34mmsg\\x1b[0m\\n\"\n\n\ndef test_print_array(capsys, RichPdb):\n    RichPdb()._print(\"[[8]]\")\n    captured = capsys.readouterr()\n    assert (\n        captured.out == \"\\x1b[1m[\\x1b[0m\\x1b[1m[\\x1b[0m\\x1b[1;36m8\"\n        \"\\x1b[0m\\x1b[1m]\\x1b[0m\\x1b[1m]\\x1b[0m\\n\"\n    )\n\n\ndef test_onecmd(capsys, RichPdb):\n    rpdb = RichPdb()\n    cmd = 'print(\"msg\")'\n    stop = rpdb.precmd(cmd)\n    captured = capsys.readouterr()\n    assert not stop\n    assert captured.out == \"msg\\n\"\n"
  },
  {
    "path": "tests/tests_django/__init__.py",
    "content": ""
  },
  {
    "path": "tests/tests_django/test_settings.py",
    "content": "from pathlib import Path\n\nBASE_DIR = Path(__file__).absolute().parents[1]\n\nSECRET_KEY = \"fake-key\"\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.sqlite3\",\n        \"NAME\": str(BASE_DIR / \"db.sqlite3\"),\n    }\n}\n\nINSTALLED_APPS = (\"tests.tests_django\",)\n\nTEST_RUNNER = \"pdbr.runner.PdbrDiscoverRunner\"\n\nROOT_URLCONF = \"tests.tests_django.urls\"\n\nMIDDLEWARE = [\"pdbr.middlewares.django.PdbrMiddleware\"]\n"
  },
  {
    "path": "tests/tests_django/tests.py",
    "content": "from django.test import TestCase\n\n\nclass DjangoTest(TestCase):\n    def test_runner(self):\n        self.assertEqual(\"foo\", \"foo\")\n\n    def test_middleware(self):\n        response = self.client.get(\"\")\n        self.assertEqual(response.status_code, 200)\n"
  },
  {
    "path": "tests/tests_django/urls.py",
    "content": "from django.http import HttpResponse\nfrom django.urls import path\n\nurlpatterns = [\n    path(\"\", lambda request: HttpResponse()),\n]\n"
  }
]