Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AsgerPetersen/2000eb6f3e3307bd25190b19493dd9a3 to your computer and use it in GitHub Desktop.
Save AsgerPetersen/2000eb6f3e3307bd25190b19493dd9a3 to your computer and use it in GitHub Desktop.
Debugging QGIS python plugins on OSX using VSCode

Debugging QGIS python plugins using VSCode on OSX

This is how I debug QGIS using VSCode on OSX. Previously I used this approach. But since then ptvsd was deprecated and I also needed to install python modules backed by native C code. This is much easier to do in a conda based environment (actually, I didn't find a way to make it work with the official QGIS installer :-)).

Get QGIS running in a conda env

Follow this description to get QGIS running in conda.

Install debugpy

ptvsd is superseded by debugpy.

Activate your conda environment and instal debugpy:

> conda activate conda_qgis
> conda install debugpy

In VSCode

  1. Open the plugin code directory. This could for instance be /Users/asger/Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins/QlrBrowser.

  2. Select the Python interpreter from your conda environment which has QGIS installed.

  3. In Debug panel click Add Configuration... in the configuration drop down.

  4. When VSCode Asks for an Environment select Python.

  5. Then VSCode shows a Select a debug configuration dropdown where you select Remote Attach.

  6. VSCode asks for a host name. Just use localhost

  7. Lastly you are asked for a port number. Use 5678. Now VSCode should have a Python: Remote Attach option in the debug configurations drop down .

  8. You need to change the pathMappings in the configuration manually. Click the cogwheel next to the configurations drop down and edit the pathMappings like to look like this

"pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "${workspaceFolder}"
                }
            ]

Your launch.json may now look like this

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
    

        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        },
        {
            "name": "Python: Remote Attach",
            "type": "python",
            "request": "attach",
            "port": 5678,
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "${workspaceFolder}"
                }
            ]
        }
    ]
}

Instrumenting your code

So far there does not seem to be a QGIS plugin which starts a debugpy remote debugger. There is a plugin for the ptvsd debugger but that is now deprecated in favour of debugpy. (Note to self: Update that plugin to use debugpy.)

We have to invoke the debug adapter from our code. So put code like this somewhere it will be hit

import debugpy
import shutil
debugpy.configure(python=shutil.which("python"))
debugpy.listen(('localhost', 5678))
debugpy.wait_for_client()  # blocks execution until client is attached

If you are writing a plugin a good place to put the code could be at the top of the plugin´s __init__.py file.

Now, when the above code is hit QGIS will wait patiently for you to attach to the debugger. In VS Code open the Debug panel and start debugging using the Python: Remote Attach configuration defined above.

Note on Processing plugins

Processing by default executes the processAlgorithm(...) method on a seperate thread. So your breakpoints in the method will not be hit if your debug adapter runs in the main thread which will be the case if you have done as described above.

There are two possible solutions to this problem.

Force execution on main thread

Add this method to your QgsProcessingAlgorithm class:

def flags(self):
    return QgsProcessingAlgorithm.FlagNoThreading

See the documentation about flags().

Invoke a debug adapter in the processing thread

An other option is invoking the debug adapter in the thread which executes the processAlgorithm(...) method. Put these lines at the top of the processAlgorithm(...) method like this:

def processAlgorithm(self, parameters, context, feedback):
    import debugpy
    import shutil
    debugpy.configure(python=shutil.which("python"))
    debugpy.listen(('localhost', 5678))
    debugpy.wait_for_client()  # blocks execution until client is attached
    # Actual code will be below this line

Make sure these lines are not hit when putting the code into production!

Note that you cannot run two adapters on the same port. Either invoke only one adapter or make your adapters use different ports. When attaching to the debugger you naturally need to specify the right port (see configuring launch.json above).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment