fix the lint error in backend

This commit is contained in:
Willem Jiang 2026-04-26 15:09:25 +08:00
parent 3b71e2d377
commit 829e82a9af
15 changed files with 76 additions and 49 deletions

View File

@ -13,7 +13,6 @@ matching the LangGraph Platform wire format expected by the
from __future__ import annotations
import logging
import re
import time
import uuid
from typing import Any

View File

@ -8,18 +8,16 @@ frames, and consuming stream bridge events. Router modules
from __future__ import annotations
import asyncio
import dataclasses
import json
import logging
import re
import time
from collections.abc import Mapping
from typing import Any
from fastapi import HTTPException, Request
from langchain_core.messages import HumanMessage
from app.gateway.deps import get_run_context, get_run_manager, get_run_store, get_stream_bridge
from app.gateway.deps import get_run_context, get_run_manager, get_stream_bridge
from app.gateway.utils import sanitize_log_param
from deerflow.runtime import (
END_SENTINEL,

View File

@ -13,9 +13,7 @@ from deerflow.persistence.base import Base
class FeedbackRow(Base):
__tablename__ = "feedback"
__table_args__ = (
UniqueConstraint("thread_id", "run_id", "user_id", name="uq_feedback_thread_run_user"),
)
__table_args__ = (UniqueConstraint("thread_id", "run_id", "user_id", name="uq_feedback_thread_run_user"),)
feedback_id: Mapped[str] = mapped_column(String(64), primary_key=True)
run_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)

View File

@ -5,11 +5,10 @@ Usage:
The script is idempotent re-running it after a successful migration is a no-op.
"""
import argparse
import json
import logging
import shutil
from pathlib import Path
from deerflow.config.paths import Paths, get_paths

View File

@ -29,7 +29,6 @@ apps with the real middleware — those should not use this module.
from __future__ import annotations
from collections.abc import Callable
from typing import ParamSpec, TypeVar
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4
@ -113,11 +112,7 @@ def make_authed_test_app(
return app
_P = ParamSpec("_P")
_R = TypeVar("_R")
def call_unwrapped(decorated: Callable[_P, _R], /, *args: _P.args, **kwargs: _P.kwargs) -> _R:
def call_unwrapped[*P, R](decorated: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs) -> R:
"""Invoke the underlying function of a ``@require_permission``-decorated route.
``functools.wraps`` sets ``__wrapped__`` on each layer; we walk all

View File

@ -1,4 +1,5 @@
"""Tests for user_id propagation through memory queue."""
from unittest.mock import MagicMock, patch
from deerflow.agents.memory.queue import ConversationContext, MemoryUpdateQueue

View File

@ -1,8 +1,10 @@
"""Tests for per-user memory storage isolation."""
import pytest
from pathlib import Path
from unittest.mock import patch
import pytest
from deerflow.agents.memory.storage import FileMemoryStorage, create_empty_memory
@ -65,8 +67,8 @@ class TestUserIsolatedStorage:
assert loaded_a["user"]["workContext"]["summary"] == "A"
def test_no_user_id_uses_legacy_path(self, base_dir: Path):
from deerflow.config.paths import Paths
from deerflow.config.memory_config import MemoryConfig
from deerflow.config.paths import Paths
paths = Paths(base_dir)
with patch("deerflow.agents.memory.storage.get_paths", return_value=paths):
@ -79,8 +81,8 @@ class TestUserIsolatedStorage:
def test_user_and_legacy_do_not_interfere(self, base_dir: Path):
"""user_id=None (legacy) and user_id='alice' must use different files and caches."""
from deerflow.config.paths import Paths
from deerflow.config.memory_config import MemoryConfig
from deerflow.config.paths import Paths
paths = Paths(base_dir)
with patch("deerflow.agents.memory.storage.get_paths", return_value=paths):

View File

@ -1,7 +1,8 @@
"""Tests for user_id propagation in memory updater."""
from unittest.mock import MagicMock, patch
from deerflow.agents.memory.updater import get_memory_data, clear_memory_data, _save_memory_to_file
from deerflow.agents.memory.updater import _save_memory_to_file, clear_memory_data, get_memory_data
def test_get_memory_data_passes_user_id():

View File

@ -1,8 +1,10 @@
"""Tests for per-user data migration."""
import json
import pytest
from pathlib import Path
import pytest
from deerflow.config.paths import Paths
@ -23,6 +25,7 @@ class TestMigrateThreadDirs:
(legacy / "file.txt").write_text("hello")
from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"})
expected = base_dir / "users" / "alice" / "threads" / "t1" / "user-data" / "workspace" / "file.txt"
@ -35,6 +38,7 @@ class TestMigrateThreadDirs:
legacy.mkdir(parents=True)
from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={})
expected = base_dir / "users" / "default" / "threads" / "t2"
@ -45,6 +49,7 @@ class TestMigrateThreadDirs:
new_dir.mkdir(parents=True)
from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"})
assert new_dir.exists()
@ -58,6 +63,7 @@ class TestMigrateThreadDirs:
(dest / "new.txt").write_text("new")
from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"})
assert (dest / "new.txt").read_text() == "new"
@ -69,6 +75,7 @@ class TestMigrateThreadDirs:
legacy.mkdir(parents=True)
from scripts.migrate_user_isolation import migrate_thread_dirs
migrate_thread_dirs(paths, thread_owner_map={})
assert not (base_dir / "threads").exists()
@ -78,6 +85,7 @@ class TestMigrateThreadDirs:
legacy.mkdir(parents=True)
from scripts.migrate_user_isolation import migrate_thread_dirs
report = migrate_thread_dirs(paths, thread_owner_map={"t1": "alice"}, dry_run=True)
assert len(report) == 1
@ -91,6 +99,7 @@ class TestMigrateMemory:
legacy_mem.write_text(json.dumps({"version": "1.0", "facts": []}))
from scripts.migrate_user_isolation import migrate_memory
migrate_memory(paths, user_id="default")
expected = base_dir / "users" / "default" / "memory.json"
@ -106,6 +115,7 @@ class TestMigrateMemory:
dest.write_text(json.dumps({"version": "new"}))
from scripts.migrate_user_isolation import migrate_memory
migrate_memory(paths, user_id="default")
assert json.loads(dest.read_text())["version"] == "new"
@ -113,4 +123,5 @@ class TestMigrateMemory:
def test_no_legacy_memory_is_noop(self, base_dir: Path, paths: Paths):
from scripts.migrate_user_isolation import migrate_memory
migrate_memory(paths, user_id="default") # should not raise

View File

@ -1,7 +1,9 @@
"""Tests for user-scoped path resolution in Paths."""
import pytest
from pathlib import Path
import pytest
from deerflow.config.paths import Paths

View File

@ -1,4 +1,5 @@
"""Tests for paginated list_messages_by_run across all RunEventStore backends."""
import pytest
from deerflow.runtime.events.store.memory import MemoryRunEventStore
@ -14,14 +15,19 @@ async def test_list_messages_by_run_default_returns_all(base_store):
store = base_store
for i in range(7):
await store.put(
thread_id="t1", run_id="run-a",
thread_id="t1",
run_id="run-a",
event_type="human_message" if i % 2 == 0 else "ai_message",
category="message", content=f"msg-a-{i}",
category="message",
content=f"msg-a-{i}",
)
for i in range(3):
await store.put(
thread_id="t1", run_id="run-b",
event_type="human_message", category="message", content=f"msg-b-{i}",
thread_id="t1",
run_id="run-b",
event_type="human_message",
category="message",
content=f"msg-b-{i}",
)
await store.put(thread_id="t1", run_id="run-a", event_type="tool_call", category="trace", content="trace")
@ -36,9 +42,11 @@ async def test_list_messages_by_run_with_limit(base_store):
store = base_store
for i in range(7):
await store.put(
thread_id="t1", run_id="run-a",
thread_id="t1",
run_id="run-a",
event_type="human_message" if i % 2 == 0 else "ai_message",
category="message", content=f"msg-a-{i}",
category="message",
content=f"msg-a-{i}",
)
msgs = await store.list_messages_by_run("t1", "run-a", limit=3)
@ -52,9 +60,11 @@ async def test_list_messages_by_run_after_seq(base_store):
store = base_store
for i in range(7):
await store.put(
thread_id="t1", run_id="run-a",
thread_id="t1",
run_id="run-a",
event_type="human_message" if i % 2 == 0 else "ai_message",
category="message", content=f"msg-a-{i}",
category="message",
content=f"msg-a-{i}",
)
all_msgs = await store.list_messages_by_run("t1", "run-a")
@ -69,9 +79,11 @@ async def test_list_messages_by_run_before_seq(base_store):
store = base_store
for i in range(7):
await store.put(
thread_id="t1", run_id="run-a",
thread_id="t1",
run_id="run-a",
event_type="human_message" if i % 2 == 0 else "ai_message",
category="message", content=f"msg-a-{i}",
category="message",
content=f"msg-a-{i}",
)
all_msgs = await store.list_messages_by_run("t1", "run-a")
@ -86,13 +98,19 @@ async def test_list_messages_by_run_does_not_include_other_run(base_store):
store = base_store
for i in range(7):
await store.put(
thread_id="t1", run_id="run-a",
event_type="human_message", category="message", content=f"msg-a-{i}",
thread_id="t1",
run_id="run-a",
event_type="human_message",
category="message",
content=f"msg-a-{i}",
)
for i in range(3):
await store.put(
thread_id="t1", run_id="run-b",
event_type="human_message", category="message", content=f"msg-b-{i}",
thread_id="t1",
run_id="run-b",
event_type="human_message",
category="message",
content=f"msg-b-{i}",
)
msgs = await store.list_messages_by_run("t1", "run-b")

View File

@ -381,5 +381,3 @@ class TestMiddlewareEvents:
event_types = {e["event_type"] for e in events}
assert "middleware:title" in event_types
assert "middleware:guardrail" in event_types

View File

@ -1,15 +1,14 @@
"""Tests for GET /api/runs/{run_id}/messages and GET /api/runs/{run_id}/feedback endpoints."""
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock, MagicMock
import pytest
from _router_auth_helpers import make_authed_test_app
from fastapi.testclient import TestClient
from app.gateway.routers import runs
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
@ -113,7 +112,8 @@ def test_run_messages_passes_after_seq_to_event_store():
response = client.get("/api/runs/run-3/messages?after_seq=5")
assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with(
"thread-3", "run-3",
"thread-3",
"run-3",
limit=51, # default limit(50) + 1
before_seq=None,
after_seq=5,
@ -133,7 +133,8 @@ def test_run_messages_respects_custom_limit():
response = client.get("/api/runs/run-4/messages?limit=10")
assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with(
"thread-4", "run-4",
"thread-4",
"run-4",
limit=11, # 10 + 1
before_seq=None,
after_seq=None,
@ -153,7 +154,8 @@ def test_run_messages_passes_before_seq_to_event_store():
response = client.get("/api/runs/run-5/messages?before_seq=10")
assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with(
"thread-5", "run-5",
"thread-5",
"run-5",
limit=51,
before_seq=10,
after_seq=None,

View File

@ -1,15 +1,14 @@
"""Tests for paginated GET /api/threads/{thread_id}/runs/{run_id}/messages endpoint."""
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock
import pytest
from _router_auth_helpers import make_authed_test_app
from fastapi.testclient import TestClient
from app.gateway.routers import thread_runs
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
@ -78,7 +77,8 @@ def test_after_seq_forwarded_to_event_store():
response = client.get("/api/threads/thread-3/runs/run-3/messages?after_seq=5")
assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with(
"thread-3", "run-3",
"thread-3",
"run-3",
limit=51, # default limit(50) + 1
before_seq=None,
after_seq=5,
@ -94,7 +94,8 @@ def test_before_seq_forwarded_to_event_store():
response = client.get("/api/threads/thread-4/runs/run-4/messages?before_seq=10")
assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with(
"thread-4", "run-4",
"thread-4",
"run-4",
limit=51,
before_seq=10,
after_seq=None,
@ -110,7 +111,8 @@ def test_custom_limit_forwarded_to_event_store():
response = client.get("/api/threads/thread-5/runs/run-5/messages?limit=10")
assert response.status_code == 200
event_store.list_messages_by_run.assert_awaited_once_with(
"thread-5", "run-5",
"thread-5",
"run-5",
limit=11, # 10 + 1
before_seq=None,
after_seq=None,

View File

@ -10,8 +10,8 @@ from types import SimpleNamespace
import pytest
from deerflow.runtime.user_context import (
CurrentUser,
DEFAULT_USER_ID,
CurrentUser,
get_current_user,
get_effective_user_id,
require_current_user,
@ -100,6 +100,7 @@ def test_effective_user_id_returns_user_id_when_set():
def test_effective_user_id_coerces_to_str():
"""User.id might be a UUID object; must come back as str."""
import uuid
uid = uuid.uuid4()
user = SimpleNamespace(id=uid)