Skip to content

Commit 2284616

Browse files
authored
refactor(python): drop Python 3.9 support (#587)
## Summary This PR drops Python 3.9 support from RedisVL and makes Python 3.10 the project minimum. That change is carried through package metadata, the lockfile, CI configuration, repo-owned documentation, and the codebase’s type annotations so the stated support policy and the implementation match. ## Motivation Supporting Python 3.9 was already imposing ongoing cost in a few different ways. It required extra compatibility handling, kept the project split between core-package and MCP-specific runtime messaging, and forced older annotation syntax across large parts of the repository. Python 3.9 is also past its official end-of-life, so continuing to carry support for it no longer has a strong maintenance or ecosystem justification. The project already targets newer runtimes in practice, which makes keeping 3.9 mostly maintenance overhead. ## Changes The support-floor change starts in packaging and CI. `pyproject.toml`, `uv.lock`, and the GitHub Actions workflows now treat Python 3.10 as the minimum supported version, while preserving current newer-runtime coverage, including Python 3.14 in lint and test configuration. This aligns the declared support policy with the environments the project is expected to run and validate against. The library code has also been updated to use Python 3.10-native typing syntax throughout repo-owned Python sources. Legacy `typing` container aliases and `Optional`/`Union` forms were replaced with built-in generics and `|` unions where valid, while runtime-sensitive sites were adjusted manually so forward references, type checks, and schema/type-introspection behavior remain valid without adding `from __future__ import annotations`. Additional follow-up changes include: - Moving `Annotated` imports to the standard library where supported - Keeping `typing_extensions.Self` in place where it is still required for Python 3.10 compatibility - Removing the redundant MCP CLI runtime guard that duplicated the package-level Python requirement - Updating README content, installation guidance, MCP docs, notebook prose, and tests that assert exact type-hint shapes so they align with the new support floor and modernized annotations ## Note for Reviewers The docs build still reports existing Sphinx warnings that are unrelated to this PR. ## Testing The following checks were run during development: - `make check-sort-imports` - `make check-format` - `make check-types` - `uv run python -m tests.test_imports redisvl` - `uv run python -m pytest tests/unit/test_validation.py tests/unit/test_index_schema_type_issue.py tests/unit/test_base_vectorizer.py tests/unit/test_cli_utils.py tests/unit/test_hybrid_types.py tests/unit/test_mcp/test_search_tool_unit.py tests/unit/test_mcp/test_upsert_tool_unit.py tests/integration/test_mcp/test_server_startup.py tests/integration/test_mcp/test_upsert_tool.py -q` - `make docs-build` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it is a breaking support-policy change (Python >=3.10) and touches broad, core surfaces (index/storage/cache/router/CLI) via widespread type-annotation refactors that could affect runtime edge cases if any typing changes leaked into behavior. > > **Overview** > Drops Python 3.9 support across the project: updates `pyproject.toml` (`requires-python` >=3.10), removes 3.9 from classifiers/Black targets, and adjusts optional dependency markers now that the whole package requires 3.10+. > > Aligns CI and docs with the new minimum by removing 3.9 from the test matrix, adding 3.14 to lint, and updating installation/MCP documentation and a notebook to state Python 3.10+. > > Modernizes repo-owned Python sources to Python 3.10 typing syntax (built-in generics and `|` unions) across caches, message history, router, and the core `SearchIndex`/storage layer, and removes the redundant MCP CLI runtime version guard. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 57bb351. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 7cafaa0 commit 2284616

80 files changed

Lines changed: 1290 additions & 2763 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/lint.yml

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,12 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
# Only lint on the min and max supported Python versions.
19-
# It's extremely unlikely that there's a lint issue on any version in between
20-
# that doesn't show up on the min or max versions.
21-
#
22-
# GitHub rate-limits how many jobs can be running at any one time.
23-
# Starting new jobs is also relatively slow,
24-
# so linting on fewer versions makes CI faster.
2518
python-version:
26-
- "3.9"
2719
- "3.10"
2820
- "3.11"
2921
- "3.12"
3022
- "3.13"
23+
- "3.14"
3124

3225
steps:
3326
- name: Check out repository
@@ -62,4 +55,4 @@ jobs:
6255
6356
- name: check-mypy
6457
run: |
65-
make check-types
58+
make check-types

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
strategy:
6363
fail-fast: false
6464
matrix:
65-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
65+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
6666
redis-py-version: ["5.x", "6.x", "7.x"]
6767
redis-image: ["redis:8.2", "redis:8.4", "redis:latest"]
6868
steps:

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Perfect for building **RAG pipelines** with real-time retrieval, **AI agents** w
4545

4646
## Installation
4747

48-
Install `redisvl` into your Python (>=3.9) environment using `pip`:
48+
Install `redisvl` into your Python (>=3.10) environment using `pip`:
4949

5050
```bash
5151
pip install redisvl
@@ -57,8 +57,6 @@ Install the MCP server extra when you want to expose an existing Redis index thr
5757
pip install redisvl[mcp]
5858
```
5959

60-
The `redisvl[mcp]` extra requires Python 3.10 or newer.
61-
6260
> For more detailed instructions, visit the [installation guide](https://docs.redisvl.com/en/latest/user_guide/installation.html).
6361
> For MCP concepts and setup, see the [RedisVL MCP docs](https://docs.redisvl.com/en/latest/concepts/mcp.html) and the [MCP how-to guide](https://docs.redisvl.com/en/latest/user_guide/how_to_guides/mcp.html).
6462

docs/_extension/gallery_directive.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
abstracted into a standalone package if it proves useful.
1010
"""
1111
from pathlib import Path
12-
from typing import Any, Dict, List
12+
from typing import Any
1313

1414
from docutils import nodes
1515
from docutils.parsers.rst import directives
@@ -54,7 +54,7 @@ class GalleryDirective(SphinxDirective):
5454
"class-card": directives.unchanged,
5555
}
5656

57-
def run(self) -> List[nodes.Node]:
57+
def run(self) -> list[nodes.Node]:
5858
if self.arguments:
5959
# If an argument is given, assume it's a path to a YAML file
6060
# Parse it and load it into the directive content
@@ -145,7 +145,7 @@ def run(self) -> List[nodes.Node]:
145145
return [container]
146146

147147

148-
def setup(app: Sphinx) -> Dict[str, Any]:
148+
def setup(app: Sphinx) -> dict[str, Any]:
149149
"""Add custom configuration to sphinx app.
150150
151151
Args:
@@ -158,4 +158,4 @@ def setup(app: Sphinx) -> Dict[str, Any]:
158158
return {
159159
"parallel_read_safe": True,
160160
"parallel_write_safe": True,
161-
}
161+
}

docs/user_guide/13_langcache_semantic_cache.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"\n",
1515
"Before you begin, ensure you have:\n",
1616
"- Installed RedisVL with the LangCache extra: `pip install redisvl[langcache]`\n",
17-
"- Python 3.9+ (same as RedisVL)\n",
17+
"- Python 3.10+ (same as RedisVL)\n",
1818
"- A LangCache service with a **cache ID** and **API key**. You can set up a LangCache service in Redis Cloud [here](https://cloud.redis.io/#/)\n",
1919
"- Optionally: **attributes** configured on your LangCache cache if you plan to pass `metadata` / `attributes` from RedisVL\n",
2020
"\n",

docs/user_guide/how_to_guides/mcp.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -436,10 +436,6 @@ pip install redisvl[mcp]
436436

437437
If the configured vectorizer needs a provider SDK, install that provider extra too.
438438

439-
### Unsupported Python Runtime
440-
441-
RedisVL MCP requires Python 3.10 or newer even though the core package supports Python 3.9. Use a newer interpreter for the MCP server process.
442-
443439
### Configured Redis Index Does Not Exist
444440

445441
The server only binds to an existing index. Create the index first, then point `indexes.<id>.redis_name` at that index name.

docs/user_guide/installation.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ There are a few ways to install RedisVL. The easiest way is to use pip.
1111

1212
## Install RedisVL with Pip
1313

14-
Install `redisvl` into your Python (>=3.9) environment using `pip`:
14+
Install `redisvl` into your Python (>=3.10) environment using `pip`:
1515

1616
```bash
1717
$ pip install -U redisvl
@@ -54,10 +54,6 @@ To install **all** optional dependencies at once:
5454
$ pip install redisvl[all]
5555
```
5656

57-
```{note}
58-
The core RedisVL package supports Python 3.9+, but the `redisvl[mcp]` extra requires Python 3.10 or newer because the MCP server depends on `fastmcp`.
59-
```
60-
6157
## Install RedisVL from Source
6258

6359
To install RedisVL from source, clone the repository and install the package using `pip`:

pyproject.toml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name = "redisvl"
44
version = "0.17.1"
55
description = "Python client library and CLI for using Redis as a vector database"
66
authors = [{ name = "Redis Inc.", email = "applied.ai@redis.com" }]
7-
requires-python = ">=3.9.2,<3.15"
7+
requires-python = ">=3.10,<3.15"
88
readme = "README.md"
99
license = "MIT"
1010
keywords = [
@@ -15,7 +15,6 @@ keywords = [
1515
"vector-search",
1616
]
1717
classifiers = [
18-
"Programming Language :: Python :: 3.9",
1918
"Programming Language :: Python :: 3.10",
2019
"Programming Language :: Python :: 3.11",
2120
"Programming Language :: Python :: 3.12",
@@ -36,7 +35,7 @@ dependencies = [
3635

3736
[project.optional-dependencies]
3837
mcp = [
39-
"fastmcp>=2.0.0 ; python_version >= '3.10'",
38+
"fastmcp>=2.0.0",
4039
"pydantic-settings>=2.0",
4140
]
4241
mistralai = ["mistralai>=1.0.0"]
@@ -102,7 +101,7 @@ dev = [
102101
"types-pyyaml",
103102
"types-pyopenssl",
104103
"testcontainers>=4.3.1,<5",
105-
"cryptography>=44.0.1 ; python_version > '3.9.1'",
104+
"cryptography>=44.0.1",
106105
"codespell>=2.4.1,<3",
107106
"langcache>=0.9.0",
108107
]
@@ -124,7 +123,7 @@ default-groups = [
124123
]
125124

126125
[tool.black]
127-
target-version = ['py39', 'py310', 'py311', 'py312', 'py313']
126+
target-version = ['py310', 'py311', 'py312', 'py313', 'py314']
128127
exclude = '''
129128
(
130129
| \.egg

redisvl/cli/mcp.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ def __init__(self):
7373
def _run(self, args):
7474
"""Validate the environment, build the server, and serve stdio requests."""
7575
try:
76-
self._ensure_supported_python()
7776
settings_cls, server_cls = self._load_mcp_components()
7877
settings = settings_cls.from_env(
7978
config=args.config,
@@ -94,20 +93,6 @@ def _run(self, args):
9493
self._print_error(str(exc))
9594
raise SystemExit(1)
9695

97-
@staticmethod
98-
def _ensure_supported_python():
99-
"""Fail fast when the current interpreter cannot support MCP extras."""
100-
if sys.version_info < (3, 10):
101-
version = "%s.%s.%s" % (
102-
sys.version_info.major,
103-
sys.version_info.minor,
104-
sys.version_info.micro,
105-
)
106-
raise RuntimeError(
107-
"RedisVL MCP CLI requires Python 3.10 or newer. "
108-
"Current runtime is Python %s." % version
109-
)
110-
11196
@staticmethod
11297
def _load_mcp_components():
11398
"""Import optional MCP dependencies only on the `rvl mcp` code path."""

redisvl/cli/utils.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
from argparse import ArgumentParser, Namespace
3-
from typing import Optional
43
from urllib.parse import quote, urlparse, urlunparse
54

65
from redisvl.redis.constants import REDIS_URL_ENV_VAR
@@ -22,7 +21,7 @@ def _has_explicit_connection_options(args: Namespace) -> bool:
2221
)
2322

2423

25-
def _get_auth_credentials(args: Namespace) -> tuple[Optional[str], Optional[str]]:
24+
def _get_auth_credentials(args: Namespace) -> tuple[str | None, str | None]:
2625
return getattr(args, "user", None) or None, getattr(args, "password", None) or None
2726

2827

0 commit comments

Comments
 (0)