Refa: empty ids means no-op operation (#13439)

### What problem does this PR solve?

Empty ids means no-op operation.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
- [x] Documentation Update
- [x] Refactoring

---------

Co-authored-by: writinwaters <cai.keith@gmail.com>
This commit is contained in:
Yongteng Lei
2026-03-06 18:16:42 +08:00
committed by GitHub
parent 7781c51a21
commit 51be1f1442
43 changed files with 446 additions and 190 deletions

View File

@ -25,6 +25,36 @@ def batch_create_datasets(client: RAGFlow, num: int) -> list[DataSet]:
return [client.create_dataset(name=f"dataset_{i}") for i in range(num)]
def delete_all_datasets(client: RAGFlow, *, page_size: int = 1000) -> None:
# Dataset DELETE now treats null/empty ids as a no-op, so cleanup must enumerate explicit ids.
page = 1
dataset_ids: list[str] = []
while True:
datasets = client.list_datasets(page=page, page_size=page_size)
dataset_ids.extend(dataset.id for dataset in datasets)
if len(datasets) < page_size:
break
page += 1
if dataset_ids:
client.delete_datasets(ids=dataset_ids)
def delete_all_chats(client: RAGFlow, *, page_size: int = 1000) -> None:
# Chat DELETE now treats null/empty ids as a no-op, so cleanup must enumerate explicit ids.
page = 1
chat_ids: list[str] = []
while True:
chats = client.list_chats(page=page, page_size=page_size)
chat_ids.extend(chat.id for chat in chats)
if len(chats) < page_size:
break
page += 1
if chat_ids:
client.delete_chats(ids=chat_ids)
# FILE MANAGEMENT WITHIN DATASET
def bulk_upload_documents(dataset: DataSet, num: int, tmp_path: Path) -> list[Document]:
document_infos = []
@ -37,6 +67,51 @@ def bulk_upload_documents(dataset: DataSet, num: int, tmp_path: Path) -> list[Do
return dataset.upload_documents(document_infos)
def delete_all_documents(dataset: DataSet, *, page_size: int = 1000) -> None:
# Document DELETE now treats missing/null/empty ids as a no-op, so cleanup must enumerate explicit ids.
page = 1
document_ids: list[str] = []
while True:
documents = dataset.list_documents(page=page, page_size=page_size)
document_ids.extend(document.id for document in documents)
if len(documents) < page_size:
break
page += 1
if document_ids:
dataset.delete_documents(ids=document_ids)
def delete_all_sessions(chat_assistant: Chat, *, page_size: int = 1000) -> None:
# Session DELETE now treats missing/null/empty ids as a no-op, so cleanup must enumerate explicit ids.
page = 1
session_ids: list[str] = []
while True:
sessions = chat_assistant.list_sessions(page=page, page_size=page_size)
session_ids.extend(session.id for session in sessions)
if len(sessions) < page_size:
break
page += 1
if session_ids:
chat_assistant.delete_sessions(ids=session_ids)
def delete_all_chunks(document: Document, *, page_size: int = 1000) -> None:
# Chunk DELETE now treats missing/null/empty ids as a no-op, so cleanup must enumerate explicit ids.
page = 1
chunk_ids: list[str] = []
while True:
chunks = document.list_chunks(page=page, page_size=page_size)
chunk_ids.extend(chunk.id for chunk in chunks)
if len(chunks) < page_size:
break
page += 1
if chunk_ids:
document.delete_chunks(ids=chunk_ids)
# CHUNK MANAGEMENT WITHIN DATASET
def batch_add_chunks(document: Document, num: int) -> list[Chunk]:
return [document.add_chunk(content=f"chunk test {i}") for i in range(num)]

View File

@ -23,6 +23,10 @@ from common import (
batch_create_chat_assistants,
batch_create_datasets,
bulk_upload_documents,
delete_all_chats,
delete_all_chunks,
delete_all_datasets,
delete_all_sessions,
)
from configs import HOST_ADDRESS, VERSION
from pytest import FixtureRequest
@ -88,7 +92,7 @@ def client(token: str) -> RAGFlow:
@pytest.fixture(scope="function")
def clear_datasets(request: FixtureRequest, client: RAGFlow):
def cleanup():
client.delete_datasets(ids=None)
delete_all_datasets(client)
request.addfinalizer(cleanup)
@ -96,7 +100,7 @@ def clear_datasets(request: FixtureRequest, client: RAGFlow):
@pytest.fixture(scope="function")
def clear_chat_assistants(request: FixtureRequest, client: RAGFlow):
def cleanup():
client.delete_chats(ids=None)
delete_all_chats(client)
request.addfinalizer(cleanup)
@ -106,7 +110,7 @@ def clear_session_with_chat_assistants(request, add_chat_assistants):
def cleanup():
for chat_assistant in chat_assistants:
try:
chat_assistant.delete_sessions(ids=None)
delete_all_sessions(chat_assistant)
except Exception:
pass
@ -118,7 +122,7 @@ def clear_session_with_chat_assistants(request, add_chat_assistants):
@pytest.fixture(scope="class")
def add_dataset(request: FixtureRequest, client: RAGFlow) -> DataSet:
def cleanup():
client.delete_datasets(ids=None)
delete_all_datasets(client)
request.addfinalizer(cleanup)
return batch_create_datasets(client, 1)[0]
@ -127,7 +131,7 @@ def add_dataset(request: FixtureRequest, client: RAGFlow) -> DataSet:
@pytest.fixture(scope="function")
def add_dataset_func(request: FixtureRequest, client: RAGFlow) -> DataSet:
def cleanup():
client.delete_datasets(ids=None)
delete_all_datasets(client)
request.addfinalizer(cleanup)
return batch_create_datasets(client, 1)[0]
@ -142,7 +146,7 @@ def add_document(add_dataset: DataSet, ragflow_tmp_dir: Path) -> tuple[DataSet,
def add_chunks(request: FixtureRequest, add_document: tuple[DataSet, Document]) -> tuple[DataSet, Document, list[Chunk]]:
def cleanup():
try:
document.delete_chunks(ids=[])
delete_all_chunks(document)
except Exception:
pass
@ -161,7 +165,7 @@ def add_chunks(request: FixtureRequest, add_document: tuple[DataSet, Document])
def add_chat_assistants(request, client, add_document) -> tuple[DataSet, Document, list[Chat]]:
def cleanup():
try:
client.delete_chats(ids=None)
delete_all_chats(client)
except Exception:
pass

View File

@ -14,7 +14,7 @@
# limitations under the License.
#
import pytest
from common import batch_create_chat_assistants
from common import batch_create_chat_assistants, delete_all_chats
from pytest import FixtureRequest
from ragflow_sdk import Chat, DataSet, Document, RAGFlow
from utils import wait_for
@ -32,7 +32,7 @@ def condition(_dataset: DataSet):
@pytest.fixture(scope="function")
def add_chat_assistants_func(request: FixtureRequest, client: RAGFlow, add_document: tuple[DataSet, Document]) -> tuple[DataSet, Document, list[Chat]]:
def cleanup():
client.delete_chats(ids=None)
delete_all_chats(client)
request.addfinalizer(cleanup)

View File

@ -23,8 +23,8 @@ class TestChatAssistantsDelete:
@pytest.mark.parametrize(
"payload, expected_message, remaining",
[
pytest.param(None, "", 0, marks=pytest.mark.p3),
pytest.param({"ids": []}, "", 0, marks=pytest.mark.p3),
pytest.param(None, "", 5, marks=pytest.mark.p3),
pytest.param({"ids": []}, "", 5, marks=pytest.mark.p3),
pytest.param({"ids": ["invalid_id"]}, "Assistant(invalid_id) not found.", 5, marks=pytest.mark.p3),
pytest.param({"ids": ["\n!?。;!?\"'"]}, """Assistant(\n!?。;!?"\') not found.""", 5, marks=pytest.mark.p3),
pytest.param(lambda r: {"ids": r[:1]}, "", 4, marks=pytest.mark.p3),

View File

@ -18,7 +18,7 @@
from time import sleep
import pytest
from common import batch_add_chunks
from common import batch_add_chunks, delete_all_chunks
from pytest import FixtureRequest
from ragflow_sdk import Chunk, DataSet, Document
from utils import wait_for
@ -37,7 +37,7 @@ def condition(_dataset: DataSet):
def add_chunks_func(request: FixtureRequest, add_document: tuple[DataSet, Document]) -> tuple[DataSet, Document, list[Chunk]]:
def cleanup():
try:
document.delete_chunks(ids=[])
delete_all_chunks(document)
except Exception:
pass

View File

@ -88,12 +88,12 @@ class TestChunksDeletion:
@pytest.mark.parametrize(
"payload, expected_message, remaining",
[
pytest.param(None, "TypeError", 5, marks=pytest.mark.skip),
pytest.param(None, "", 5, marks=pytest.mark.p3),
pytest.param({"ids": ["invalid_id"]}, "rm_chunk deleted chunks 0, expect 1", 5, marks=pytest.mark.p3),
pytest.param("not json", "UnboundLocalError", 5, marks=pytest.mark.skip(reason="pull/6376")),
pytest.param(lambda r: {"ids": r[:1]}, "", 4, marks=pytest.mark.p3),
pytest.param(lambda r: {"ids": r}, "", 1, marks=pytest.mark.p1),
pytest.param({"ids": []}, "", 0, marks=pytest.mark.p3),
pytest.param({"ids": []}, "", 5, marks=pytest.mark.p3),
],
)
def test_basic_scenarios(self, add_chunks_func, payload, expected_message, remaining):
@ -107,7 +107,10 @@ class TestChunksDeletion:
document.delete_chunks(**payload)
assert expected_message in str(exception_info.value), str(exception_info.value)
else:
document.delete_chunks(**payload)
if payload is None:
document.delete_chunks()
else:
document.delete_chunks(**payload)
remaining_chunks = document.list_chunks()
assert len(remaining_chunks) == remaining, str(remaining_chunks)

View File

@ -16,13 +16,13 @@
import pytest
from common import batch_create_datasets
from common import batch_create_datasets, delete_all_datasets
@pytest.fixture(scope="class")
def add_datasets(client, request):
def cleanup():
client.delete_datasets(**{"ids": None})
delete_all_datasets(client)
request.addfinalizer(cleanup)
@ -32,7 +32,7 @@ def add_datasets(client, request):
@pytest.fixture(scope="function")
def add_datasets_func(client, request):
def cleanup():
client.delete_datasets(**{"ids": None})
delete_all_datasets(client)
request.addfinalizer(cleanup)

View File

@ -95,7 +95,7 @@ class TestDatasetsDelete:
client.delete_datasets(**payload)
datasets = client.list_datasets()
assert len(datasets) == 0, str(datasets)
assert len(datasets) == 3, str(datasets)
@pytest.mark.p2
@pytest.mark.usefixtures("add_dataset_func")

View File

@ -16,7 +16,7 @@
import pytest
from common import bulk_upload_documents
from common import bulk_upload_documents, delete_all_documents
from pytest import FixtureRequest
from ragflow_sdk import DataSet, Document
@ -27,7 +27,7 @@ def add_document_func(request: FixtureRequest, add_dataset: DataSet, ragflow_tmp
documents = bulk_upload_documents(dataset, 1, ragflow_tmp_dir)
def cleanup():
dataset.delete_documents(ids=None)
delete_all_documents(dataset)
request.addfinalizer(cleanup)
return dataset, documents[0]
@ -39,7 +39,7 @@ def add_documents(request: FixtureRequest, add_dataset: DataSet, ragflow_tmp_dir
documents = bulk_upload_documents(dataset, 5, ragflow_tmp_dir)
def cleanup():
dataset.delete_documents(ids=None)
delete_all_documents(dataset)
request.addfinalizer(cleanup)
return dataset, documents
@ -51,7 +51,7 @@ def add_documents_func(request: FixtureRequest, add_dataset_func: DataSet, ragfl
documents = bulk_upload_documents(dataset, 3, ragflow_tmp_dir)
def cleanup():
dataset.delete_documents(ids=None)
delete_all_documents(dataset)
request.addfinalizer(cleanup)
return dataset, documents

View File

@ -24,8 +24,8 @@ class TestDocumentsDeletion:
@pytest.mark.parametrize(
"payload, expected_message, remaining",
[
({"ids": None}, "", 0),
({"ids": []}, "", 0),
({"ids": None}, "", 3),
({"ids": []}, "", 3),
({"ids": ["invalid_id"]}, "Documents not found: ['invalid_id']", 3),
({"ids": ["\n!?。;!?\"'"]}, "Documents not found: ['\\n!?。;!?\"\\'']", 3),
("not json", "must be a mapping", 3),

View File

@ -14,7 +14,7 @@
# limitations under the License.
#
import pytest
from common import batch_add_sessions_with_chat_assistant
from common import batch_add_sessions_with_chat_assistant, delete_all_sessions
from pytest import FixtureRequest
from ragflow_sdk import Chat, DataSet, Document, Session
@ -24,7 +24,7 @@ def add_sessions_with_chat_assistant(request: FixtureRequest, add_chat_assistant
def cleanup():
for chat_assistant in chat_assistants:
try:
chat_assistant.delete_sessions(ids=None)
delete_all_sessions(chat_assistant)
except Exception :
pass
@ -39,7 +39,7 @@ def add_sessions_with_chat_assistant_func(request: FixtureRequest, add_chat_assi
def cleanup():
for chat_assistant in chat_assistants:
try:
chat_assistant.delete_sessions(ids=None)
delete_all_sessions(chat_assistant)
except Exception :
pass

View File

@ -84,12 +84,12 @@ class TestSessionWithChatAssistantDelete:
@pytest.mark.parametrize(
"payload, expected_message, remaining",
[
pytest.param(None, """TypeError("argument of type \'NoneType\' is not iterable")""", 0, marks=pytest.mark.skip),
pytest.param(None, "", 5, marks=pytest.mark.p3),
pytest.param({"ids": ["invalid_id"]}, "The chat doesn't own the session invalid_id", 5, marks=pytest.mark.p3),
pytest.param("not json", """AttributeError("\'str\' object has no attribute \'get\'")""", 5, marks=pytest.mark.skip),
pytest.param(lambda r: {"ids": r[:1]}, "", 4, marks=pytest.mark.p3),
pytest.param(lambda r: {"ids": r}, "", 0, marks=pytest.mark.p1),
pytest.param({"ids": []}, "", 0, marks=pytest.mark.p3),
pytest.param({"ids": []}, "", 5, marks=pytest.mark.p3),
],
)
def test_basic_scenarios(self, add_sessions_with_chat_assistant_func, payload, expected_message, remaining):
@ -102,7 +102,10 @@ class TestSessionWithChatAssistantDelete:
chat_assistant.delete_sessions(**payload)
assert expected_message in str(exception_info.value)
else:
chat_assistant.delete_sessions(**payload)
if payload is None:
chat_assistant.delete_sessions()
else:
chat_assistant.delete_sessions(**payload)
sessions = chat_assistant.list_sessions()
assert len(sessions) == remaining