Skip to content

Instantly share code, notes, and snippets.

@AsgerPetersen
Last active March 28, 2025 18:31
Show Gist options
  • Save AsgerPetersen/9ea79ae4139f4977c31dd6ede2297f90 to your computer and use it in GitHub Desktop.
Save AsgerPetersen/9ea79ae4139f4977c31dd6ede2297f90 to your computer and use it in GitHub Desktop.
Debugging QGIS 3.x python plugins on OSX using VS Code

Debugging QGIS 3.x python plugins on OSX using VS Code

Plugin

In QGIS install the plugin debugvs.

Python dependencies

The debugvs plugin needs the python module ptvsd to function. This module is not installed by default.

In principle you just pip install ptvsd in the python interpreter used by QGIS.

I am using the QGIS OSX installer from Lutra Consulting. This installer works really great, but installing additional python modules is not very easy.

What I did was this:

  1. Download the ptvsd wheel from pypi. I tried with the newest version (4.2.4), but that didnt work for me. I ended up using ptvsd-4.1.4.zip.
  2. Unzip the wheel (if it has the extension .whl then rename it to .zip)
  3. Inside the zip there are two directories. Copy the directory ptvsd to the Resources/python of your QGIS installation. In my case this was /Applications/QGIS3.6.app/Contents/Resources/python/.

Restart QGIS.

Setting up VSCode

The default remote debugger configuration in VS Code looks like this

{
    "name": "Python: Remote Attach",
    "type": "python",
    "request": "attach",
    "port": 5678,
    "host": "localhost",
    "pathMappings": [
        {
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "."
        }
    ]
},

I had to change the pathMappings to get it to work:

{
    "name": "Python: Remote Attach",
    "type": "python",
    "request": "attach",
    "port": 5678,
    "host": "localhost",
    "pathMappings": [
        {
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "${workspaceFolder}"
        }
    ]
},

Debugging

  • In QGIS click Plugins -> Enable Debug for Visual Studio -> Enable Debug for Visual Studio
  • You should now see a message in the QGIS message bar saying something like DebugVS : Run the Debug in Visual Studio(Python:Attach)
  • In VS Code start debugging using the Python: Remote Attach configuration defined above.

Now you should be able to set breakpoints in VS Code.

@stelf
Copy link

stelf commented Mar 21, 2025

so update for everyone again struggling (as myself) to connect to debug plugins in March 2025 to the ... present version of QGIS 3.40.something.

as you can see below this is not conda, no venv, no fancy stuff, just straight system config, as qgis comes with its own python anyway. my install is the OSGeo4W install, though pretty sure this should work with other stripped-down versions.

contrary to what it was in 2019, now vscode-hosted debugging is possible solely with debugpy and this is the prolog which goes in init.py for my plugins

import debugpy

sys.path.append( 'C:\\Users\\strix\\.vscode\\extensions\\ms-python.python-2025.3.2025031701-win32-x64\\python_files\\lib\\python\\')

def setup_debugging():
    # Configure Python interpreter
    python_path = shutil.which("python")
    if not python_path:
        python_path = re.sub(r'[^/\\]+(?:\.exe)?$', 'python', sys.executable)

    # Print current environment for debugging
    print(f"Current directory   : {os.getcwd()}")
    print(f"Module path         : {__file__}")
    print(f"sys.executable      : {sys.executable}")
    print(f"devised python path : {python_path}")
    print(f"Python on PATH      : {shutil.which("python")}")
    
    try:
        # This can help if you're running different Python versions
        # Cross-platform method to replace executable with python
        debugpy.configure(python=python_path)

        print("try to listen")
        try:
            debugpy.listen(("localhost", 5678))
            print("Debugpy server started, waiting for connection...")
            # debugpy.wait_for_client()
            print("Debugger connected!")
        except Exception as e:
            # If listening fails, try to connect (client mode)
            print(f"Couldn't start debugpy server: {e}")
            print("Trying to connect as client...")
            debugpy.connect(("localhost", 5678))
            print("Connected to debugpy server!")
    except Exception as e:
        print(f"Debugpy error: {e}")
    

notes:

  • debugpy.wait_for_client() is grayed out as it causes basically causes QGIS to reload. At one point it was like a fork bomb actually, very vad. I thought the python path was the issue initially, perhaps is not. Others also complain, I could find some cries for help on various forums. So if you want to wait for your debugger, perhaps another logic should be implemented, thing is it WORKS as it is now, given the plugin compiles and imports modules properly, and then you can async connect to the debugpy which is alredy working in some separate light interpreter thread I guess (not sure the mechanics though).
  • strix here is some generic user, and your version of vscode plugins dir may differ. i'm absolute not convinced you actually need this.
  • the setup checks for already connected debugger. this presumes you can reload (CTRL-F5) your plugin with the excellent Plugin Re-loader plugin
  • you can install debugpy from the python console in QGIS, or via the OSGeo4W, I trust you know how to do it .
  • dubgvs is not needed anymore as plugin

the struggle, hence the length of this setup_debugging, was to map the dev dir to the qgis dir. not sure how you guys develop, but I have the code in separate dir from the plugins dir, and neither I use the additional plugin dirs feature. Claude created for me a deploy script such that it watches over files and copies those modified to the plugin dir (on windows one also has to explicitly allow this, as c:\Program Files\ is protected... perhaps my setup is a bit unpopular one, but works OK for me).

I spent like a day remembering how exactly this mapping thing should normally be, and eventually ended with this launch.json

    "configurations": [
        {
            "name": "Python: Remote Attach",
            "type": "debugpy",
            "request": "attach",
            "connect": {
                "host": "localhost",
                "port": 5678
            },
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}/",
                    "remoteRoot": "C:\\PROGRA~1\\QGIS34~1.4\\apps\\qgis-ltr\\python\\plugins\\"
                },
            ],
            "justMyCode": false
        },

Here, localRoot typically equals to your current dir, the project dir. The remoteRoot changes depending on where you deploy (or if you have had QGIS load plugin directly from the dev dir). I decided to not try to set it automatically, though is perhaps possible with some prerun script to get the QGIS location for the user (and unless, again he has 3 different versions like myself). Most tutorials have you set remoteRoot to the same dir as localRoot which should (havent tested it) work when you develop and load/run the plugin from one and the same dir.

Also make sure that you've set the python compiler for pylance to work properly and the extraPaths. here's a Qt5 config that works

{
  "python.analysis.extraPaths": [
    "C:\\Program Files\\QGIS 3.40.4\\apps\\Python312",
    "C:\\Program Files\\QGIS 3.40.4\\apps\\Python312\\Lib\\site-packages",
    "C:\\Program Files\\QGIS 3.40.4\\apps\\qgis-ltr\\python",
    "C:\\Users\\strix\\.vscode\\extensions\\ms-python.python-2025.3.2025031001-win32-x64\\python_files\\lib\\python\\"
  ],
  "python.analysis.typeCheckingMode": "basic",
  "python.analysis.diagnosticMode": "workspace",
  "python.analysis.include": [
    "c:/Work/fmigis/apify"
  ],
  "python.analysis.diagnosticSeverityOverrides": {
    "reportOptionalMemberAccess": "none"
  },
  "python.languageServer": "Pylance",
  "python.analysis.autoImportCompletions": true,
  "python.analysis.indexing": true,
  "python.analysis.packageIndexDepths": [
    { "name": "qgis", "depth": 4,  "includeAllSymbols": true },
    { "name": "PyQt5", "depth": 4,  "includeAllSymbols": true }
  ]
}

for everyone reading this far, perhaps you should note that "c:/Work/fmigis/apify" in the example is the project homedir, so update this accordingly. this is basically the git project root. also these python.analysis directives were absolutely necessarily to get the linter read the correct dirs. also, the selected language server is pylance and the analysis is done by it, hence your linter may behave differently.

Note - it is very likely that this will not work with newer PyQt6 versions, still experimental at time of writing. QGIS devs introduced a compatibility layer, but I couldn't find a way to have all the linters understand its magic, thus my imports were littered with warnings which impact the dev process, and my progress respectively.

Good luck, and I may actually release the plugin I'm writing atm, I just needed to share findings once the vscode connected, as this obstacle really drove me crazy.

...

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