fix: support Python 3.15 (#48359)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yujonglee 2026-10-26 09:00:11 -07:00 • committed by GitHub
parent dddbaa803d
commit ebf829c2fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1292 additions and 434 deletions

View file

@ -45,7 +45,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
@ -132,7 +132,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:

View file

@ -33,7 +33,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
env:
LITELLM_LOCAL_MODEL_COST_MAP: "True"
steps:
@ -76,6 +76,12 @@ jobs:
- name: Import and CLI checks
run: $IN_NETNS .venv/bin/python .github/scripts/run_merge_smoke.py cli
- name: Routing and UUID compatibility
run: >-
$IN_NETNS .venv/bin/python -m pytest
tests/unit/router_strategy/test_complexity_router.py::TestCustomDimensions
tests/unit/test_uuid_helper.py -q
- name: Proxy startup check
run: $IN_NETNS .venv/bin/python .github/scripts/run_merge_smoke.py proxy-startup --diagnostics-dir "${RUNNER_TEMP}/smoke-diagnostics"

View file

@ -1,10 +1,14 @@
"""
Internal unified UUID helper.
Always uses fastuuid for performance.
Uses fastuuid where supported and the standard library on Python 3.15+.
"""
import fastuuid as _uuid
import sys
import uuid as _uuid
if sys.version_info < (3, 15):
import fastuuid as _uuid
# Expose a module-like alias so callers can use: uuid.uuid4()
uuid = _uuid

View file

@ -7,12 +7,12 @@ All values are configurable via proxy config.yaml.
import math
import re
import warnings
import sys
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from enum import Enum
from types import MappingProxyType
from typing import Annotated, Final, Literal, NamedTuple
from typing import TYPE_CHECKING, Annotated, Final, Literal, NamedTuple
from pydantic import (
ConfigDict,
@ -27,8 +27,13 @@ from pydantic import (
from litellm.types.llms.base import LiteLLMBaseModel
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
if TYPE_CHECKING:
import sre_constants
import sre_parse
elif sys.version_info >= (3, 11):
from re import _constants as sre_constants
from re import _parser as sre_parse
else:
import sre_constants
import sre_parse

View file

@ -3,14 +3,14 @@ name = "litellm-core"
version = "1.105.0"
description = "Library to easily interface with LLM API providers"
readme = "README.md"
requires-python = ">=3.10, <3.15"
requires-python = ">=3.10, <3.16"
license = "MIT"
license-files = ["LICENSE"]
authors = [
{ name = "BerriAI" },
]
dependencies = [
"fastuuid>=0.14.0,<1.0",
"fastuuid>=0.14.0,<1.0; python_version < '3.15'",
"filelock>=3.16.1,<4.0",
"httpx[http2]>=0.28.0,<1.0",
"openai>=2.20.0,<4.0.0",
@ -20,14 +20,16 @@ dependencies = [
"packaging>=24.0",
"importlib-metadata>=8.0.0,<9.0",
"tiktoken>=0.8.0,<1.0; python_version < '3.14'",
"tiktoken>=0.12.0,<1.0; python_version >= '3.14'",
"tiktoken>=0.12.0,<1.0; python_version == '3.14'",
"tiktoken>=0.14.0,<1.0; python_version >= '3.15'",
"click>=8.0.0,<9.0",
"jinja2>=3.1.6,<4.0",
"aiohttp>=3.14.2,<4.0",
"anyio>=4.14.0,<5.0",
"async-timeout>=4.0.3,<6.0; python_version < '3.11'",
"pydantic>=2.11.0,<3.0.0; python_version < '3.14'",
"pydantic>=2.12.0,<3.0.0; python_version >= '3.14'",
"pydantic>=2.12.0,<3.0.0; python_version == '3.14'",
"pydantic>=2.14.0,<3.0.0; python_version >= '3.15'",
"pydantic-settings>=2.14.1,<3.0",
"jsonschema>=4.0.0,<5.0",
"certifi>=2026.10.19",

View file

@ -3,7 +3,7 @@ name = "litellm"
version = "1.108.0"
description = "Library to easily interface with LLM API providers"
readme = "README.md"
requires-python = ">=3.10, <3.15"
requires-python = ">=3.10, <3.16"
license = "MIT"
license-files = ["LICENSE"]
authors = [
@ -14,7 +14,7 @@ dependencies = [
# deps. Reproducibility for our Docker/CI comes from `uv.lock`.
# When changing a floor, verify it installs + imports on every supported
# Python with: `uv pip install --resolution=lowest-direct .`
"fastuuid>=0.14.0,<1.0",
"fastuuid>=0.14.0,<1.0; python_version < '3.15'",
"filelock>=3.16.1,<4.0",
"httpx[http2]>=0.28.0,<1.0",
"openai>=2.20.0,<4.0.0",
@ -23,7 +23,8 @@ dependencies = [
"packaging>=24.0",
"importlib-metadata>=8.0.0,<9.0",
"tiktoken>=0.8.0,<1.0; python_version < '3.14'",
"tiktoken>=0.12.0,<1.0; python_version >= '3.14'",
"tiktoken>=0.12.0,<1.0; python_version == '3.14'",
"tiktoken>=0.14.0,<1.0; python_version >= '3.15'",
"tokenizers>=0.21.0,<1.0",
"huggingface-hub>=0.34.0,<2.0",
"click>=8.0.0,<9.0",
@ -32,7 +33,8 @@ dependencies = [
"anyio>=4.14.0,<5.0",
"async-timeout>=4.0.3,<6.0; python_version < '3.11'",
"pydantic>=2.11.0,<3.0.0; python_version < '3.14'",
"pydantic>=2.12.0,<3.0.0; python_version >= '3.14'",
"pydantic>=2.12.0,<3.0.0; python_version == '3.14'",
"pydantic>=2.14.0,<3.0.0; python_version >= '3.15'",
"pydantic-settings>=2.14.1,<3.0",
"jsonschema>=4.0.0,<5.0",
"boto3>=1.43.117,<2.0",
@ -55,17 +57,20 @@ Documentation = "https://docs.litellm.ai"
proxy = [
"gunicorn>=23.0.0,<24.0",
"uvicorn>=0.33.0,<1.0",
"granian>=2.7.4,<3.0",
"granian>=2.7.4,<3.0; python_version < '3.15'",
"granian>=2.8.0,<3.0; python_version >= '3.15'",
"uvloop>=0.22.1,<1.0; sys_platform != 'win32'",
"fastapi>=0.136.3,<1.0",
"starlette>=1.0.1,<2.0",
"backoff>=2.2.1,<3.0",
"pyyaml>=6.0.3,<7.0",
"rq>=2.7.0,<3.0",
"orjson>=3.11.6,<4.0",
"orjson>=3.11.6,<4.0; python_version < '3.15'",
"orjson>=3.12.0,<4.0; python_version >= '3.15'",
# redis-py's C response parser. It arrives with redis (via rq) either way; naming
# it here is what makes redis-py select _HiredisParser instead of the Python one.
"hiredis>=3.0.0,<4.0",
"hiredis>=3.0.0,<4.0; python_version < '3.15'",
"hiredis>=3.5.0,<4.0; python_version >= '3.15'",
"apscheduler>=3.11.2,<4.0",
"fastapi-sso>=0.19.0,<1.0",
"PyJWT>=2.13.0,<3.0",
@ -210,8 +215,8 @@ dev = [
"tomli==2.4.1; python_version < '3.11'",
"pytest-mock==3.15.1",
"pytest-asyncio==1.3.0",
"psycopg==3.3.3",
"psycopg-binary==3.3.3",
"psycopg==3.3.6",
"psycopg-binary==3.3.6",
"pytest-xdist==3.8.0",
"requests-mock==1.12.1",
"responses==0.26.0",

View file

@ -1065,6 +1065,11 @@ class TestCustomDimensions:
"create table widgets (id integer); ALTER TABLE widgets ADD label text;",
id="regex",
),
pytest.param(
{"patterns": [r"\b(?P<verb>create|alter)\s{1,4}?table\b"]},
"CREATE TABLE widgets (id integer)",
id="named-group-alternation-lazy-repeat",
),
],
)
def test_custom_dimension_changes_only_matching_requests(

View file

@ -1,14 +1,20 @@
import importlib
from typing import Final
from uuid import NAMESPACE_DNS, UUID, uuid5 as stdlib_uuid5
from litellm._uuid import uuid, uuid4
def test_uses_fastuuid_and_uuid4_works():
mod = importlib.import_module("litellm._uuid")
fastuuid_mod = importlib.import_module("fastuuid")
assert hasattr(mod, "uuid4")
assert hasattr(mod, "uuid")
assert mod.uuid is fastuuid_mod
def test_uuid4_returns_canonical_random_uuid() -> None:
for value in (uuid4(), uuid.uuid4()):
encoded: Final = str(value)
parsed: Final = UUID(encoded)
assert parsed.version == 4
assert encoded == str(parsed)
assert value.bytes == parsed.bytes
# Ensure uuid4 returns something that looks like a UUID string
val = str(mod.uuid4())
assert isinstance(val, str)
assert len(val) >= 8
def test_uuid5_accepts_byte_names_for_stable_ids() -> None:
namespace: Final = uuid.UUID(str(NAMESPACE_DNS))
name: Final = "guardrail-\u00e9"
generated: Final = uuid.uuid5(namespace, name.encode("utf-8"))
assert str(generated) == str(stdlib_uuid5(NAMESPACE_DNS, name))

1627
uv.lock generated

File diff suppressed because it is too large Load diff