Skip to content

Commit f7df963

Browse files
Add Python 3.14 support (#468)
## Summary Add Python 3.14 support to RedisVL. ## Changes - Update `requires-python` upper bound from `<3.14` to `<3.15` - Add `Programming Language :: Python :: 3.14` classifier - Add Python 3.14 to the CI test matrix ## Testing Strategy **HuggingFace/sentence-transformers tests are skipped on Python 3.14** because the upstream `transformers` library doesn't fully support Python 3.14 yet. Affected components (all optional): - `HFTextVectorizer` - `HFCrossEncoderReranker` - `SemanticRouter` (when using default HF vectorizer) - `SemanticMessageHistory` (when using HF vectorizer) Users can still use these features on Python 3.9-3.13, or use alternative vectorizers (OpenAI, Cohere, etc.) on Python 3.14. **Implementation:** - Added `SKIP_HF` constant in `conftest.py` to detect Python 3.14+ - Skip HF-dependent test files entirely via `pytestmark` - Skip individual HF tests via `@pytest.mark.requires_hf` marker - Skip HF parameters in parametrized tests via `pytest.param(..., marks=...)` Once `transformers` adds Python 3.14 support, these skips can be removed. ## Additional Fix Added missing `@pytest.mark.requires_api_keys` marker to `test_default_dtype` (pre-existing bug unrelated to this PR). --------- Co-authored-by: Vishal Bala <vishalbala.1994@gmail.com>
1 parent fc18904 commit f7df963

File tree

10 files changed

+915
-115
lines changed

10 files changed

+915
-115
lines changed

.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"]
65+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
6666
redis-py-version: ["5.x", "6.x", "7.x"]
6767
redis-image: ["redis/redis-stack-server:latest", "redis:latest"]
6868
steps:

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "redisvl"
33
version = "0.14.0"
44
description = "Python client library and CLI for using Redis as a vector database"
55
authors = [{ name = "Redis Inc.", email = "applied.ai@redis.com" }]
6-
requires-python = ">=3.9.2,<3.14"
6+
requires-python = ">=3.9.2,<3.15"
77
readme = "README.md"
88
license = "MIT"
99
keywords = [
@@ -19,6 +19,7 @@ classifiers = [
1919
"Programming Language :: Python :: 3.11",
2020
"Programming Language :: Python :: 3.12",
2121
"Programming Language :: Python :: 3.13",
22+
"Programming Language :: Python :: 3.14",
2223
"License :: OSI Approved :: MIT License",
2324
]
2425
dependencies = [

tests/conftest.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import os
33
import subprocess
4+
import sys
45
from datetime import datetime, timezone
56

67
import pytest
@@ -9,7 +10,12 @@
910
from redisvl.index.index import AsyncSearchIndex, SearchIndex
1011
from redisvl.redis.connection import RedisConnectionFactory, is_version_gte
1112
from redisvl.redis.utils import array_to_buffer
12-
from redisvl.utils.vectorize import HFTextVectorizer
13+
14+
# Check if we're on Python 3.14+ where sentence-transformers may not work
15+
SKIP_HF = sys.version_info >= (3, 14)
16+
17+
if not SKIP_HF:
18+
from redisvl.utils.vectorize import HFTextVectorizer
1319

1420
logger = logging.getLogger(__name__)
1521

@@ -206,6 +212,8 @@ def cluster_client(redis_cluster_url):
206212

207213
@pytest.fixture(scope="session")
208214
def hf_vectorizer():
215+
if SKIP_HF:
216+
pytest.skip("HFTextVectorizer not supported on Python 3.14+")
209217
return HFTextVectorizer(
210218
model="sentence-transformers/all-mpnet-base-v2",
211219
token=os.getenv("HF_TOKEN"),
@@ -215,11 +223,15 @@ def hf_vectorizer():
215223

216224
@pytest.fixture(scope="session")
217225
def hf_vectorizer_float16():
226+
if SKIP_HF:
227+
pytest.skip("HFTextVectorizer not supported on Python 3.14+")
218228
return HFTextVectorizer(dtype="float16")
219229

220230

221231
@pytest.fixture(scope="session")
222232
def hf_vectorizer_with_model():
233+
if SKIP_HF:
234+
pytest.skip("HFTextVectorizer not supported on Python 3.14+")
223235
return HFTextVectorizer("sentence-transformers/all-mpnet-base-v2")
224236

225237

@@ -420,6 +432,10 @@ def pytest_configure(config: pytest.Config) -> None:
420432
config.addinivalue_line(
421433
"markers", "requires_cluster: mark test as requiring a Redis cluster"
422434
)
435+
config.addinivalue_line(
436+
"markers",
437+
"requires_hf: mark test as requiring HuggingFace/sentence-transformers",
438+
)
423439

424440

425441
def pytest_collection_modifyitems(
@@ -436,13 +452,18 @@ def pytest_collection_modifyitems(
436452
skip_cluster = pytest.mark.skip(
437453
reason="Skipping test because Redis cluster is not available. Use --run-cluster-tests to run these tests."
438454
)
455+
skip_hf = pytest.mark.skip(
456+
reason="Skipping test because sentence-transformers is not supported on Python 3.14+"
457+
)
439458

440459
# Apply skip markers independently based on flags
441460
for item in items:
442461
if item.get_closest_marker("requires_api_keys") and not run_api_tests:
443462
item.add_marker(skip_api)
444463
if item.get_closest_marker("requires_cluster") and not run_cluster_tests:
445464
item.add_marker(skip_cluster)
465+
if item.get_closest_marker("requires_hf") and SKIP_HF:
466+
item.add_marker(skip_hf)
446467

447468

448469
@pytest.fixture

tests/integration/test_cross_encoder_reranker.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import pytest
22

3-
from redisvl.utils.rerank.hf_cross_encoder import HFCrossEncoderReranker
3+
from tests.conftest import SKIP_HF
4+
5+
if not SKIP_HF:
6+
from redisvl.utils.rerank.hf_cross_encoder import HFCrossEncoderReranker
7+
8+
pytestmark = pytest.mark.skipif(
9+
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
10+
)
411

512

613
@pytest.fixture(scope="session")

tests/integration/test_llmcache.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import sys
23
import warnings
34
from collections import namedtuple
45
from time import sleep, time
@@ -10,8 +11,14 @@
1011
from redisvl.extensions.cache.llm import SemanticCache
1112
from redisvl.index.index import AsyncSearchIndex, SearchIndex
1213
from redisvl.query.filter import Num, Tag, Text
13-
from redisvl.utils.vectorize import HFTextVectorizer
14-
from tests.conftest import skip_if_no_redisearch, skip_if_no_redisearch_async
14+
from tests.conftest import SKIP_HF, skip_if_no_redisearch, skip_if_no_redisearch_async
15+
16+
if not SKIP_HF:
17+
from redisvl.utils.vectorize import HFTextVectorizer
18+
19+
pytestmark = pytest.mark.skipif(
20+
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
21+
)
1522

1623

1724
@pytest.fixture(scope="session")

tests/integration/test_message_history.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55

66
from redisvl.extensions.constants import ID_FIELD_NAME
77
from redisvl.extensions.message_history import MessageHistory, SemanticMessageHistory
8-
from tests.conftest import skip_if_no_redisearch
8+
from tests.conftest import SKIP_HF, skip_if_no_redisearch
9+
10+
requires_hf = pytest.mark.skipif(
11+
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
12+
)
913

1014

1115
@pytest.fixture
@@ -327,6 +331,7 @@ def test_standard_clear(standard_history):
327331

328332

329333
# test semantic message history
334+
@requires_hf
330335
def test_semantic_specify_client(client, hf_vectorizer):
331336
skip_if_no_redisearch(client)
332337
history = SemanticMessageHistory(
@@ -339,6 +344,7 @@ def test_semantic_specify_client(client, hf_vectorizer):
339344
assert isinstance(history._index.client, type(client))
340345

341346

347+
@requires_hf
342348
def test_semantic_bad_connection_info(hf_vectorizer):
343349
with pytest.raises(ConnectionError):
344350
SemanticMessageHistory(
@@ -349,6 +355,7 @@ def test_semantic_bad_connection_info(hf_vectorizer):
349355
)
350356

351357

358+
@requires_hf
352359
def test_semantic_scope(semantic_history):
353360
# store entries under default session tag
354361
semantic_history.store("some prompt", "some response")
@@ -376,6 +383,7 @@ def test_semantic_scope(semantic_history):
376383
assert no_context == []
377384

378385

386+
@requires_hf
379387
def test_semantic_store_and_get_recent(semantic_history):
380388
context = semantic_history.get_recent()
381389
assert len(context) == 0
@@ -461,6 +469,7 @@ def test_semantic_store_and_get_recent(semantic_history):
461469
bad_context = semantic_history.get_recent(top_k="3")
462470

463471

472+
@requires_hf
464473
def test_semantic_messages_property(semantic_history):
465474
semantic_history.add_messages(
466475
[
@@ -505,6 +514,7 @@ def test_semantic_messages_property(semantic_history):
505514
]
506515

507516

517+
@requires_hf
508518
def test_semantic_add_and_get_relevant(semantic_history):
509519
semantic_history.add_message(
510520
{"role": "system", "content": "discussing common fruits and vegetables"}
@@ -580,6 +590,7 @@ def test_semantic_add_and_get_relevant(semantic_history):
580590
bad_context = semantic_history.get_relevant("test prompt", top_k="3")
581591

582592

593+
@requires_hf
583594
def test_semantic_get_raw(semantic_history):
584595
semantic_history.store("first prompt", "first response")
585596
semantic_history.store("second prompt", "second response")
@@ -591,6 +602,7 @@ def test_semantic_get_raw(semantic_history):
591602
assert raw[1]["content"] == "first response"
592603

593604

605+
@requires_hf
594606
def test_semantic_drop(semantic_history):
595607
semantic_history.store("first prompt", "first response")
596608
semantic_history.store("second prompt", "second response")
@@ -679,6 +691,7 @@ def create_same_type():
679691
)
680692

681693

694+
@requires_hf
682695
def test_vectorizer_dtype_mismatch(client, redis_url, hf_vectorizer_float16):
683696
skip_if_no_redisearch(client)
684697
with pytest.raises(ValueError):

tests/integration/test_rerankers.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import pytest
44

5-
from redisvl.utils.rerank import (
6-
CohereReranker,
7-
HFCrossEncoderReranker,
8-
VoyageAIReranker,
5+
from redisvl.utils.rerank import CohereReranker, VoyageAIReranker
6+
from tests.conftest import SKIP_HF
7+
8+
if not SKIP_HF:
9+
from redisvl.utils.rerank import HFCrossEncoderReranker
10+
11+
requires_hf = pytest.mark.skipif(
12+
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
913
)
1014

1115

@@ -25,11 +29,15 @@ def reranker(request):
2529

2630
@pytest.fixture
2731
def hfCrossEncoderReranker():
32+
if SKIP_HF:
33+
pytest.skip("HFCrossEncoderReranker not supported on Python 3.14+")
2834
return HFCrossEncoderReranker()
2935

3036

3137
@pytest.fixture
3238
def hfCrossEncoderRerankerWithCustomModel():
39+
if SKIP_HF:
40+
pytest.skip("HFCrossEncoderReranker not supported on Python 3.14+")
3341
return HFCrossEncoderReranker("cross-encoder/stsb-distilroberta-base")
3442

3543

@@ -76,6 +84,7 @@ def test_bad_input(reranker):
7684
) # Invalid rank_by field
7785

7886

87+
@requires_hf
7988
def test_rank_documents_cross_encoder(hfCrossEncoderReranker):
8089
query = "I love you"
8190
texts = ["I love you", "I like you", "I don't like you", "I hate you"]
@@ -85,6 +94,7 @@ def test_rank_documents_cross_encoder(hfCrossEncoderReranker):
8594
assert scores[i] > scores[i + 1]
8695

8796

97+
@requires_hf
8898
def test_rank_documents_cross_encoder_custom_model(
8999
hfCrossEncoderRerankerWithCustomModel,
90100
):
@@ -96,6 +106,7 @@ def test_rank_documents_cross_encoder_custom_model(
96106
assert scores[i] > scores[i + 1]
97107

98108

109+
@requires_hf
99110
@pytest.mark.asyncio
100111
async def test_async_rank_cross_encoder(hfCrossEncoderReranker):
101112
docs = ["document one", "document two", "document three"]

tests/integration/test_semantic_router.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
RoutingConfig,
1313
)
1414
from redisvl.redis.connection import is_version_gte
15-
from tests.conftest import skip_if_no_redisearch, skip_if_redis_version_below
15+
from tests.conftest import SKIP_HF, skip_if_no_redisearch, skip_if_redis_version_below
16+
17+
pytestmark = pytest.mark.skipif(
18+
SKIP_HF, reason="sentence-transformers not supported on Python 3.14+"
19+
)
1620

1721

1822
def get_base_path():

0 commit comments

Comments
 (0)