feat: bump Python minimum from 3.12 to 3.13, drop strenum backport (#14767)

Closes #14753

## What changed

| File | Change |
|---|---|
| `pyproject.toml` | `requires-python` → `>=3.13,<3.15`; remove
`strenum==0.4.15` |
| `Dockerfile` | `uv python install 3.13`, `uv sync --python 3.13` |
| `.github/workflows/tests.yml` | `uv sync --python 3.13` on both matrix
legs |
| `CLAUDE.md` | dev setup command + requirements note updated |
| `deepdoc/parser/mineru_parser.py` | `from strenum import StrEnum` →
`from enum import StrEnum` |
| `agent/tools/code_exec.py` | same |

`StrEnum` has been in the stdlib since Python 3.11 — the `strenum`
backport package is no longer needed once the floor is 3.13.

## Why uv.lock is not regenerated

`uv lock --python 3.13` fails because:

1. The infiniflow/graspologic fork pins `numpy>=1.26.4,<2.0.0`
2. `tensorflow-cpu>=2.20.0` (the first release with cp313 wheels)
depends on `ml-dtypes>=0.5.1`, which requires `numpy>=2.1.0`
3. These two constraints are irreconcilable on Python 3.13

The lockfile regeneration requires loosening the `numpy` upper bound in
the `infiniflow/graspologic` fork. Once that fork commit is updated and
the SHA in `pyproject.toml:49` is bumped, `uv lock --python 3.13` will
succeed.

## RFC corrections

Two claims in the original RFC (#14753) did not hold up under code
review:

- **"graspologic hard-blocks 3.13"** — the infiniflow fork at the pinned
commit has no `<3.13` Python constraint. The blocker is the transitive
`numpy<2.0.0` conflict with tensorflow-cpu's test dependency, not a
direct Python version cap.
- **"free-threading throughput gains for I/O-bound workload"** — Python
3.13 free-threading requires a special `--disable-gil` build and
provides no benefit for async I/O code (the GIL is already released
during I/O). The real motivation is forward compatibility and improved
error messages.
This commit is contained in:
wdeveloper16
2026-05-15 08:40:53 +02:00
committed by GitHub
parent cb606e1c38
commit 14c0985182
13 changed files with 139 additions and 1173 deletions

View File

@@ -158,7 +158,7 @@ jobs:
- name: Run unit test
run: |
uv sync --python 3.12 --group test --frozen
uv sync --python 3.13 --group test --frozen
source .venv/bin/activate
which pytest || echo "pytest not in PATH"
echo "Start to run unit test"
@@ -222,7 +222,7 @@ jobs:
# Patch entrypoint.sh for coverage
sed -i '/"\$PY" api\/ragflow_server.py \${INIT_SUPERUSER_ARGS} &/c\ echo "Ensuring coverage is installed..."\n "$PY" -m pip install coverage -i https://mirrors.aliyun.com/pypi/simple\n export COVERAGE_FILE=/ragflow/logs/.coverage\n echo "Starting ragflow_server with coverage..."\n "$PY" -m coverage run --source=./api/apps --omit="*/tests/*,*/migrations/*" -a api/ragflow_server.py ${INIT_SUPERUSER_ARGS} &' ./entrypoint.sh
cd ..
uv sync --python 3.12 --group test --frozen && uv pip install -e sdk/python
uv sync --python 3.13 --group test --frozen && uv pip install -e sdk/python
- name: Start ragflow:nightly for Infinity

View File

@@ -51,7 +51,7 @@ RAGFlow is an open-source RAG (Retrieval-Augmented Generation) engine based on d
```bash
# Install Python dependencies
uv sync --python 3.12 --all-extras
uv sync --python 3.13 --all-extras
uv run python3 download_deps.py
pre-commit install
@@ -118,7 +118,7 @@ RAGFlow supports switching between Elasticsearch (default) and Infinity:
## Development Environment Requirements
- Python 3.10-3.12
- Python 3.10-3.13
- Node.js >=18.20.4
- Docker & Docker Compose
- uv package manager

View File

@@ -78,7 +78,7 @@ RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/,target=/deps
tar xzf "/deps/uv-${uv_arch}-unknown-linux-gnu.tar.gz" \
&& cp "uv-${uv_arch}-unknown-linux-gnu/"* /usr/local/bin/ \
&& rm -rf "uv-${uv_arch}-unknown-linux-gnu" \
&& uv python install 3.12
&& uv python install 3.13
ENV PYTHONDONTWRITEBYTECODE=1 DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 \
UV_HTTP_TIMEOUT=200 \
@@ -147,7 +147,7 @@ RUN --mount=type=cache,id=ragflow_uv,target=/root/.cache/uv,sharing=locked \
else \
sed -i 's|mirrors.aliyun.com/pypi|pypi.org|g' uv.lock; \
fi; \
uv sync --python 3.12 --frozen && \
uv sync --python 3.13 --frozen && \
# Ensure pip is available in the venv for runtime package installation (fixes #12651)
.venv/bin/python3 -m ensurepip --upgrade

View File

@@ -24,7 +24,7 @@ from collections.abc import Mapping
from typing import Optional
from pydantic import BaseModel, Field, field_validator
from strenum import StrEnum
from enum import StrEnum
from agent.tools.base import ToolBase, ToolMeta, ToolParamBase
from api.db.services.file_service import FileService

View File

@@ -15,7 +15,7 @@
#
from enum import IntEnum
from strenum import StrEnum
from enum import StrEnum
class UserTenantRole(StrEnum):

View File

@@ -16,7 +16,7 @@
import os
from enum import Enum, IntEnum
from strenum import StrEnum
from enum import StrEnum
SERVICE_CONF = "service_conf.yaml"
RAG_FLOW_SERVICE_NAME = "ragflow"

View File

@@ -32,7 +32,7 @@ import numpy as np
import pdfplumber
import requests
from PIL import Image
from strenum import StrEnum
from enum import StrEnum
from deepdoc.parser.pdf_parser import RAGFlowPdfParser
from deepdoc.parser.utils import extract_pdf_outlines

View File

@@ -32,7 +32,7 @@ from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.responses import JSONResponse, Response
from starlette.routing import Mount, Route
from strenum import StrEnum
from enum import StrEnum
class LaunchMode(StrEnum):

View File

@@ -5,8 +5,13 @@ description = "[RAGFlow](https://ragflow.io/) is an open-source RAG (Retrieval-A
authors = [{ name = "Zhichang Yu", email = "yuzhichang@gmail.com" }]
license-files = ["LICENSE"]
readme = "README.md"
requires-python = ">=3.12,<3.15"
requires-python = ">=3.13,<3.15"
dependencies = [
# discord-py==2.3.2 unconditionally imports audioop in discord/player.py at module-
# load time. audioop was removed from the CPython stdlib in Python 3.13 (PEP 594),
# so any import of the discord package raises ImportError on Python 3.13 — even in
# tests that never use voice features. audioop-lts provides the module as a backport.
"audioop-lts>=0.2.1",
"aiosmtplib>=5.0.0",
"akshare>=1.15.78,<2.0.0",
"anthropic==0.34.1",
@@ -74,7 +79,7 @@ dependencies = [
"opencv-python-headless==4.10.0.84",
"opendal>=0.45.0,<0.46.0",
"opensearch-py==2.7.1",
"ormsgpack==1.5.0",
"ormsgpack>=1.5.0",
"pdfplumber==0.10.4",
"pluginlib>=0.10.0",
"psycopg2-binary>=2.9.11,<3.0.0",
@@ -108,7 +113,6 @@ dependencies = [
"agentrun-sdk>=0.0.16,<1.0.0",
"nest-asyncio>=1.6.0,<2.0.0", # Needed for agent/component/message.py
"sqlglotrs==0.9.0",
"strenum==0.4.15",
"tavily-python==0.5.1",
"tencentcloud-sdk-python==3.0.1478",
"tika==2.6.0",
@@ -179,7 +183,6 @@ test = [
"pycryptodomex==3.20.0",
"pytest-playwright>=0.7.2",
"codecov>=2.1.13",
"tensorflow-cpu>=2.17.0",
]
[tool.uv]
@@ -187,11 +190,31 @@ constraint-dependencies = [
# CVE-2026-30922: Denial of Service via unbounded recursion in ASN.1 decoding (CVSS 7.5 HIGH)
# pyasn1 < 0.6.3 is vulnerable; pulled in transitively via google-auth / rsa / pyasn1-modules
"pyasn1>=0.6.3",
# Python 3.13 added pathlib.PurePath.parser as a public class attribute holding
# the posixpath/ntpath module. trio<0.26 introspects all Path class attributes to
# generate async forwards and raises TypeError on any non-callable attribute it
# encounters (fixed in trio 0.26 by skipping non-callables). Pulled in transitively
# via selenium-wire -> trio-websocket -> trio.
"trio>=0.26.0",
]
override-dependencies = [
# moodlepy<=0.24.1 pins attrs<23.0.0, but trio>=0.26.0 requires attrs>=23.2.0.
# attrs 23.x is backward-compatible; moodlepy works fine at runtime with it.
"attrs>=23.2.0",
]
# trio 0.26+ (Python 3.13 compatible) is not yet on the Aliyun mirror.
# Mark PyPI as explicit so it is used only for packages listed in [tool.uv.sources].
[[tool.uv.index]]
name = "pypi"
url = "https://pypi.org/simple"
explicit = true
[[tool.uv.index]]
url = "https://mirrors.aliyun.com/pypi/simple"
[tool.uv.sources]
trio = [{ index = "pypi" }]
[tool.setuptools]
packages = [
'agent',

View File

@@ -19,7 +19,7 @@
import importlib
import inspect
from strenum import StrEnum
from enum import StrEnum
class SupportedLiteLLMProvider(StrEnum):

View File

@@ -28,7 +28,7 @@ import json_repair
import litellm
import openai
from openai import AsyncOpenAI, OpenAI
from strenum import StrEnum
from enum import StrEnum
from common.misc_utils import thread_pool_exec
from common.token_utils import num_tokens_from_string, total_token_count_from_response

View File

@@ -123,7 +123,7 @@ def _load_session_module(monkeypatch):
# Mock common.constants module
from enum import Enum
from strenum import StrEnum
from enum import StrEnum
class _StubLLMType(StrEnum):
CHAT = "chat"

1253
uv.lock generated

File diff suppressed because it is too large Load Diff