Cooking with Gispo: QGIS Plugin Development in VS Code

In this guide we’ll set up a full working QGIS plugin development environment for VS Code on Windows.

This setup supports:

  • Autocompletion for qgis and pyqt libraries
  • Runtime debugging
  • Tests
    • VS Code test integration
    • Test coverage
    • Test debugging
  • Linting and formatting

If you are developing a public QGIS plugin I recommend using QGIS Long term release for development and having that as a minimum QGIS version requirement.

This setup should work with “Standalone installer for long term release (continued with dependencies from old OSGeo4W)” and QGIS installed with “Long-term release in old OSGeo4W (continued with previous dependencies)”. The current installer also known as the new OSGeo4W V2 installer is not supported by this setup yet since at the time of the writing there are some issues with how python 3.9 is handling dll loading.

In this tutorial we’ll set up a development environment to work with the following project structure:

📦my-qgis-plugin
 ┣ 📂.git
 ┣ 📂myqgisplugin
 ┃ ┣ 📜metadata.txt
 ┃ ┣ 📜plugin.py
 ┃ ┗ 📜__init__.py
 ┣ 📂tests
 ┃ ┣ 📜test_plugin.py
 ┣ 📜.gitignore
 ┣ 📜LICENSE
 ┣ 📜README.md
 ┣ 📜requirements-dev.txt
 ┗ 📜pyproject.toml

Creating new sample plugin

Create new plugin using cookiecutter-qgis-plugin template.

  • Make sure you have git installed
  • Install cookiecutter into some (virtual) python environment (you can create one just for cookiecutter)
> <QGIS-INSTALLATION-FOLDER>\bin\python-qgis-ltr.bat -m venv .venv
> .venv\scripts\activate
(.venv) > python -m pip install -U pip
(.venv) > pip install cookiecutter
  • The python-qgis-ltr.bat messes up with the environment (git is not in the path anymore), so it is a good idea to restart the command prompt at this point
  • Create a new plugin and answer the questions as you wish
> cookiecutter https://github.com/GispoCoding/cookiecutter-qgis-plugin
  • You should now have a working QGIS plugin!

VS Code startup

Right-click the plugin directory created in previous step and click Open with Code. If you don’t have the Python extension installed, install it at this point.

Setting up a virtual python environment

We’ll use a virtual python environment to install all our development tools and packages so those don’t interfere with the QGIS environment.

First add all the paths containing dll files inside QGIS installation directory to the user’s PATH environment variable.

  • <QGIS-INSTALLATION-FOLDER\>\bin
  • <QGIS-INSTALLATION-FOLDER\>\apps\qgis-ltr\bin
  • <QGIS-INSTALLATION-FOLDER\>\apps\Qt5\bin

With command prompt create a virtual python environment and save path to QGIS python libraries as .pth file by giving a commands:

# Navigate to the plugin folder
> cd my-qgis-plugin
> <QGIS-INSTALLATION-FOLDER>\bin\python-qgis-ltr.bat -m venv --system-site-packages .venv
> <QGIS-INSTALLATION-FOLDER>\bin\python-qgis-ltr.bat -c "import pathlib;import qgis;print(str((pathlib.Path(qgis.__file__)/'../..').resolve()))" > .venv\qgis.pth

If you are running PowerShell, make sure to change the encoding of the .venv\qgis.pth from UTF-16 LE to UTF-8.

Now with a different command prompt window activate the virtual python environment and upgrade pip by giving a commands:

> .venv\scripts\activate
(.venv) > python -m pip install -U pip

If you are running PowerShell and get an error about Activate.ps1 cannot be loaded because running scripts is disabled on this system, you can run the following line and then try again: Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

Set that just created environment as a Python Interpreter for this project.
Ctrl+Shift+P -> “Python: Select Interpreter”
If the .venv interpreter is not showing in the list use “Enter interpreter path” > “Find” and browse for .venv/Scripts/python.exe.

Install recommended development tools

With the virtual python environment activated install the recommended development tools.

(.venv) > pip install pytest pytest-qgis pytest-cov flake8 flake8-qgis black isort
ToolPurpose
pytestTest framework
pytest-qgispytest plugin for making it easier to write QGIS plugin tests
pytest-covTest coverage tool with pyproject.toml configuration support
flake8Linting support
flake8-qgisLinting support for PyQGIS specific code
blackPython code formatter
isortSorts imports automatically as recommended

You can also automate the linting and formatting to happen before committing via pre-commit tool. It can be installed with:

(.venv) > pip install pre-commit
(.venv) > pre-commit install

It will automatically download the linting and formatting tools (configured in .pre-commit-config.yaml) and run those before each commit.

pytest config

You could configure pytest and coverage further by giving configurations in pyproject.toml file. For example:

# pyproject.toml
[tool.pytest.ini_options]
addopts = "-v --tb=short --cov=myqgisplugin"
testpaths = [
    "tests"
]

[tool.coverage.run]
omit="myqgisplugin/somepackage/*"

flake8 configuration

We must set up few settings for flake8 so it is black compatible. Flake8 doesn’t yet support pyproject.toml so we must use its own .flake8 configuration file.

# .flake8
max-line-length = 88
extend-ignore =
    E203

isort configuration

For isort to be black compatible we must set up it to use black profile.

# pyproject.toml
[tool.isort]
# Black compatible values for isort https://black.readthedocs.io/en/stable/compatible_configs.html#isort
profile = "black"
multi_line_output = 3

Project Settings

Paste the following settings to the workspace settings file: ./.vscode/settings.json.

{
    "python.languageServer": "Jedi",
    "python.linting.enabled": false,
    "python.linting.flake8Enabled": true,
    "python.formatting.provider": "black",
    "editor.formatOnSave": true,
    "[python]": {
        "editor.codeActionsOnSave": {
            "source.organizeImports": true
        }
    },
    "python.testing.unittestEnabled": false,
    "python.testing.nosetestsEnabled": false,
    "python.testing.pytestEnabled": true,
    "files.associations": {
        "metadata.txt": "ini"
    }
}

Note 1: Currently the only one language server supporting autocompletion for qgis and PyQt5 libraries is the Jedi language server. Qgis and PyQt5 packages are using compiled python code and other language servers are having troubles parsing the API from those libraries (Actually this is for security reasons).
In the following QGIS releases python stub files that describes the API are included in the qgis package so also much better Pylance language server can be then used. If you have *.pyi files in C:/OSGeo4W64/apps/qgis-ltr/python/qgis go with the Pylance language server.

Note 2: If you choose to add the vscode config in Cookiecutter template a ./myqgisplugin.code-workspace file will be created to the root. You can use it instead of manually copying the settings.

Testing

With the above settings VS Code should be able to discover your tests automatically.

Deploying the plugin

The main plugin lies in the myqgisplugin directory (name differs if you chose a different name in cookiecutter). To make the plugin available for QGIS, it has to has access to that directory (or copy of it).

1) Copying the plugin manually You can copy the directory manually to C:/Users//AppData/Roaming/QGIS/QGIS3/profiles/default/python/plugins everytime you do changes and then install / reload the plugin with QGIS. You can also utilize tools like qgis_plugin_tools or pb_tool to accomplish this in a easier way.

3) Using symbolic link With symbolic links you can achieve the same result of having the code in QGIS plugin folder but you don’t have to worry about doing the copying again each time you change the code. There is a huge risk though of losing your code if you uninstall the plugin by accident.

2) Modifying QGIS_PLUGINPATH Other alternative is to modify the environment variable QGIS_PLUGINPATH to make QGIS search the plugins from different directories. This way you can have QGIS find the plugin straight from your development environment. As with the upper method, there is a hugre risk of losing your code if you uninstall the plugin.

Starting the plugin

Now that the plugin is deployed, it can be started by installing it from QGIS Plugins > Manage and Install Plugins.

To use the plugin, just open the Python console first and the click Plugins > MyQGISPlugin > MyQGISPlugin and a friendly text should be printed at the bottom of the console.

Debugging

With VS Code debugging works so that you should start a debugging server on QGIS side and then attach to it in VS Code.

There are two alternative ways to start the debugging server on QGIS. Either use a Debugvs -plugin or add a small code snippet to your plugin code.

1) Debugvs plugin
Install the Debugvs plugin from QGIS plugin repository. Debugvs needs a ptvsd python package available so install it first.

In your Osgeo4W Shell (C:\OSGeo4W64\OSGeo4W.bat).

> py3_env
> python3 -m pip install ptvsd

If you get an error that you don’t have module pip then install it first by downloading get-pip.py and executing it with python3 get-pip.py

ptvsd is though now deprecated project and the recommended debugging library is now debugpy. There are not yet any QGIS plugin for debugpy.

2) Code snippet in plugin code
This method is using the new debugpy library.

In your Osgeo4W Shell (C:\OSGeo4W64\OSGeo4W.bat).

> py3_env
> python3 -m pip install debugpy

Insert the following code in to the __init__.py file of your plugin’s main package (if you are using qgis_plugin_tools)

Note: If you are using qgis_plugin_tools, you don’t have to insert the following code, just add the following environment variable to enable debugging: QGIS_PLUGIN_USE_DEBUGGER=debugpy.

import os
import shutil

if os.environ.get("QGIS_DEBUGPY_HAS_LOADED"):
    try:
        import debugpy
        debugpy.configure(python=shutil.which("python"))
        debugpy.listen(('localhost', 5678))
    except Exception as e:
        print("Unable to create debugpy debugger: {}".format(e))
    else:
        os.environ["QGIS_DEBUGPY_HAS_LOADED"] = "1"

Debug launch configuration

To launch the debugging, create the following launch configuration in file ./.vscode/launch.json.

{
    // 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: Remote Attach",
            "type": "python",
            "request": "attach",
            "connect": {
                "host": "localhost",
                "port": 5678
            },
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}/myqgisplugin",
                    "remoteRoot": "C:/Users/${env:USERNAME}/AppData/Roaming/QGIS/QGIS3/profiles/default/python/plugins/myqgisplugin"
                }
            ],
            "justMyCode": false,
        },
        {
            "name": "Debug Tests",
            "type": "python",
            "request": "test",
            "console": "integratedTerminal",
            "justMyCode": false,
            "env": {
                "PYTEST_ADDOPTS": "--no-cov"
            }
        }
    ]
}

The first configuration is for attaching to the debugging server running on QGIS side.
The second makes sure that tests can be debugged in VS Code even if using testing coverage.

Note: Cookiecutter’s ./myqgisplugin.code-workspace file also has these configurations set if you choose to include vs code configs.

To start the debugging:

  1. Start QGIS
  2. Launch the Python: Remote Attach launch configuration
  3. Put some breakpoints and start the plugin from QGIS

Recommended VS Code extensions

  • EditorConfig for VS Code

Lauri is M.Sc. (tech) who is interested in coding, tech and problem solving. In his freetime he likes to spend time in nature and to downhill ski.