Lerry William S.

I Don't Mind My Heart Breaking — As Long As QGIS Doesn't

June 30, 2026


A practical guide to quv: the Python environment manager that finally understands QGIS

Part 1 of 3


If you’ve used QGIS for any length of time, you’ve lived this. You found a promising Python library — a machine learning toolkit, a nicer HTTP client, or just plain pandas — and you did the obvious, reasonable thing. You ran pip install.

And then QGIS stopped working.

Maybe the canvas went blank. Maybe it crashed on startup. Maybe it was quieter than that: a single ImportError where a working plugin used to be, no drama, no explanation. You didn’t do anything wrong. You just forgot the first rule of QGIS Python:

QGIS is like a spouse. Treat her carefully, or she will make your life very difficult, very quickly.

I made that rule up. It has never once been wrong.

This is a guide to breaking the habit — to getting the Python workflow you actually want without leaving QGIS in pieces.

Back in 2019 I wrote two posts about exactly this problem. Install Jupyter Notebook in QGIS 3 walked through getting Jupyter talking to QGIS’s Python. A week later, PyQGIS in Jupyter Notebook covered the reverse: getting PyQGIS working inside Jupyter, and running standalone QGIS scripts outside the app entirely. People found them. They solved a real problem. I still get emails about them.

But if I’m honest, they weren’t solutions. They were workarounds — well-documented ones, but workarounds all the same. A list of manual steps: install this here, export that variable, point this path there, register a kernel, and pray it survives the next QGIS update. Every time someone wrote to ask why it broke after they upgraded, I had exactly one answer: “redo the setup.” Not my proudest support policy.

The blog posts weren’t the problem. The problem was that the right tool didn’t exist yet.

So after years of the same question landing in QGIS forums, on Reddit, and in my inbox, I decided to build the thing I kept telling people to fake by hand.

The trigger was uv, which showed up around 2024 and made Python package management fast in a way it frankly never had been. I started playing with it and did what any reasonable developer does when handed a fast new tool: I wrapped it in a pile of batch files and shell scripts to automate the QGIS setup. It worked. Mostly. Right up until I sat down at a different machine.

Here’s the thing about batch files — they’re intimate. They encode your paths, your OS, your QGIS install location, your particular life choices. I work across Linux and Windows, and every workstation switch cost me an hour of rediscovering which script assumed what. One .bat had my home Windows path baked into it — copy it to the office, edit the path, fine. Come home, switch to Linux, reach for an entirely different set of scripts. And that’s before we get to my colleagues’ machines, each subtly different from mine in ways that only surfaced at the worst possible moment.

Eventually the obvious sank in: the scripts weren’t the solution. They were the original problem wearing a convincing disguise.

So naturally, I wrote a C implementation.

Yes. C. For a Python environment manager. I’m still not sure what I was thinking, but it worked — in a lean, humourless, “does exactly three things and refuses to do a fourth” sort of way. The trouble was that three things turned out to be two short, and every new feature meant another round with memory management and cross-platform build toolchains. It was an excellent way to learn things I already suspected I wasn’t good at. It was not the answer.

What I actually needed was one tool: cross-platform, zero-configuration, fluent in QGIS, and extensible without a lost weekend and a debugger. Something that gave me the same result on Linux Monday and Windows Thursday without my touching a single path variable.

That’s quv. Written in Python — because sometimes the right tool for automating Python environments is, a little embarrassingly, just more Python.


The Real Problem Nobody Talks About

QGIS ships with its own tightly coupled Python stack — GDAL, NumPy, PyQt, and friends — each pinned to an exact version that keeps the whole thing standing. When you pip install into that Python, you’re not just adding a library. You may be quietly overwriting one of the packages QGIS is standing on, at a version it was never tested against.

The result is unpredictable, usually silent, and deeply annoying to trace back.

The standard advice is “use a virtual environment.” Excellent advice, in theory. The catch is that a normal venv has no idea QGIS exists. Try import qgis.core in a fresh one and you get:

ModuleNotFoundError: No module named 'qgis'

You can fix it by hand — set PYTHONPATH, LD_LIBRARY_PATH, and QT_PLUGIN_PATH just so. It’s also different on every OS, different across QGIS versions, and something you will get wrong at least twice before it works. I say that from experience — collective, species-wide experience.

And just as you get comfortable: QGIS 4.x has arrived, and it ships with Qt6.

Wonderful.

If wrangling PyQGIS into a venv already felt like a full-time job, try doing it across two major QGIS versions with different Qt bindings. That import PyQt5 you’ve written since forever? Fine on 3.x, quietly broken on 4.x — the correct import was always from qgis.PyQt import ...; QGIS 3 was just polite enough not to mention it. The Qt.LeftDockWidgetArea you copied off a 2020 Stack Overflow answer is now Qt.DockWidgetArea.LeftDockWidgetArea. That pushMessage(level=0) shortcut is a TypeError on 4.x. None of these are exotic edge cases. They’re in nearly every plugin, and they all fail silently — until they don’t.

So the real problem was never “make PyQGIS work in a venv.” It’s “make PyQGIS work in a venv, on QGIS 3.x and 4.x, on Linux and Windows — including the Flatpak build that seals QGIS inside a sandbox — without losing your mind.” quv detects which QGIS you’ve got, Qt5 or Qt6, sandboxed or not, and configures the environment to match. Same command, same workflow, whichever machine and whichever QGIS you happen to be sitting in front of.


In Part 2 we get hands-on: installing quv, creating your first QGIS-aware environment, and everything the CLI can do.

Continue to Part 2: The CLI