From 49177e33b03106b3c3e376068f18d33184587452 Mon Sep 17 00:00:00 2001 From: Pavlo Mishchenko Date: Wed, 2 Sep 2026 12:33:53 +0300 Subject: [PATCH 1/3] docs: add Driver-Server Version Compatibility Matrix --- README.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/README.md b/README.md index f645d481..239015e4 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,70 @@ pip install 'singlestoredb[vectorstore,sqlalchemy]' | `docker` | Docker SDK for automated test container management | | `pytest` | Pytest plugin with SingleStoreDB Docker test fixtures | +### Driver-Server Version Compatibility Matrix + +The table below infers supported SingleStore server versions from each client release date and the [SingleStore software EOL policy](https://docs.singlestore.com/db/v9.1/support/singlestore-software-end-of-life-eol-policy/): a server version is listed if it had already been released and was still inside its two-year support window on that day. Tested Python versions are the smoke-test CI matrix shipped with that release. + +| Driver Version | Release date | Supported engine versions | Tested Python versions | +| -------------- | ------------ | ------------------------- | ---------------------- | +| 1.17.2 | 2026-08-26 | 8.9, 9.0, 9.1 RC | 3.9–3.13 | +| 1.17.1 | 2026-08-11 | 8.9, 9.0, 9.1 RC | 3.9–3.13 | +| 1.17.0 | 2026-06-18 | 8.9, 9.0, 9.1 RC | 3.9–3.13 | +| 1.16.10 | 2026-04-13 | 8.7, 8.9, 9.0, 9.1 RC | 3.9–3.13 | +| 1.16.9 | 2026-02-05 | 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.8 | 2026-01-09 | 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.7 | 2026-01-07 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.6 | 2025-12-11 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.5 | 2025-12-03 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.4 | 2025-12-01 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.3 | 2025-11-20 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.2 | 2025-11-13 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.1 | 2025-11-05 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.0 | 2025-10-23 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.15.8 | 2025-09-26 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.7 | 2025-09-23 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.6 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.5 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.4 | 2025-09-04 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.3 | 2025-08-27 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.2 | 2025-08-18 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.1 | 2025-08-07 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.0 | 2025-07-21 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.14.2 | 2025-06-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.14.1 | 2025-06-10 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.14.0 | 2025-05-29 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.13.0 | 2025-04-16 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.13 | +| 1.12.4 | 2025-04-02 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.3 | 2025-04-01 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.2 | 2025-03-19 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.1 | 2025-03-10 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.0 | 2025-02-27 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.11.0 | 2025-01-09 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.10.0 | 2024-12-10 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.9.0 | 2024-12-04 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.8.0 | 2024-11-19 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.7.2 | 2024-10-16 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.7.1 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.7.0 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.3 | 2024-09-05 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.2 | 2024-08-06 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.1 | 2024-07-31 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.0 | 2024-07-29 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.5.0 | 2024-07-12 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.3 | 2024-07-10 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.2 | 2024-07-09 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.1 | 2024-07-02 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.0 | 2024-06-13 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.3.1 | 2024-05-23 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.3.0 | 2024-05-10 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.2.0 | 2024-04-25 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.1.0 | 2024-04-18 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.4 | 2024-04-03 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.3 | 2024-04-02 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.2 | 2024-03-08 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.1 | 2024-02-16 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.0 | 2024-02-12 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | + ## Documentation https://singlestore-labs.github.io/singlestoredb-python From 12b6c5f1bf36a40a2715c477ba996fef25f61c2d Mon Sep 17 00:00:00 2001 From: Pavlo Mishchenko Date: Mon, 12 Oct 2026 15:48:29 +0300 Subject: [PATCH 2/3] generate matrix programmatially --- COMPATIBILITY.md | 69 ++++++ CONTRIBUTING.md | 14 +- README.md | 66 +----- resources/bump_version.py | 34 ++- resources/generate_compatibility.py | 313 ++++++++++++++++++++++++++++ 5 files changed, 422 insertions(+), 74 deletions(-) create mode 100644 COMPATIBILITY.md create mode 100644 resources/generate_compatibility.py diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md new file mode 100644 index 00000000..8a96b289 --- /dev/null +++ b/COMPATIBILITY.md @@ -0,0 +1,69 @@ +# Driver-Server Version Compatibility Matrix + + + +This file is regenerated for every client release by `resources/bump_version.py`. + +Supported engine versions come from the [SingleStore software EOL policy](https://docs.singlestore.com/db/latest/support/singlestore-software-end-of-life-eol-policy/). An engine is listed when it had been released and its end-of-life date was still in the future on the client release date. The [latest release-candidate index](https://release.memsql.com/rc/index/singlestoredbserver/latest.json) does not publish a start date, so the current release candidate is listed on the current client release. + +Tested Python versions come from the smoke-test CI workflow at each release tag. + +| Driver Version | Release date | Supported engine versions | Tested Python versions | +| -------------- | ------------ | ------------------------- | ---------------------- | +| 1.17.2 | 2026-08-26 | 8.9, 9.0, 9.1 RC | 3.9–3.13 | +| 1.17.1 | 2026-08-11 | 8.9, 9.0 | 3.9–3.13 | +| 1.17.0 | 2026-06-18 | 8.9, 9.0 | 3.9–3.13 | +| 1.16.10 | 2026-04-13 | 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.9 | 2026-02-05 | 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.8 | 2026-01-09 | 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.7 | 2026-01-07 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.6 | 2025-12-11 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.5 | 2025-12-03 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.4 | 2025-12-01 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.3 | 2025-11-20 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.2 | 2025-11-13 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.1 | 2025-11-05 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.0 | 2025-10-23 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.15.8 | 2025-09-26 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.7 | 2025-09-23 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.6 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.5 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.4 | 2025-09-04 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.3 | 2025-08-27 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.2 | 2025-08-18 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.1 | 2025-08-07 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.0 | 2025-07-21 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.14.2 | 2025-06-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.14.1 | 2025-06-10 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.14.0 | 2025-05-29 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.13.0 | 2025-04-16 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.13 | +| 1.12.4 | 2025-04-02 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.3 | 2025-04-01 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.2 | 2025-03-19 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.1 | 2025-03-10 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.0 | 2025-02-27 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.11.0 | 2025-01-09 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.10.0 | 2024-12-10 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.9.0 | 2024-12-04 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.8.0 | 2024-11-19 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.7.2 | 2024-10-16 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.7.1 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.7.0 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.3 | 2024-09-05 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.2 | 2024-08-06 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.1 | 2024-07-31 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.0 | 2024-07-29 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.5.0 | 2024-07-12 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.3 | 2024-07-10 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.2 | 2024-07-09 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.1 | 2024-07-02 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.0 | 2024-06-13 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.3.1 | 2024-05-23 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.3.0 | 2024-05-10 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.2.0 | 2024-04-25 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.1.0 | 2024-04-18 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.4 | 2024-04-03 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.3 | 2024-04-02 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.2 | 2024-03-08 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.1 | 2024-02-16 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.0 | 2024-02-12 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2cbea082..ca350df9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -222,9 +222,17 @@ The script performs the following steps: 3. Updates version in both `pyproject.toml` and `singlestoredb/__init__.py` 4. Generates release notes from git history (or uses provided summary) 5. Opens an editor to customize release notes for `docs/src/whatsnew.rst` -6. Builds the documentation -7. Stages all modified files for commit -8. Optionally commits and pushes changes +6. Regenerates `COMPATIBILITY.md` +7. Builds the documentation +8. Stages all modified files for commit +9. Optionally commits and pushes changes + +`COMPATIBILITY.md` is generated from the release dates in `docs/src/whatsnew.rst`, the Python matrices in each release tag's smoke-test workflow, and the [SingleStore software EOL policy](https://docs.singlestore.com/db/latest/support/singlestore-software-end-of-life-eol-policy/). An engine is listed when it had been released and its end-of-life date was still in the future on that client release date. Regeneration requires network access. To regenerate or verify the file manually, run: + +```bash +python resources/generate_compatibility.py +python resources/generate_compatibility.py --verify-matrix +``` ### Creating a Release diff --git a/README.md b/README.md index 239015e4..afb19064 100644 --- a/README.md +++ b/README.md @@ -72,69 +72,9 @@ pip install 'singlestoredb[vectorstore,sqlalchemy]' | `docker` | Docker SDK for automated test container management | | `pytest` | Pytest plugin with SingleStoreDB Docker test fixtures | -### Driver-Server Version Compatibility Matrix - -The table below infers supported SingleStore server versions from each client release date and the [SingleStore software EOL policy](https://docs.singlestore.com/db/v9.1/support/singlestore-software-end-of-life-eol-policy/): a server version is listed if it had already been released and was still inside its two-year support window on that day. Tested Python versions are the smoke-test CI matrix shipped with that release. - -| Driver Version | Release date | Supported engine versions | Tested Python versions | -| -------------- | ------------ | ------------------------- | ---------------------- | -| 1.17.2 | 2026-08-26 | 8.9, 9.0, 9.1 RC | 3.9–3.13 | -| 1.17.1 | 2026-08-11 | 8.9, 9.0, 9.1 RC | 3.9–3.13 | -| 1.17.0 | 2026-06-18 | 8.9, 9.0, 9.1 RC | 3.9–3.13 | -| 1.16.10 | 2026-04-13 | 8.7, 8.9, 9.0, 9.1 RC | 3.9–3.13 | -| 1.16.9 | 2026-02-05 | 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.8 | 2026-01-09 | 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.7 | 2026-01-07 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.6 | 2025-12-11 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.5 | 2025-12-03 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.4 | 2025-12-01 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.3 | 2025-11-20 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.2 | 2025-11-13 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.1 | 2025-11-05 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.0 | 2025-10-23 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.15.8 | 2025-09-26 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.7 | 2025-09-23 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.6 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.5 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.4 | 2025-09-04 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.3 | 2025-08-27 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.2 | 2025-08-18 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.1 | 2025-08-07 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.0 | 2025-07-21 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.14.2 | 2025-06-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.14.1 | 2025-06-10 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.14.0 | 2025-05-29 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.13.0 | 2025-04-16 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.13 | -| 1.12.4 | 2025-04-02 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.12.3 | 2025-04-01 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.12.2 | 2025-03-19 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.12.1 | 2025-03-10 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.12.0 | 2025-02-27 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.11.0 | 2025-01-09 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.10.0 | 2024-12-10 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.9.0 | 2024-12-04 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.8.0 | 2024-11-19 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.7.2 | 2024-10-16 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.7.1 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.7.0 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.6.3 | 2024-09-05 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.6.2 | 2024-08-06 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.6.1 | 2024-07-31 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.6.0 | 2024-07-29 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.5.0 | 2024-07-12 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.4.3 | 2024-07-10 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.4.2 | 2024-07-09 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.4.1 | 2024-07-02 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.4.0 | 2024-06-13 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.3.1 | 2024-05-23 | 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.3.0 | 2024-05-10 | 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.2.0 | 2024-04-25 | 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.1.0 | 2024-04-18 | 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.4 | 2024-04-03 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.3 | 2024-04-02 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.2 | 2024-03-08 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.1 | 2024-02-16 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.0 | 2024-02-12 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +### SingleStore Server Compatibility + +See the [driver, server, and Python version compatibility matrix](COMPATIBILITY.md). ## Documentation diff --git a/resources/bump_version.py b/resources/bump_version.py index 488a8832..dc6d34aa 100755 --- a/resources/bump_version.py +++ b/resources/bump_version.py @@ -389,6 +389,19 @@ def build_docs() -> None: status('✅ Documentation built successfully') +def generate_compatibility() -> None: + """Regenerate the driver, server, and Python compatibility matrix.""" + generator = Path(__file__).parent / 'generate_compatibility.py' + result = subprocess.run([sys.executable, str(generator)]) + if result.returncode != 0: + print( + '❌ Error generating COMPATIBILITY.md', + file=sys.stderr, + ) + sys.exit(1) + status('✅ Compatibility matrix generated successfully') + + def stage_files() -> None: """Stage all modified files for commit.""" status('📦 Staging files for commit...') @@ -397,6 +410,7 @@ def stage_files() -> None: 'pyproject.toml', 'singlestoredb/__init__.py', 'docs/src/whatsnew.rst', + 'COMPATIBILITY.md', 'docs/', # All generated documentation files ] @@ -435,7 +449,7 @@ def main() -> None: print('=' * 50, file=sys.stderr) # Step 1: Get current version - step(1, 6, 'Reading current version') + step(1, 7, 'Reading current version') current_version = get_current_version() status(f'Current version: {current_version}') @@ -444,7 +458,7 @@ def main() -> None: status(f'New version will be: {new_version}') # Step 2: Update version in files - step(2, 6, 'Updating version in files') + step(2, 7, 'Updating version in files') start_time = time.time() update_version_in_file(Path(__file__).parent.parent / 'pyproject.toml', current_version, new_version) @@ -461,7 +475,7 @@ def main() -> None: status(f'✅ Version files updated in {elapsed:.1f}s') # Step 3: Generate release summary - step(3, 6, 'Generating release summary') + step(3, 7, 'Generating release summary') start_time = time.time() if args.summary: @@ -477,7 +491,7 @@ def main() -> None: status(f'✅ Release summary generated in {elapsed:.1f}s') # Step 4: Update whatsnew.rst with editor - step(4, 6, 'Updating release notes') + step(4, 7, 'Updating release notes') start_time = time.time() status('📝 Opening editor for release notes...') @@ -499,12 +513,16 @@ def main() -> None: elapsed = time.time() - start_time status(f'✅ Release notes updated in {elapsed:.1f}s') - # Step 5: Build documentation - step(5, 6, 'Building documentation') + # Step 5: Generate compatibility matrix + step(5, 7, 'Generating compatibility matrix') + generate_compatibility() + + # Step 6: Build documentation + step(6, 7, 'Building documentation') build_docs() - # Step 6: Stage files - step(6, 6, 'Staging files for commit') + # Step 7: Stage files + step(7, 7, 'Staging files for commit') stage_files() total_elapsed = time.time() - total_start_time diff --git a/resources/generate_compatibility.py b/resources/generate_compatibility.py new file mode 100644 index 00000000..7ec604a4 --- /dev/null +++ b/resources/generate_compatibility.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +"""Generate the driver, server, and Python compatibility matrix.""" +from __future__ import annotations + +import argparse +import datetime +import json +import re +import subprocess +import sys +import urllib.request +from collections.abc import Sequence +from html.parser import HTMLParser +from pathlib import Path +from typing import Dict +from typing import List +from typing import Tuple + + +ROOT = Path(__file__).parent.parent +OUTPUT_PATH = ROOT / 'COMPATIBILITY.md' +WHATSNEW_PATH = ROOT / 'docs' / 'src' / 'whatsnew.rst' +WORKFLOW_PATH = '.github/workflows/smoke-test.yml' +VERSION_PATTERN = re.compile(r'^v(\d+\.\d+\.\d+) - (.+)$', re.MULTILINE) +EOL_PAGE_URL = ( + 'https://docs.singlestore.com/db/latest/support/' + 'singlestore-software-end-of-life-eol-policy/' +) +RC_VERSIONS_URL = ( + 'https://release.memsql.com/rc/index/singlestoredbserver/latest.json' +) +EngineRelease = Tuple[str, datetime.date, datetime.date] + + +def parse_date(value: str) -> datetime.date: + """Parse either an ISO date or a release-note date.""" + value = value.split('(', 1)[0].strip() + try: + return datetime.date.fromisoformat(value) + except ValueError: + return datetime.datetime.strptime( + value.replace(',', ''), + '%B %d %Y', + ).date() + + +def version_key(version: str) -> Tuple[int, ...]: + """Return a sortable key for a dotted version.""" + return tuple(int(item) for item in version.split('.')) + + +def get_current_version() -> str: + """Read the current package version.""" + content = (ROOT / 'pyproject.toml').read_text() + match = re.search( + r'^version\s*=\s*["\'](.+)["\']$', + content, + re.MULTILINE, + ) + if not match: + raise ValueError('Could not find version in pyproject.toml') + return match.group(1) + + +def get_client_releases() -> List[Tuple[str, datetime.date]]: + """Read stable client releases and dates from the changelog.""" + releases: Dict[str, datetime.date] = {} + for match in VERSION_PATTERN.finditer(WHATSNEW_PATH.read_text()): + version = match.group(1) + if version_key(version)[0] < 1 or version in releases: + continue + releases[version] = parse_date(match.group(2)) + return sorted( + releases.items(), + key=lambda item: version_key(item[0]), + reverse=True, + ) + + +def read_workflow(version: str, current_version: str) -> str: + """Read the smoke-test workflow used by a release.""" + if version == current_version: + return (ROOT / WORKFLOW_PATH).read_text() + + result = subprocess.run( + ['git', 'show', f'v{version}:{WORKFLOW_PATH}'], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + if result.returncode != 0: + raise RuntimeError( + f'Could not read {WORKFLOW_PATH} from tag v{version}: ' + f'{result.stderr.strip()}', + ) + return result.stdout + + +def get_tested_python_versions(workflow: str) -> List[str]: + """Extract the Python matrix from a smoke-test workflow.""" + lines = workflow.splitlines() + for index, line in enumerate(lines): + if line.strip() != 'python-version:': + continue + + indentation = len(line) - len(line.lstrip()) + versions: List[str] = [] + for item in lines[index + 1:]: + item_indentation = len(item) - len(item.lstrip()) + if item.strip() and item_indentation <= indentation: + break + match = re.match(r'\s*-\s+["\']?(3\.\d+)["\']?\s*$', item) + if match: + versions.append(match.group(1)) + if versions: + return versions + + raise ValueError('Could not find the Python matrix in smoke-test.yml') + + +def format_python_versions(versions: Sequence[str]) -> str: + """Format a Python version list as a compact range when possible.""" + minors = [int(version.split('.')[1]) for version in versions] + if minors == list(range(minors[0], minors[-1] + 1)): + return f'{versions[0]}–{versions[-1]}' + return ', '.join(versions) + + +def fetch_bytes(url: str) -> bytes: + """Fetch a URL used to build the compatibility matrix.""" + request = urllib.request.Request( + url, + headers={'User-Agent': 'singlestoredb-python-compatibility'}, + ) + with urllib.request.urlopen(request, timeout=30) as response: + return response.read() + + +class _EOLTableParser(HTMLParser): + """Extract the engine lifecycle table from the EOL policy page.""" + + def __init__(self) -> None: + super().__init__() + self.rows: List[List[str]] = [] + self._in_table = False + self._in_cell = False + self._row: List[str] = [] + self._cell: List[str] = [] + + def handle_starttag( + self, + tag: str, + attrs: List[Tuple[str, str | None]], + ) -> None: + attributes = dict(attrs) + if tag == 'table' and 'table-component' in (attributes.get('class') or ''): + self._in_table = True + elif self._in_table and tag == 'tr': + self._row = [] + elif self._in_table and tag in ('td', 'th'): + self._in_cell = True + self._cell = [] + + def handle_endtag(self, tag: str) -> None: + if tag == 'table' and self._in_table: + self._in_table = False + elif self._in_table and tag in ('td', 'th') and self._in_cell: + self._row.append(''.join(self._cell).strip()) + self._in_cell = False + elif self._in_table and tag == 'tr' and self._row: + self.rows.append(self._row) + + def handle_data(self, data: str) -> None: + if self._in_cell: + self._cell.append(data) + + +def fetch_engine_releases() -> List[EngineRelease]: + """Read engine release and EOL dates from the EOL policy page.""" + parser = _EOLTableParser() + parser.feed(fetch_bytes(EOL_PAGE_URL).decode('utf-8', 'replace')) + releases = [] + for row in parser.rows: + if len(row) < 3 or row[0] == 'Version': + continue + releases.append(( + row[0].split(' v', 1)[-1], + datetime.datetime.strptime(row[1], '%B %d, %Y').date(), + datetime.datetime.strptime(row[2], '%B %d, %Y').date(), + )) + if not releases: + raise RuntimeError(f'Could not parse engine versions from {EOL_PAGE_URL}') + return releases + + +def fetch_rc_version() -> str: + """Return the latest release candidate as major.minor.""" + data = json.loads(fetch_bytes(RC_VERSIONS_URL).decode('utf-8')) + version = str(data.get('version') or '') + parts = version.split('.') + if len(parts) >= 2: + return f'{parts[0]}.{parts[1]}' + return version + + +def engines_supported_on( + as_of: datetime.date, + engines: Sequence[EngineRelease], + rc_version: str, + include_rc: bool, +) -> List[str]: + """Return engines inside their support window on a client release date.""" + supported = [ + version + for version, release_date, end_of_life in engines + if release_date <= as_of and end_of_life > as_of + ] + if include_rc and rc_version and rc_version not in supported: + supported.append(f'{rc_version} RC') + return sorted(supported, key=lambda version: version_key(version.split()[0])) + + +def format_table(rows: Sequence[Sequence[str]]) -> str: + """Render an aligned Markdown table.""" + widths = [ + max(len(row[index]) for row in rows) + for index in range(len(rows[0])) + ] + + def render(row: Sequence[str]) -> str: + cells = [ + value.ljust(width) + for value, width in zip(row, widths) + ] + return '| ' + ' | '.join(cells) + ' |' + + separator = '| ' + ' | '.join('-' * width for width in widths) + ' |' + return '\n'.join([render(rows[0]), separator, *map(render, rows[1:])]) + + +def generate() -> str: + """Generate the compatibility document.""" + engines = fetch_engine_releases() + rc_version = fetch_rc_version() + current_version = get_current_version() + releases = get_client_releases() + rows = [[ + 'Driver Version', + 'Release date', + 'Supported engine versions', + 'Tested Python versions', + ]] + + for version, release_date in releases: + workflow = read_workflow(version, current_version) + python_versions = format_python_versions( + get_tested_python_versions(workflow), + ) + supported_engines = engines_supported_on( + release_date, + engines, + rc_version, + include_rc=version == current_version, + ) + rows.append([ + version, + release_date.isoformat(), + ', '.join(supported_engines), + python_versions, + ]) + + table = format_table(rows) + return f"""# Driver-Server Version Compatibility Matrix + + + +This file is regenerated for every client release by `resources/bump_version.py`. + +Supported engine versions come from the [SingleStore software EOL policy]({EOL_PAGE_URL}). An engine is listed when it had been released and its end-of-life date was still in the future on the client release date. The [latest release-candidate index]({RC_VERSIONS_URL}) does not publish a start date, so the current release candidate is listed on the current client release. + +Tested Python versions come from the smoke-test CI workflow at each release tag. + +{table} +""" + + +def main() -> None: + """Write the generated document or check that it is current.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--verify-matrix', + action='store_true', + help='fail if COMPATIBILITY.md is not current', + ) + args = parser.parse_args() + + content = generate() + if args.verify_matrix: + if not OUTPUT_PATH.exists() or OUTPUT_PATH.read_text() != content: + print( + 'COMPATIBILITY.md is out of date; run ' + 'python resources/generate_compatibility.py', + file=sys.stderr, + ) + sys.exit(1) + return + + OUTPUT_PATH.write_text(content) + + +if __name__ == '__main__': + main() From 5441827da6e4260cd8243f2b377bed4627a22160 Mon Sep 17 00:00:00 2001 From: Pavlo Mishchenko Date: Mon, 12 Oct 2026 16:08:06 +0300 Subject: [PATCH 3/3] improve generate_compatibility --- COMPATIBILITY.md | 125 ++++++++++++------------ CONTRIBUTING.md | 9 +- README.md | 2 +- resources/bump_version.py | 5 +- resources/generate_compatibility.py | 145 ++++++++++++++++++---------- 5 files changed, 170 insertions(+), 116 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 8a96b289..e47117a9 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -2,68 +2,71 @@ -This file is regenerated for every client release by `resources/bump_version.py`. +`resources/bump_version.py` adds a row for every client release. Published rows are kept as they are, so each row reflects what was known on that release date. -Supported engine versions come from the [SingleStore software EOL policy](https://docs.singlestore.com/db/latest/support/singlestore-software-end-of-life-eol-policy/). An engine is listed when it had been released and its end-of-life date was still in the future on the client release date. The [latest release-candidate index](https://release.memsql.com/rc/index/singlestoredbserver/latest.json) does not publish a start date, so the current release candidate is listed on the current client release. +Engine versions in support come from the [SingleStore software EOL policy](https://docs.singlestore.com/db/latest/support/singlestore-software-end-of-life-eol-policy/). An engine is listed when it had been released and its end-of-life date was still in the future on the client release date. These engine versions are not each tested in CI; the smoke tests run against a single managed SingleStore cluster. A version marked RC was the [latest release candidate](https://release.memsql.com/rc/index/singlestoredbserver/latest.json) when that client version was released. Tested Python versions come from the smoke-test CI workflow at each release tag. -| Driver Version | Release date | Supported engine versions | Tested Python versions | -| -------------- | ------------ | ------------------------- | ---------------------- | -| 1.17.2 | 2026-08-26 | 8.9, 9.0, 9.1 RC | 3.9–3.13 | -| 1.17.1 | 2026-08-11 | 8.9, 9.0 | 3.9–3.13 | -| 1.17.0 | 2026-06-18 | 8.9, 9.0 | 3.9–3.13 | -| 1.16.10 | 2026-04-13 | 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.9 | 2026-02-05 | 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.8 | 2026-01-09 | 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.7 | 2026-01-07 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.6 | 2025-12-11 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.5 | 2025-12-03 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.4 | 2025-12-01 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.3 | 2025-11-20 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.2 | 2025-11-13 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.1 | 2025-11-05 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.16.0 | 2025-10-23 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | -| 1.15.8 | 2025-09-26 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.7 | 2025-09-23 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.6 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.5 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.4 | 2025-09-04 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.3 | 2025-08-27 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.2 | 2025-08-18 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.1 | 2025-08-07 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.15.0 | 2025-07-21 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.14.2 | 2025-06-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.14.1 | 2025-06-10 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.14.0 | 2025-05-29 | 8.5, 8.7, 8.9 | 3.9–3.13 | -| 1.13.0 | 2025-04-16 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.13 | -| 1.12.4 | 2025-04-02 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.12.3 | 2025-04-01 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.12.2 | 2025-03-19 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.12.1 | 2025-03-10 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.12.0 | 2025-02-27 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.11.0 | 2025-01-09 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.10.0 | 2024-12-10 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.9.0 | 2024-12-04 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.8.0 | 2024-11-19 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | -| 1.7.2 | 2024-10-16 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.7.1 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.7.0 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.6.3 | 2024-09-05 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.6.2 | 2024-08-06 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.6.1 | 2024-07-31 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.6.0 | 2024-07-29 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.5.0 | 2024-07-12 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.4.3 | 2024-07-10 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.4.2 | 2024-07-09 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.4.1 | 2024-07-02 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.4.0 | 2024-06-13 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | -| 1.3.1 | 2024-05-23 | 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.3.0 | 2024-05-10 | 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.2.0 | 2024-04-25 | 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.1.0 | 2024-04-18 | 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.4 | 2024-04-03 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.3 | 2024-04-02 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.2 | 2024-03-08 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.1 | 2024-02-16 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | -| 1.0.0 | 2024-02-12 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| Driver Version | Release date | Engine versions in support | Tested Python versions | +| -------------- | ------------ | -------------------------- | ---------------------- | +| 1.18.1 | 2026-10-02 | 8.9, 9.0 | 3.9–3.14 | +| 1.18.0 | 2026-10-01 | 8.9, 9.0 | 3.9–3.14 | +| 1.17.3 | 2026-09-10 | 8.9, 9.0 | 3.9–3.13 | +| 1.17.2 | 2026-08-26 | 8.9, 9.0 | 3.9–3.13 | +| 1.17.1 | 2026-08-11 | 8.9, 9.0 | 3.9–3.13 | +| 1.17.0 | 2026-06-18 | 8.9, 9.0 | 3.9–3.13 | +| 1.16.10 | 2026-04-13 | 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.9 | 2026-02-05 | 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.8 | 2026-01-09 | 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.7 | 2026-01-07 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.6 | 2025-12-11 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.5 | 2025-12-03 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.4 | 2025-12-01 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.3 | 2025-11-20 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.2 | 2025-11-13 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.1 | 2025-11-05 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.16.0 | 2025-10-23 | 8.5, 8.7, 8.9, 9.0 | 3.9–3.13 | +| 1.15.8 | 2025-09-26 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.7 | 2025-09-23 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.6 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.5 | 2025-09-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.4 | 2025-09-04 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.3 | 2025-08-27 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.2 | 2025-08-18 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.1 | 2025-08-07 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.15.0 | 2025-07-21 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.14.2 | 2025-06-16 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.14.1 | 2025-06-10 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.14.0 | 2025-05-29 | 8.5, 8.7, 8.9 | 3.9–3.13 | +| 1.13.0 | 2025-04-16 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.13 | +| 1.12.4 | 2025-04-02 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.3 | 2025-04-01 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.2 | 2025-03-19 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.1 | 2025-03-10 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.12.0 | 2025-02-27 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.11.0 | 2025-01-09 | 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.10.0 | 2024-12-10 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.9.0 | 2024-12-04 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.8.0 | 2024-11-19 | 8.0, 8.1, 8.5, 8.7, 8.9 | 3.8–3.12 | +| 1.7.2 | 2024-10-16 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.7.1 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.7.0 | 2024-09-20 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.3 | 2024-09-05 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.2 | 2024-08-06 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.1 | 2024-07-31 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.6.0 | 2024-07-29 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.5.0 | 2024-07-12 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.3 | 2024-07-10 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.2 | 2024-07-09 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.1 | 2024-07-02 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.4.0 | 2024-06-13 | 8.0, 8.1, 8.5, 8.7 | 3.8–3.12 | +| 1.3.1 | 2024-05-23 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.3.0 | 2024-05-10 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.2.0 | 2024-04-25 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.1.0 | 2024-04-18 | 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.4 | 2024-04-03 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.3 | 2024-04-02 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.2 | 2024-03-08 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.1 | 2024-02-16 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | +| 1.0.0 | 2024-02-12 | 7.8, 8.0, 8.1, 8.5 | 3.8–3.12 | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca350df9..23a65e33 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -222,16 +222,21 @@ The script performs the following steps: 3. Updates version in both `pyproject.toml` and `singlestoredb/__init__.py` 4. Generates release notes from git history (or uses provided summary) 5. Opens an editor to customize release notes for `docs/src/whatsnew.rst` -6. Regenerates `COMPATIBILITY.md` +6. Adds the new release to `COMPATIBILITY.md` 7. Builds the documentation 8. Stages all modified files for commit 9. Optionally commits and pushes changes -`COMPATIBILITY.md` is generated from the release dates in `docs/src/whatsnew.rst`, the Python matrices in each release tag's smoke-test workflow, and the [SingleStore software EOL policy](https://docs.singlestore.com/db/latest/support/singlestore-software-end-of-life-eol-policy/). An engine is listed when it had been released and its end-of-life date was still in the future on that client release date. Regeneration requires network access. To regenerate or verify the file manually, run: +If `COMPATIBILITY.md` cannot be generated, for example without network access, the script logs a warning and continues the release. Run `python resources/generate_compatibility.py` afterwards to add the missing row. + +`COMPATIBILITY.md` is generated from the release dates in `docs/src/whatsnew.rst`, the Python matrices in each release tag's smoke-test workflow, and the [SingleStore software EOL policy](https://docs.singlestore.com/db/latest/support/singlestore-software-end-of-life-eol-policy/). An engine is listed when it had been released and its end-of-life date was still in the future on that client release date. The [latest release candidate](https://release.memsql.com/rc/index/singlestoredbserver/latest.json) is added only to a release whose tag does not exist yet, which is the release being prepared. + +Rows already in `COMPATIBILITY.md` are kept as they are; only releases missing from the file are generated, and only those need network access. To add missing rows, check that the file is current, or regenerate every row, run: ```bash python resources/generate_compatibility.py python resources/generate_compatibility.py --verify-matrix +python resources/generate_compatibility.py --rebuild ``` ### Creating a Release diff --git a/README.md b/README.md index afb19064..04a38efc 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ pip install 'singlestoredb[vectorstore,sqlalchemy]' ### SingleStore Server Compatibility -See the [driver, server, and Python version compatibility matrix](COMPATIBILITY.md). +See the [driver, server, and Python version compatibility matrix](https://github.com/singlestore-labs/singlestoredb-python/blob/main/COMPATIBILITY.md). ## Documentation diff --git a/resources/bump_version.py b/resources/bump_version.py index dc6d34aa..017f338a 100755 --- a/resources/bump_version.py +++ b/resources/bump_version.py @@ -395,10 +395,11 @@ def generate_compatibility() -> None: result = subprocess.run([sys.executable, str(generator)]) if result.returncode != 0: print( - '❌ Error generating COMPATIBILITY.md', + '⚠️ Could not generate COMPATIBILITY.md; continuing without it. ' + 'Run python resources/generate_compatibility.py after the release.', file=sys.stderr, ) - sys.exit(1) + return status('✅ Compatibility matrix generated successfully') diff --git a/resources/generate_compatibility.py b/resources/generate_compatibility.py index 7ec604a4..5627fb39 100644 --- a/resources/generate_compatibility.py +++ b/resources/generate_compatibility.py @@ -14,6 +14,7 @@ from pathlib import Path from typing import Dict from typing import List +from typing import Optional from typing import Tuple @@ -22,6 +23,7 @@ WHATSNEW_PATH = ROOT / 'docs' / 'src' / 'whatsnew.rst' WORKFLOW_PATH = '.github/workflows/smoke-test.yml' VERSION_PATTERN = re.compile(r'^v(\d+\.\d+\.\d+) - (.+)$', re.MULTILINE) +ROW_VERSION_PATTERN = re.compile(r'^\d+\.\d+\.\d+$') EOL_PAGE_URL = ( 'https://docs.singlestore.com/db/latest/support/' 'singlestore-software-end-of-life-eol-policy/' @@ -29,6 +31,12 @@ RC_VERSIONS_URL = ( 'https://release.memsql.com/rc/index/singlestoredbserver/latest.json' ) +HEADER = [ + 'Driver Version', + 'Release date', + 'Engine versions in support', + 'Tested Python versions', +] EngineRelease = Tuple[str, datetime.date, datetime.date] @@ -77,11 +85,22 @@ def get_client_releases() -> List[Tuple[str, datetime.date]]: ) -def read_workflow(version: str, current_version: str) -> str: - """Read the smoke-test workflow used by a release.""" - if version == current_version: - return (ROOT / WORKFLOW_PATH).read_text() +def read_existing_rows() -> Dict[str, List[str]]: + """Read the rows already published in COMPATIBILITY.md.""" + if not OUTPUT_PATH.exists(): + return {} + rows: Dict[str, List[str]] = {} + for line in OUTPUT_PATH.read_text().splitlines(): + if not line.startswith('|'): + continue + cells = [cell.strip() for cell in line.strip().strip('|').split('|')] + if len(cells) == len(HEADER) and ROW_VERSION_PATTERN.match(cells[0]): + rows[cells[0]] = cells + return rows + +def read_tagged_workflow(version: str) -> Optional[str]: + """Read the smoke-test workflow at a release tag, if the tag exists.""" result = subprocess.run( ['git', 'show', f'v{version}:{WORKFLOW_PATH}'], cwd=ROOT, @@ -89,23 +108,38 @@ def read_workflow(version: str, current_version: str) -> str: text=True, check=False, ) - if result.returncode != 0: - raise RuntimeError( - f'Could not read {WORKFLOW_PATH} from tag v{version}: ' - f'{result.stderr.strip()}', - ) - return result.stdout + return result.stdout if result.returncode == 0 else None + + +def read_workflow(version: str, current_version: str) -> str: + """Read the smoke-test workflow used by a release.""" + workflow = read_tagged_workflow(version) + if workflow is not None: + return workflow + if version == current_version: + return (ROOT / WORKFLOW_PATH).read_text() + raise RuntimeError(f'Could not read {WORKFLOW_PATH} from tag v{version}') def get_tested_python_versions(workflow: str) -> List[str]: """Extract the Python matrix from a smoke-test workflow.""" lines = workflow.splitlines() for index, line in enumerate(lines): - if line.strip() != 'python-version:': + key, _, value = line.strip().partition(':') + if key != 'python-version': + continue + + value = value.strip() + if value.startswith('['): + versions = re.findall(r'3\.\d+', value) + if versions: + return versions + continue + if value: continue indentation = len(line) - len(line.lstrip()) - versions: List[str] = [] + versions = [] for item in lines[index + 1:]: item_indentation = len(item) - len(item.lstrip()) if item.strip() and item_indentation <= indentation: @@ -138,12 +172,13 @@ def fetch_bytes(url: str) -> bytes: class _EOLTableParser(HTMLParser): - """Extract the engine lifecycle table from the EOL policy page.""" + """Extract the first engine lifecycle table from the EOL policy page.""" def __init__(self) -> None: super().__init__() self.rows: List[List[str]] = [] self._in_table = False + self._table_done = False self._in_cell = False self._row: List[str] = [] self._cell: List[str] = [] @@ -154,7 +189,11 @@ def handle_starttag( attrs: List[Tuple[str, str | None]], ) -> None: attributes = dict(attrs) - if tag == 'table' and 'table-component' in (attributes.get('class') or ''): + if ( + tag == 'table' + and not self._table_done + and 'table-component' in (attributes.get('class') or '') + ): self._in_table = True elif self._in_table and tag == 'tr': self._row = [] @@ -165,6 +204,7 @@ def handle_starttag( def handle_endtag(self, tag: str) -> None: if tag == 'table' and self._in_table: self._in_table = False + self._table_done = True elif self._in_table and tag in ('td', 'th') and self._in_cell: self._row.append(''.join(self._cell).strip()) self._in_cell = False @@ -184,11 +224,16 @@ def fetch_engine_releases() -> List[EngineRelease]: for row in parser.rows: if len(row) < 3 or row[0] == 'Version': continue - releases.append(( - row[0].split(' v', 1)[-1], - datetime.datetime.strptime(row[1], '%B %d, %Y').date(), - datetime.datetime.strptime(row[2], '%B %d, %Y').date(), - )) + try: + releases.append(( + row[0].split(' v', 1)[-1], + datetime.datetime.strptime(row[1], '%B %d, %Y').date(), + datetime.datetime.strptime(row[2], '%B %d, %Y').date(), + )) + except ValueError as exc: + raise RuntimeError( + f'Could not parse engine row {row} from {EOL_PAGE_URL}', + ) from exc if not releases: raise RuntimeError(f'Could not parse engine versions from {EOL_PAGE_URL}') return releases @@ -207,8 +252,7 @@ def fetch_rc_version() -> str: def engines_supported_on( as_of: datetime.date, engines: Sequence[EngineRelease], - rc_version: str, - include_rc: bool, + rc_version: Optional[str], ) -> List[str]: """Return engines inside their support window on a client release date.""" supported = [ @@ -216,7 +260,7 @@ def engines_supported_on( for version, release_date, end_of_life in engines if release_date <= as_of and end_of_life > as_of ] - if include_rc and rc_version and rc_version not in supported: + if rc_version and rc_version not in supported: supported.append(f'{rc_version} RC') return sorted(supported, key=lambda version: version_key(version.split()[0])) @@ -239,35 +283,30 @@ def render(row: Sequence[str]) -> str: return '\n'.join([render(rows[0]), separator, *map(render, rows[1:])]) -def generate() -> str: +def generate(rebuild: bool = False) -> str: """Generate the compatibility document.""" - engines = fetch_engine_releases() - rc_version = fetch_rc_version() current_version = get_current_version() - releases = get_client_releases() - rows = [[ - 'Driver Version', - 'Release date', - 'Supported engine versions', - 'Tested Python versions', - ]] - - for version, release_date in releases: + existing = {} if rebuild else read_existing_rows() + engines: Optional[List[EngineRelease]] = None + rows = [HEADER] + + for version, release_date in get_client_releases(): + if version in existing: + rows.append(existing[version]) + continue + + if engines is None: + engines = fetch_engine_releases() + rc_version = None + if version == current_version and read_tagged_workflow(version) is None: + rc_version = fetch_rc_version() + workflow = read_workflow(version, current_version) - python_versions = format_python_versions( - get_tested_python_versions(workflow), - ) - supported_engines = engines_supported_on( - release_date, - engines, - rc_version, - include_rc=version == current_version, - ) rows.append([ version, release_date.isoformat(), - ', '.join(supported_engines), - python_versions, + ', '.join(engines_supported_on(release_date, engines, rc_version)), + format_python_versions(get_tested_python_versions(workflow)), ]) table = format_table(rows) @@ -275,9 +314,9 @@ def generate() -> str: -This file is regenerated for every client release by `resources/bump_version.py`. +`resources/bump_version.py` adds a row for every client release. Published rows are kept as they are, so each row reflects what was known on that release date. -Supported engine versions come from the [SingleStore software EOL policy]({EOL_PAGE_URL}). An engine is listed when it had been released and its end-of-life date was still in the future on the client release date. The [latest release-candidate index]({RC_VERSIONS_URL}) does not publish a start date, so the current release candidate is listed on the current client release. +Engine versions in support come from the [SingleStore software EOL policy]({EOL_PAGE_URL}). An engine is listed when it had been released and its end-of-life date was still in the future on the client release date. These engine versions are not each tested in CI; the smoke tests run against a single managed SingleStore cluster. A version marked RC was the [latest release candidate]({RC_VERSIONS_URL}) when that client version was released. Tested Python versions come from the smoke-test CI workflow at each release tag. @@ -288,14 +327,20 @@ def generate() -> str: def main() -> None: """Write the generated document or check that it is current.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( + group = parser.add_mutually_exclusive_group() + group.add_argument( '--verify-matrix', action='store_true', - help='fail if COMPATIBILITY.md is not current', + help='fail if COMPATIBILITY.md is missing a release or is not current', + ) + group.add_argument( + '--rebuild', + action='store_true', + help='regenerate every row instead of keeping published rows', ) args = parser.parse_args() - content = generate() + content = generate(rebuild=args.rebuild) if args.verify_matrix: if not OUTPUT_PATH.exists() or OUTPUT_PATH.read_text() != content: print(