Skip to content

build!: drop Python 3.10 support - #514

Merged
Guts merged 1 commit into
masterfrom
packaging/drop-python-3.10
Oct 18, 2026
Merged

Guts merged 1 commit into
masterfrom
packaging/drop-python-3.10

Conversation

@Guts

@Guts Guts commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Description

Drops Python 3.10 support. 3.10 reaches its end of life this month (October 2026), and dependencies already move on: pgserviceparser 3 requires Python >= 3.11 (see #513, which needs environment markers only because of 3.10).

Where Change
pyproject.toml requires-python = ">=3.11, <4", 3.10 classifier removed, ruff target-version = "py311"
.pre-commit-config.yaml pyupgrade --py311-plus
tester.yml 3.10 removed from the Ubuntu matrix (the Windows one already started at 3.11, for lack of a cp310 GDAL wheel in the pinned geospatial-wheels release)
builder_releaser.yml Linux executables built with Python 3.11 instead of 3.10 — the Windows ones already used 3.11
docs_builder.yml docs built with 3.11
sonar-project.properties analyzed versions
docs/development/ Ubuntu and Windows setup pages

No code change was needed: ruff with the py311 target and pyupgrade --py311-plus find nothing to rewrite, and there was no 3.10-specific code path. The ExtendedEnum.has_value() helper stays: membership testing an Enum with a plain value still raises TypeError on 3.11 (it only works from 3.12).

Verification

  • pre-commit run passes on the branch's changes.
  • Installing on Python 3.10 is now refused by the resolver (requires-python).

Breaking change

DicoGIS no longer installs on Python 3.10. The executables are unaffected for end users (they embed their own Python), but the Linux ones are now built with 3.11: worth a quick smoke test of the next release build.

Related issue(s)

Unblocks a simpler #513 (no more environment markers for pgserviceparser).

Type of change

  • Bug fix (fix/… branch)
  • New feature or enhancement (feature/… branch)
  • Documentation (docs/… branch or *.md/docs/** changes)
  • Packaging / build (packaging/… branch or builder/** changes)
  • Tooling / CI-CD (tooling/… branch or .github/** changes)
  • Tests only (tests/** changes)

Checklist

  • I have read the Contributing Guidelines
  • pre-commit run --all-files passes locally (on the branch's changes)
  • I have added or updated tests covering my changes (CI matrix change only)
  • I have updated the documentation where relevant
  • pytest passes locally (no code change)

AI/LLM disclosure


Generated by Claude Code

Python 3.10 reaches its end of life this month (October 2026), and dependencies already move on: pgserviceparser 3 requires Python >= 3.11. Raise the floor to 3.11:

- pyproject.toml: requires-python ">=3.11, <4", 3.10 classifier removed, ruff target-version py311;
- pre-commit: pyupgrade --py311-plus;
- CI: 3.10 removed from the Ubuntu test matrix (Windows already started at 3.11, for lack of a cp310 GDAL wheel), Linux executables and docs built with 3.11 instead of 3.10, like the Windows executables already were;
- SonarCloud analyzed Python versions, and the development setup docs.

BREAKING CHANGE: DicoGIS no longer installs on Python 3.10.

AI-Level: 7 (human specced, bots coded)
AI-Model: claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dwmm8jY9WWvgUJt9WQ7QWZ
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci-cd Everything related to continuous integration and deployment tooling packaging labels Oct 18, 2026
@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.11%. Comparing base (56a63fa) to head (10572f0).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           master     #514   +/-   ##
=======================================
  Coverage   78.11%   78.11%           
=======================================
  Files          55       55           
  Lines        3473     3473           
  Branches      424      424           
=======================================
  Hits         2713     2713           
  Misses        639      639           
  Partials      121      121           
Flag Coverage Δ
unittests 78.05% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Guts Guts mentioned this pull request Oct 18, 2026
5 of 11 tasks
@Guts
Guts merged commit 6128112 into master Oct 18, 2026
21 of 22 checks passed
@Guts
Guts deleted the packaging/drop-python-3.10 branch October 18, 2026 11:27
Guts added a commit that referenced this pull request Oct 18, 2026
> Stacked on #514 (base `packaging/drop-python-3.10`). **The build
workflow only runs on pull requests targeting `master`**, so it will run
once GitHub retargets this PR, after #514 is merged: that run is the
real validation of the executables.

## Description

Builds the Linux and Windows executables with **Python 3.14** instead of
3.11. Set once, in the workflow's global `PYTHON_VERSION`: the Windows
job no longer overrides it.

### GDAL on Windows

GDAL comes from a
[cgohlke/geospatial-wheels](https://github.com/cgohlke/geospatial-wheels)
wheel, previously hardcoded (GDAL 3.11.1, cp311, release v2025.7.4).
That release does contain a cp314 wheel, but it was built on 4 July
2025, against a Python 3.14 **pre-release**, before the ABI was frozen
at 3.14.0rc1. This PR uses **v2025.10.25**, the first release built
after Python 3.14.0 final, with **GDAL 3.11.4**: same minor version as
the Linux build (`3.11.*`).

The wheel URL is now built from `GDAL_VERSION`,
`GEOSPATIAL_WHEELS_RELEASE` and `PYTHON_VERSION` (the way `tester.yml`
already does); the resulting URL was checked to resolve.

### Checked beforehand

- **PyInstaller** allows Python 3.14 from 6.15.0 on (`requires_python`
of earlier releases stops at `<3.14`); the current range `>=6.8.0,<6.23`
resolves to 6.22.3 there, so no pin change.
- **Compiled runtime dependencies** — PyQt6, PyQt6-Qt6, PyQt6-sip, lxml,
numpy, charset-normalizer — all publish cp314 or abi3 wheels for Windows
and Linux x86_64: no build from source during packaging.
- **Linux GDAL bindings** are built from source against the system
`libgdal`, as the Ubuntu 3.14 test job already does successfully.

### Windows tests aligned

The Windows **test** matrix used the v2025.7.4 wheels (GDAL 3.11.1) and
stopped at 3.13. It now uses the same release and GDAL version as the
builder (v2025.10.25, GDAL 3.11.4, set through the same
`GEOSPATIAL_WHEELS_RELEASE`/`GDAL_VERSION` env variables, with a note in
both workflows to keep them in sync) and runs on **3.11 to 3.14**, like
Ubuntu: the Python version shipped in the Windows executable is now
unit-tested on Windows. The example wheel name in
`docs/development/windows.md` follows.

## Related issue(s)

Follows #514.

## Type of change

- [ ] Bug fix (`fix/…` branch)
- [ ] New feature or enhancement (`feature/…` branch)
- [x] Documentation (`docs/…` branch or `*.md`/`docs/**` changes)
- [x] Packaging / build (`packaging/…` branch or `builder/**` changes)
- [x] Tooling / CI-CD (`tooling/…` branch or `.github/**` changes)
- [ ] Tests only (`tests/**` changes)

## Checklist

- [x] I have read the [Contributing Guidelines](/CONTRIBUTING.md)
- [x] `pre-commit run --all-files` passes locally (on the branch's
changes; workflow YAML validated, pre-commit.ci runs on the PR)
- [ ] I have added or updated tests covering my changes (workflow
change, validated by the build job itself)
- [ ] I have updated the documentation where relevant (nothing documents
the build's Python version)
- [ ] `pytest` passes locally (no code change)

## AI/LLM disclosure

- **Level**: 7 — human specced, bots coded.
- **Model**: claude-opus-5-5
- **Session**: https://claude.ai/code/session_01Dwmm8jY9WWvgUJt9WQ7QWZ

https://claude.ai/code/session_01Dwmm8jY9WWvgUJt9WQ7QWZ

---
_Generated by [Claude Code](https://claude.ai/code)_
Guts added a commit that referenced this pull request Oct 18, 2026
…afe (#513)

## Description

Moves to pgserviceparser 3 (3.0.3, latest), and fixes a GUI binding
issue its upgrade exposed.

### Dependency

`pgserviceparser>=3.0.3,<4`. pgserviceparser 3.0 requires Python >=
3.11: Python 3.10 support was dropped in #514 (it previously kept 2.x on
3.10 through environment markers, no longer needed).

The API DicoGIS uses (`conf_path`, `service_names`, `write_service`,
`remove_service`, `ServiceFileNotFound`) has the same signatures in
2.5.0 and 3.0.3 (compared on both sources): 3.x only adds functions, an
optional `sorted_alphabetically` argument to `service_names()`, and a
retry on read-only service files. The Docker image pin moves to 3.0.3.

### The issue it exposed (first commit)

`import pgserviceparser` 3.x tries to import its new Qt GUI, through
`qgis.PyQt` when QGIS is installed, PyQt6 otherwise. And QGIS's own
`qgis/__init__.py` imports `qgis.gui`, hence `qgis.core`. Measured, with
a fake `qgis.PyQt` on the path standing in for an installed QGIS:

- `dicogis.ui.qt_compat`'s `auto` mode picked `qgis.PyQt` as soon as a
`qgis` package was imported: importing pgserviceparser before the GUI
made a **standalone** run believe it was inside QGIS. Only the current
import order of `dicogis-gui` (binding resolved before pgserviceparser
is imported) avoided it.
- With a QGIS 3 (Qt 5) `qgis.PyQt`, a standalone `dicogis-gui` ends up
loading PyQt5 next to PyQt6 in the same process. No crash observed
offscreen here, but that's fragile — and out of DicoGIS's hands, it's
pgserviceparser importing QGIS.

`auto` now selects `qgis.PyQt` only when the running Qt application is a
`QgsApplication`, looking at an already loaded `qgis.core` without ever
importing QGIS. Inside QGIS (desktop, plugin, `qgis_process`) one runs;
standalone it doesn't, whatever got imported. Tests cover QGIS absent /
imported without application (the pgserviceparser case — fails on the
previous detection) / a plain Qt application / running / a failing
check, in-process and in subprocesses.

### Remaining side effect, not fixable here

pgserviceparser only has a `gui` extra (adding PyQt6), no GUI-free
variant: its `__init__` tries to import the GUI in any case, and it
succeeds whenever PyQt6 or `qgis.PyQt` is importable. So `dicogis-cli`
now loads PyQt6 at import when DicoGIS's `gui` extra is installed
(listing import: 16 → 48 ms here), and QGIS's libraries when QGIS is
installed, both through pgserviceparser. Worth raising with
pgserviceparser upstream: importing the core package shouldn't import a
GUI toolkit.

### Verification

- Real DicoGIS calls against a real `pg_service.conf` (missing file →
`ServiceFileNotFound`, `store_in_pgservice_file()`, `service_names()`,
connection string/URI, `check_usable_pg_services()`,
`remove_service()`): identical results with 2.5.0 and 3.0.3.
- Full suite on Python 3.12 with pgserviceparser 3.0.3: 482 passed,
exactly the same failures as `master` with 2.5.0 in the same environment
(no PostGIS instance, missing fixtures).
- The Docker workflow built and smoke-tested the image with the 3.0.3
pin (first CI run of this PR, all green).

## Related issue(s)

None.

## Type of change

- [x] Bug fix (`fix/…` branch)
- [x] New feature or enhancement (`feature/…` branch)
- [ ] Documentation (`docs/…` branch or `*.md`/`docs/**` changes)
- [x] Packaging / build (`packaging/…` branch or `builder/**` changes)
- [ ] Tooling / CI-CD (`tooling/…` branch or `.github/**` changes)
- [ ] Tests only (`tests/**` changes)

## Checklist

- [x] I have read the [Contributing Guidelines](/CONTRIBUTING.md)
- [x] `pre-commit run --all-files` passes locally (on the branch's
changes)
- [x] I have added or updated tests covering my changes
- [x] I have updated the documentation where relevant (`qt_compat`
docstring, `CLAUDE.md`)
- [x] `pytest` passes locally, apart from the environment failures
listed above, identical on `master`

## AI/LLM disclosure

- **Level**: 7 — human specced, bots coded.
- **Model**: claude-opus-5-5
- **Session**: https://claude.ai/code/session_01Dwmm8jY9WWvgUJt9WQ7QWZ

https://claude.ai/code/session_01Dwmm8jY9WWvgUJt9WQ7QWZ


---
_Generated by [Claude Code](https://claude.ai/code)_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-cd Everything related to continuous integration and deployment documentation Improvements or additions to documentation packaging tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants