inducer / pudb

Full-screen console debugger for Python
https://documen.tician.de/pudb/
Other
2.94k stars 226 forks source link

Ipython StartUp Script for pudb python shell. #642

Closed maltezc closed 5 months ago

maltezc commented 5 months ago

Is your feature request related to a problem? Please describe. Is there a way to add a hook(not sure if this is the correct usage of the term 'hook') for a PYTHONSTARTUP variable to point to a startup.py file that can be ran whenever pudb starts up?

I'm able to get it to work with my ipython shell from terminal however I can't get it to work in pudb's ipython. I figure if i can get it to work for my terminal's ipython, its possible for pudb?

Describe the solution you'd like I would like for pudb to search my .zshrc file to look for a PYTHONSTARTUP or similar variable which would then point to a /my/pudb/startup.py script

Describe alternatives you've considered not sure of any alternatives yet.

Additional context Add any other context or screenshots about the feature request here.

What I tried I gave it an attempt and modified the run.py. I tried in the iterm2 console python3 debug_me.py but no dice. if there is somewhere else I should be modifying, please let me know.

COMMAND = {"zsh": "{_command_names -e}"}
PREAMBLE = {
    "zsh": """\
_script_args() {
  # pudb -m <TAB>
  if (($words[(I)-m] == $#words - 1)); then
    _python_modules
  # pudb -m XXX <TAB>
  elif (($words[(I)-m])); then
    _files
  # pudb <TAB>
  else
    _arguments -S -s '(-)1:script_args:_files -g "*.py"' '*: :_files'
  fi
}
""",
}
SCRIPT_ARGS = {"zsh": "_script_args"}

def get_argparse_parser():
    import os
    import sys
    import argparse
    try:
        import shtab
    except ImportError:
        from . import _shtab as shtab

    from pudb import VERSION

    version_info = "%(prog)s v" + VERSION

    if sys.argv[1:] == ["-v"]:
        print(version_info % {"prog": "pudb"})
        sys.exit(os.EX_OK)

    parser = argparse.ArgumentParser(
        "pudb",
        usage="%(prog)s [options] [-m] SCRIPT-OR-MODULE-TO-RUN [SCRIPT_ARGS]",
        epilog=version_info
    )
    shtab.add_argument_to(parser, preamble=PREAMBLE)
    # dest="_continue_at_start" needed as "continue" is a python keyword
    parser.add_argument(
        "-c", "--continue",
        action="store_true",
        dest="_continue_at_start",
        help="Let the script run until an exception occurs or a breakpoint is hit",
    )
    parser.add_argument("-s", "--steal-output", action="store_true")

    # note: we're implementing -m as a boolean flag, mimicking pdb's behavior,
    # and makes it possible without much fuss to support cases like:
    #    python -m pudb -m http.server -h
    # where the -h will be passed to the http.server module
    parser.add_argument("-m", "--module", action="store_true",
                        help="Debug as module or package instead of as a script")

    parser.add_argument("-le", "--log-errors", nargs=1, metavar="FILE",
                        help="Log internal errors to the given file"
                        ).complete = shtab.FILE
    parser.add_argument("--pre-run", metavar="COMMAND",
                        help="Run command before each program run",
                        default="").complete = COMMAND
    parser.add_argument("--version", action="version", version=version_info)
    parser.add_argument("script_args", nargs=argparse.REMAINDER,
                        help="Arguments to pass to script or module"
                        ).complete = SCRIPT_ARGS
    return parser

def get_python_startup_script():
    import os

    # Look for PYTHONSTARTUP variable in .zshrc file
    zshrc_path = os.path.expanduser("~/.zshrc")
    python_startup_script = None

    if os.path.isfile(zshrc_path):
        with open(zshrc_path, "r") as f:
            for line in f:
                if line.startswith("export PYTHONSTARTUP="):
                    _, python_startup_script = line.strip().split("=")
                    python_startup_script = python_startup_script.strip('"\'')
                    break

    return python_startup_script

def main(**kwargs):
    import sys
    import os

    # Execute the Python startup script specified in .zshrc
    startup_script = get_python_startup_script()
    if startup_script and os.path.isfile(startup_script):
        with open(startup_script, "r") as f:
            exec(f.read())

    parser = get_argparse_parser()

    options = parser.parse_args()
    args = options.script_args

    if options.log_errors:
        from pudb.lowlevel import setlogfile
        setlogfile(options.log_errors[0])

    options_kwargs = {
        "pre_run": options.pre_run,
        "steal_output": options.steal_output,
        "_continue_at_start": options._continue_at_start,
    }

    if len(args) < 1:
        parser.print_help()
        sys.exit(2)

    mainpyfile = args[0]
    sys.argv = args

    if options.module:
        from pudb import runmodule
        runmodule(mainpyfile, **options_kwargs)
    else:
        from os.path import exists
        if not exists(mainpyfile):
            print("Error: %s does not exist" % mainpyfile, file=sys.stderr)
            sys.exit(1)

        from pudb import runscript
        runscript(mainpyfile, **options_kwargs)

if __name__ == "__main__":
    main()
inducer commented 5 months ago

It seems as though you're wanting this to affect the shell in the debugger. Have you considered using pudb's custom shell feature?

maltezc commented 5 months ago

I have not yet. If that is another way to go, then i will investigate it. Thank you!

maltezc commented 5 months ago

hello again!

so i've hooked up the custom_shell and set up my file pudb_custom_shell.py and started messing with it.

i've found that i need to use cons.push() to "push" code to my custom shell. for example, if i have

cons.push("from pprint from pprint")

To use pprint in my console, i can just do `pprint("hello") and it comes out correctly.

is there a way i can run an entire .py file instead of just doing line by line in my pudb_custom_shell.py file?

i've tried the following below and it will output a print statement but it won't import pprint.

See below for 2 versions of my pudb_custom_shell.py file. one with cons.push('from pprint import pprint') and then the one trying import a ipython_startup_script.py file and running everything in that.

This version below works:

here is what it looks like:

""" This file shows how you can define a custom shell for PuDB. This is the shell used when pressing the ! key in the debugger (it does not affect the Ctrl-x shell that is built into PuDB).

To create a custom shell, create a file like this one with a function called pudb_shell(_globals, _locals) defined at the module level. Note that the file will be execfile'd.

Then, go to the PuDB preferences window (type Ctrl-p inside of PuDB) and add the path to the file in the "Custom" field under the "Shell" heading.

The example in this file

"""

Define this a function with this name and signature at the module level.

def pudb_shell(_globals, _locals): """ This example shell runs a classic Python shell. It is based on run_classic_shell in pudb.shell.

"""
# Many shells only let you pass in a single locals dictionary, rather than
# separate globals and locals dictionaries. In this case, you can use
# pudb.shell.SetPropagatingDict to automatically merge the two into a
# single dictionary. It does this in such a way that assignments propogate
# to _locals, so that when the debugger is at the module level, variables
# can be reassigned in the shell.
from pudb.shell import SetPropagatingDict
ns = SetPropagatingDict([_locals, _globals], _locals)

try:
    import readline
    import rlcompleter
    HAVE_READLINE = True
except ImportError:
    HAVE_READLINE = False

if HAVE_READLINE:
    readline.set_completer(
        rlcompleter.Completer(ns).complete)
    readline.parse_and_bind("tab: complete")
    readline.clear_history()

from code import InteractiveConsole
cons = InteractiveConsole(ns)

cons.push("from pprint import pprint")  # this works.

other version trying to import and run an entire file.

""" This file shows how you can define a custom shell for PuDB. This is the shell used when pressing the ! key in the debugger (it does not affect the Ctrl-x shell that is built into PuDB).

To create a custom shell, create a file like this one with a function called pudb_shell(_globals, _locals) defined at the module level. Note that the file will be execfile'd.

Then, go to the PuDB preferences window (type Ctrl-p inside of PuDB) and add the path to the file in the "Custom" field under the "Shell" heading.

The example in this file

"""

Define this a function with this name and signature at the module level.

def pudb_shell(_globals, _locals): """ This example shell runs a classic Python shell. It is based on run_classic_shell in pudb.shell.

"""
# Many shells only let you pass in a single locals dictionary, rather than
# separate globals and locals dictionaries. In this case, you can use
# pudb.shell.SetPropagatingDict to automatically merge the two into a
# single dictionary. It does this in such a way that assignments propogate
# to _locals, so that when the debugger is at the module level, variables
# can be reassigned in the shell.
from pudb.shell import SetPropagatingDict
ns = SetPropagatingDict([_locals, _globals], _locals)

try:
    import readline
    import rlcompleter
    HAVE_READLINE = True
except ImportError:
    HAVE_READLINE = False

if HAVE_READLINE:
    readline.set_completer(
        rlcompleter.Completer(ns).complete)
    readline.parse_and_bind("tab: complete")
    readline.clear_history()

from code import InteractiveConsole
cons = InteractiveConsole(ns)

# import code
import os

# Get the path to the .py file in your home directory
home_dir = os.path.expanduser("~")
module_path = os.path.join(home_dir, "ipython_startup_script.py")

# Import and execute the script
with open(module_path, 'r') as file:
    script_code = file.read()
    exec(script_code) # commenting out this or
    cons.push(script_code) # commenting out this one.

cons.interact("Press Ctrl-D to return to the debugger")
# When the function returns, control will be returned to the debugger.

my ipython_startup_script file looks like this:

print("running ipython_startup_script.py") # <-- this will output to the console from pprint import pprint # <-- this does not get imported.

i get this error if i dont put them in a function SyntaxError: multiple statements found while compiling a single statement

I've also tried button both of those lines in a function called Load() and then called Load() at the end.

Any ideas on where to go from here or is cons.push() line by line in the shell file the way to go?