mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
fix(database): validate pool settings and command_timeout against booleans and non-finite values (#6009)
This commit is contained in:
parent
857eac45cd
commit
b87ebd9402
@ -327,6 +327,15 @@ This release closes that milestone with **181 merged pull requests**.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **database:** `DatabaseConfig` now validates `pool_size`, `pool_recycle`, and
|
||||
`command_timeout` strictly. Previously, YAML booleans (`true`/`false`) were
|
||||
coerced to `1`/`0` respectively, allowing `pool_size: true` (pool size 1) and
|
||||
`command_timeout: true` (a 1-second statement timeout) to silently pass
|
||||
configuration loading. `pool_size` also accepted non-positive values (`0`,
|
||||
`-1`), and `command_timeout` accepted `inf` (which never times out).
|
||||
`pool_size` and `pool_recycle` now enforce positive integers, and
|
||||
`command_timeout` rejects booleans and non-finite floats while retaining
|
||||
`null` to disable timeouts.
|
||||
- **frontend:** The optimistic human bubble keeps its quote and conversation
|
||||
reference chips once a file upload finishes. The upload-complete update
|
||||
replaced the bubble's `additional_kwargs` with only the uploaded files, so
|
||||
|
||||
@ -332,6 +332,12 @@
|
||||
|
||||
### 修复
|
||||
|
||||
- **数据库:** `DatabaseConfig` 现在严格校验 `pool_size`、`pool_recycle` 与
|
||||
`command_timeout`。此前,YAML 布尔值(`true`/`false`)会被强制转换为 `1`/`0`,
|
||||
导致 `pool_size: true`(变成仅 1 个连接)和 `command_timeout: true`(变成 1 秒超时)
|
||||
静默通过配置加载;`pool_size` 还接受非正数(`0`、`-1`),`command_timeout` 接受
|
||||
`inf`(导致超时机制永不触发)。现在 `pool_size` 和 `pool_recycle` 强制要求正整数,
|
||||
`command_timeout` 拒绝布尔值与非有限浮点数,同时保留 `null` 显式禁用超时。
|
||||
- **前端:** 文件上传完成后,乐观显示的用户消息气泡不再丢失引用与对话
|
||||
引用标签。上传完成时的更新会用仅含已上传文件的对象替换气泡的
|
||||
`additional_kwargs`,因此在服务端回传该消息之前这些标签会消失;随本次
|
||||
|
||||
@ -184,6 +184,7 @@ class DatabaseConfig(BaseModel):
|
||||
)
|
||||
pool_size: int = Field(
|
||||
default=5,
|
||||
gt=0,
|
||||
description="Connection pool size for the app ORM engine (postgres only).",
|
||||
)
|
||||
pool_recycle: int = Field(
|
||||
@ -194,6 +195,7 @@ class DatabaseConfig(BaseModel):
|
||||
command_timeout: float | None = Field(
|
||||
default=30,
|
||||
gt=0,
|
||||
allow_inf_nan=False,
|
||||
description="Timeout in seconds for app ORM PostgreSQL commands. Set to null to disable the command timeout.",
|
||||
)
|
||||
postgres_schema: str = Field(
|
||||
@ -213,6 +215,20 @@ class DatabaseConfig(BaseModel):
|
||||
def _validate_postgres_schema(cls, value: str) -> str:
|
||||
return validate_postgres_schema(value)
|
||||
|
||||
@field_validator("pool_size", "pool_recycle", mode="before")
|
||||
@classmethod
|
||||
def _reject_boolean_pool_settings(cls, value: object) -> object:
|
||||
if isinstance(value, bool):
|
||||
raise ValueError("must be an integer, not a boolean")
|
||||
return value
|
||||
|
||||
@field_validator("command_timeout", mode="before")
|
||||
@classmethod
|
||||
def _reject_boolean_command_timeout(cls, value: object) -> object:
|
||||
if isinstance(value, bool):
|
||||
raise ValueError("command_timeout must be a number of seconds or null, not a boolean")
|
||||
return value
|
||||
|
||||
# -- Legacy key migration (not user-configured) --
|
||||
|
||||
@model_validator(mode="before")
|
||||
|
||||
@ -9,6 +9,8 @@ from types import ModuleType
|
||||
from unittest.mock import AsyncMock, MagicMock, call, patch
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from pydantic import ValidationError
|
||||
|
||||
from deerflow.config.database_config import DatabaseConfig
|
||||
from deerflow.persistence import engine as engine_mod
|
||||
@ -54,6 +56,62 @@ def test_postgres_engine_kwargs_allow_command_timeout_opt_out() -> None:
|
||||
assert kwargs["connect_args"] == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raw", "expected"),
|
||||
[
|
||||
(None, None),
|
||||
(30, 30.0),
|
||||
(0.5, 0.5),
|
||||
("60", 60.0),
|
||||
],
|
||||
)
|
||||
def test_database_command_timeout_accepts_finite_positive_seconds(raw, expected: float | None) -> None:
|
||||
assert DatabaseConfig(command_timeout=raw).command_timeout == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"raw",
|
||||
[
|
||||
True,
|
||||
False,
|
||||
yaml.safe_load("on"),
|
||||
yaml.safe_load("off"),
|
||||
yaml.safe_load("yes"),
|
||||
yaml.safe_load("no"),
|
||||
0,
|
||||
-1,
|
||||
-0.5,
|
||||
float("inf"),
|
||||
float("-inf"),
|
||||
float("nan"),
|
||||
yaml.safe_load(".inf"),
|
||||
yaml.safe_load(".nan"),
|
||||
"1e999",
|
||||
],
|
||||
)
|
||||
def test_database_command_timeout_rejects_boolean_and_non_finite_values(raw) -> None:
|
||||
with pytest.raises(ValidationError, match="command_timeout"):
|
||||
DatabaseConfig(command_timeout=raw)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field", "invalid_value"),
|
||||
[
|
||||
("pool_size", True),
|
||||
("pool_size", False),
|
||||
("pool_size", 0),
|
||||
("pool_size", -1),
|
||||
("pool_recycle", True),
|
||||
("pool_recycle", False),
|
||||
("pool_recycle", 0),
|
||||
("pool_recycle", -1),
|
||||
],
|
||||
)
|
||||
def test_database_pool_settings_reject_booleans_and_non_positive_integers(field: str, invalid_value) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
DatabaseConfig(**{field: invalid_value})
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configured_command_timeout_ends_stalled_command(monkeypatch) -> None:
|
||||
config = DatabaseConfig(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user