Debugging Guild Operations in PyCharm

To debug your Guild operations in PyCharm, set one or more breakpoints in PyCharm and use guild.ipy to run the appropriate function in your code:

# train.py

def main():
    data = _load_data()    # For example, set a breakpoint in PyCharm on this line
    model = _init_model()
    model.fit(data)

if __name__ == "__main__":
    from guild import ipy as guild
    guild.run(main)

In PyCharm, create a configuration that runs the applicable script or module. In the example above, the module is train.

To debug the module in a Guild run, use the configuration you created above. PyCharm will start a new Python session using the specified main module. The module uses the Guild Python API to run the main function.

You may want to parameterize the use of Guild to run the function so that it only applies under certain configuration. Use either environment variables or command line arguments as needed.

Example using an environment variable:

# train.py
import os

def main():
    data = _load_data()    # Set breakpoint in PyCharm on this line
    model = _init_model()
    model.fit(data)

if __name__ == "__main__":
    if os.getenv("GUILD_RUN") == "1":
        from guild import ipy as guild
        guild.run(main)
    else:
        main()

In your PyCharm configuration, specify GUILD_RUN=1 to indicate that Guild should run main.

@garrett Great to see this supported. I’ve been using debugpy but this looks much nicer. How do you include requirements from the guild.yml like this though?

I guess I would need to point to what operation I’m actually interested in or add some options when initializing the run?

P.S. I’m also trying to do this in VSCode rather than pycharm

This works for me in visual studio code:

In python module operations.train:

[...]

if __name__ == "__main__":

    if os.getenv("GUILD_DEBUG") == "1" and os.getenv("GUILD_DEBUG_STARTED") != "1":
        os.environ["GUILD_DEBUG_STARTED"] = "1"
        from guild.commands.run import run
        try:
            run(["train", "-y", "n_workers=0", "--debug-sourcecode=."])
        except SystemExit as e:
            pass
    else:
        parser = argparse.ArgumentParser()
        parser.add_argument("--batch_size", type=int, default=16)
        [...]

.vscode/launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Module",
            "type": "python",
            "request": "launch",
            "module": "operations.train",
            "env": {
                "GUILD_RUN": "1"
            }
        }
    ]
}

Edit: The negative with this solution is that errors show up as “SystemExit” on the run command rather than what we are interested in. Do you have a better solution @garrett?

I would catch the exception in the snippet.

    if os.getenv("GUILD_RUN") == "1" and os.getenv("GUILD_RUN_STARTED") != "1":
        os.environ["GUILD_RUN_STARTED"] = "1"
        from guild.commands import run
        try:
            run.run(["train", "-y"])
        catch SystemExit as e:
            print("ERROR: %s" % e)

That said, I’d like to streamline this to better support PyCharm or any of the other editors.

What is the re-entry point that causes you to use GUILD_RUN_STARTED to avoid subsequent calls?

It’s surprising to me that PyCharm is breaking correctly on the run directory source code. Guild copies the source code files to the run directory and runs the operation in separate Python VM. This is all outside PyCharm’s control and knowledge.

I’ll spend some time with this today. I’d like to get cleaner support for debugging in the various mainstream Python IDEs. Guild currently supports a handy --break option, which uses pdb to break at a line number or function. Maybe that’s a pattern that can be extended to other debuggers.

1 Like

The re-entry is this line run.run(["train", "-y"])

I would like to not get a SystemExit error but rather what the actual error was so my editor can jump to the correct file+line

This would work nicely if guild didn’t catch all errors and then raise SystemExit instead but maybe there’s a reason for that. Is there some other function I can call that avoids the try except?

I think the use of guild.commands.run there is not what you want. This is the entry point for the command line interface and not a general purpose way to run Python code. That’s what guild.ipy.run is for and why the initial example is spelled that way.

I need to reexamine this. That original examine happened to work nicely at one point but there may be issues that need to be cleaned up.

1 Like

Yes it would have been nice if guild.ipy.run had the same interface as guild.commands.run. However, at the moment it can only take a function as input and it doesn’t seem to resolve requirements like other operations. (Or at least I don’t understand how to specify them). I would also like to not specify the requirements in guild.yml again in the python code

Yes, you’re right—ipy.run doesn’t even look at the Guild file. Okay, let me poke around here as this is an important feature that we need to nail down.

1 Like