mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 23:06:40 +08:00
refactor(api): enable strict typing for RAG keyword and vector base tests (#43895)
This commit is contained in:
1 parent
1759587078
commit
fa40cb8bbb
6 files changed
+56
-60
No files matched your search
@@ -1,7 +1,7 @@
|
||||
from core.rag.datasource.keyword.jieba.stopwords import STOPWORDS
|
||||
|
||||
|
||||
def test_stopwords_loaded():
|
||||
def test_stopwords_loaded() -> None:
|
||||
assert isinstance(STOPWORDS, frozenset)
|
||||
assert "during" in STOPWORDS
|
||||
assert "the" in STOPWORDS
|
||||
@@ -1,4 +1,3 @@
|
||||
from types import SimpleNamespace
|
||||
from typing import override
|
||||
|
||||
import pytest
|
||||
@@ -16,61 +15,61 @@ def _dataset() -> Dataset:
|
||||
|
||||
class _KeywordThatRaises(BaseKeyword):
|
||||
@override
|
||||
def create(self, texts: list[Document], session, **kwargs):
|
||||
def create(self, texts: list[Document], session: Session, **kwargs: object) -> BaseKeyword:
|
||||
return super().create(texts, session, **kwargs)
|
||||
|
||||
@override
|
||||
def add_texts(self, texts: list[Document], session, **kwargs):
|
||||
def add_texts(self, texts: list[Document], session: Session, **kwargs: object) -> None:
|
||||
return super().add_texts(texts, session, **kwargs)
|
||||
|
||||
@override
|
||||
def text_exists(self, id: str, *, session) -> bool:
|
||||
def text_exists(self, id: str, *, session: Session) -> bool:
|
||||
return super().text_exists(id, session=session)
|
||||
|
||||
@override
|
||||
def delete_by_ids(self, ids: list[str], session, **kwargs):
|
||||
def delete_by_ids(self, ids: list[str], session: Session, **kwargs: object) -> None:
|
||||
return super().delete_by_ids(ids, session, **kwargs)
|
||||
|
||||
@override
|
||||
def delete(self, *, session):
|
||||
def delete(self, *, session: Session) -> None:
|
||||
return super().delete(session=session)
|
||||
|
||||
@override
|
||||
def search(self, query: str, *, session, **kwargs):
|
||||
def search(self, query: str, *, session: Session, **kwargs: object) -> list[Document]:
|
||||
return super().search(query, session=session, **kwargs)
|
||||
|
||||
|
||||
class _KeywordForHelpers(BaseKeyword):
|
||||
def __init__(self, dataset, existing_ids: set[str] | None = None):
|
||||
def __init__(self, dataset: Dataset, existing_ids: set[str] | None = None) -> None:
|
||||
super().__init__(dataset)
|
||||
self._existing_ids = existing_ids or set()
|
||||
|
||||
@override
|
||||
def create(self, texts: list[Document], session, **kwargs):
|
||||
def create(self, texts: list[Document], session: Session, **kwargs: object) -> BaseKeyword:
|
||||
return self
|
||||
|
||||
@override
|
||||
def add_texts(self, texts: list[Document], session, **kwargs):
|
||||
def add_texts(self, texts: list[Document], session: Session, **kwargs: object) -> None:
|
||||
return None
|
||||
|
||||
@override
|
||||
def text_exists(self, id: str, *, session) -> bool:
|
||||
def text_exists(self, id: str, *, session: Session) -> bool:
|
||||
return id in self._existing_ids
|
||||
|
||||
@override
|
||||
def delete_by_ids(self, ids: list[str], session, **kwargs):
|
||||
def delete_by_ids(self, ids: list[str], session: Session, **kwargs: object) -> None:
|
||||
return None
|
||||
|
||||
@override
|
||||
def delete(self, *, session):
|
||||
def delete(self, *, session: Session) -> None:
|
||||
return None
|
||||
|
||||
@override
|
||||
def search(self, query: str, *, session, **kwargs):
|
||||
def search(self, query: str, *, session: Session, **kwargs: object) -> list[Document]:
|
||||
return []
|
||||
|
||||
|
||||
def test_abstract_methods_raise_not_implemented(unbound_session: Session):
|
||||
def test_abstract_methods_raise_not_implemented(unbound_session: Session) -> None:
|
||||
keyword = _KeywordThatRaises(_dataset())
|
||||
session = unbound_session
|
||||
|
||||
@@ -93,12 +92,12 @@ def test_abstract_methods_raise_not_implemented(unbound_session: Session):
|
||||
keyword.search("query", session=session)
|
||||
|
||||
|
||||
def test_filter_duplicate_texts_removes_existing_doc_ids(unbound_session: Session):
|
||||
def test_filter_duplicate_texts_removes_existing_doc_ids(unbound_session: Session) -> None:
|
||||
keyword = _KeywordForHelpers(_dataset(), existing_ids={"duplicate"})
|
||||
texts = [
|
||||
Document(page_content="keep", metadata={"doc_id": "keep"}),
|
||||
Document(page_content="duplicate", metadata={"doc_id": "duplicate"}),
|
||||
SimpleNamespace(page_content="without-metadata", metadata=None),
|
||||
Document.model_construct(page_content="without-metadata", metadata=None),
|
||||
]
|
||||
|
||||
filtered = keyword._filter_duplicate_texts(texts, session=unbound_session)
|
||||
@@ -107,12 +106,12 @@ def test_filter_duplicate_texts_removes_existing_doc_ids(unbound_session: Sessio
|
||||
assert any(text.metadata is None for text in filtered)
|
||||
|
||||
|
||||
def test_get_uuids_returns_only_docs_with_metadata():
|
||||
def test_get_uuids_returns_only_docs_with_metadata() -> None:
|
||||
keyword = _KeywordForHelpers(_dataset())
|
||||
texts = [
|
||||
Document(page_content="doc-1", metadata={"doc_id": "doc-1"}),
|
||||
Document(page_content="doc-2", metadata={"doc_id": "doc-2"}),
|
||||
SimpleNamespace(page_content="doc-3", metadata=None),
|
||||
Document.model_construct(page_content="doc-3", metadata=None),
|
||||
]
|
||||
|
||||
assert keyword._get_uuids(texts) == ["doc-1", "doc-2"]
|
||||
@@ -12,24 +12,24 @@ from models.dataset import Dataset
|
||||
from tests.unit_tests.config_override import apply_config_overrides
|
||||
|
||||
|
||||
def test_get_keyword_factory_returns_jieba_factory(monkeypatch: pytest.MonkeyPatch):
|
||||
def test_get_keyword_factory_returns_jieba_factory(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
fake_module = types.ModuleType("core.rag.datasource.keyword.jieba.jieba")
|
||||
|
||||
class FakeJieba:
|
||||
pass
|
||||
|
||||
fake_module.Jieba = FakeJieba
|
||||
vars(fake_module)["Jieba"] = FakeJieba
|
||||
monkeypatch.setitem(sys.modules, "core.rag.datasource.keyword.jieba.jieba", fake_module)
|
||||
|
||||
assert Keyword.get_keyword_factory(KeyWordType.JIEBA) is FakeJieba
|
||||
|
||||
|
||||
def test_get_keyword_factory_raises_for_unsupported_type():
|
||||
def test_get_keyword_factory_raises_for_unsupported_type() -> None:
|
||||
with pytest.raises(ValueError, match="Keyword store unsupported is not supported"):
|
||||
Keyword.get_keyword_factory("unsupported")
|
||||
|
||||
|
||||
def test_keyword_initialization_uses_configured_factory(monkeypatch: pytest.MonkeyPatch):
|
||||
def test_keyword_initialization_uses_configured_factory(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
dataset = Dataset(
|
||||
id="dataset-1",
|
||||
tenant_id="tenant-1",
|
||||
@@ -47,7 +47,7 @@ def test_keyword_initialization_uses_configured_factory(monkeypatch: pytest.Monk
|
||||
assert keyword._keyword_processor is fake_processor
|
||||
|
||||
|
||||
def test_keyword_methods_forward_to_processor(unbound_session: Session):
|
||||
def test_keyword_methods_forward_to_processor(unbound_session: Session) -> None:
|
||||
processor = MagicMock()
|
||||
processor.text_exists.return_value = True
|
||||
processor.search.return_value = [Document(page_content="matched", metadata={"doc_id": "doc-1"})]
|
||||
@@ -72,22 +72,22 @@ def test_keyword_methods_forward_to_processor(unbound_session: Session):
|
||||
processor.search.assert_called_once_with("query", session=session, top_k=1)
|
||||
|
||||
|
||||
def test_keyword_getattr_returns_callable_and_raises_for_invalid_attributes():
|
||||
def test_keyword_getattr_returns_callable_and_raises_for_invalid_attributes() -> None:
|
||||
class Processor:
|
||||
value = 1
|
||||
|
||||
@staticmethod
|
||||
def custom():
|
||||
def custom() -> str:
|
||||
return "ok"
|
||||
|
||||
keyword = Keyword.__new__(Keyword)
|
||||
keyword._keyword_processor = Processor()
|
||||
keyword._keyword_processor = Processor() # pyrefly: ignore[bad-assignment]
|
||||
|
||||
assert keyword.custom() == "ok"
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
_ = keyword.value
|
||||
|
||||
keyword._keyword_processor = None
|
||||
keyword._keyword_processor = None # pyrefly: ignore[bad-assignment]
|
||||
with pytest.raises(AttributeError):
|
||||
_ = keyword.missing_method
|
||||
@@ -4,42 +4,42 @@ from core.rag.datasource.vdb.field import parse_metadata_json
|
||||
|
||||
|
||||
class TestParseMetadataJson:
|
||||
def test_none_returns_empty_dict(self):
|
||||
def test_none_returns_empty_dict(self) -> None:
|
||||
assert parse_metadata_json(None) == {}
|
||||
|
||||
def test_empty_string_returns_empty_dict(self):
|
||||
def test_empty_string_returns_empty_dict(self) -> None:
|
||||
assert parse_metadata_json("") == {}
|
||||
|
||||
def test_valid_json_string(self):
|
||||
def test_valid_json_string(self) -> None:
|
||||
result = parse_metadata_json('{"doc_id": "abc", "score": 0.9}')
|
||||
assert result == {"doc_id": "abc", "score": 0.9}
|
||||
|
||||
def test_dict_passthrough(self):
|
||||
def test_dict_passthrough(self) -> None:
|
||||
original = {"doc_id": "abc", "document_id": "123"}
|
||||
result = parse_metadata_json(original)
|
||||
assert result == original
|
||||
|
||||
def test_empty_json_object(self):
|
||||
def test_empty_json_object(self) -> None:
|
||||
assert parse_metadata_json("{}") == {}
|
||||
|
||||
def test_invalid_json_raises_value_error(self):
|
||||
def test_invalid_json_raises_value_error(self) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
parse_metadata_json("{invalid json")
|
||||
|
||||
def test_nested_metadata(self):
|
||||
def test_nested_metadata(self) -> None:
|
||||
result = parse_metadata_json('{"doc_id": "1", "extra": {"nested": true}}')
|
||||
assert result["extra"]["nested"] is True
|
||||
|
||||
def test_non_str_non_dict_returns_empty_dict(self):
|
||||
def test_non_str_non_dict_returns_empty_dict(self) -> None:
|
||||
assert parse_metadata_json(123) == {}
|
||||
assert parse_metadata_json([1, 2]) == {}
|
||||
|
||||
def test_bytes_input(self):
|
||||
def test_bytes_input(self) -> None:
|
||||
result = parse_metadata_json(b'{"key": "value"}')
|
||||
assert result == {"key": "value"}
|
||||
|
||||
def test_empty_bytes_returns_empty_dict(self):
|
||||
def test_empty_bytes_returns_empty_dict(self) -> None:
|
||||
assert parse_metadata_json(b"") == {}
|
||||
|
||||
def test_empty_bytearray_returns_empty_dict(self):
|
||||
def test_empty_bytearray_returns_empty_dict(self) -> None:
|
||||
assert parse_metadata_json(bytearray(b"")) == {}
|
||||
@@ -1,4 +1,4 @@
|
||||
from types import SimpleNamespace
|
||||
from collections.abc import Callable
|
||||
from typing import override
|
||||
|
||||
import pytest
|
||||
@@ -8,7 +8,7 @@ from core.rag.models.document import Document
|
||||
|
||||
|
||||
class _DummyVector(BaseVector):
|
||||
def __init__(self, collection_name: str, existing_ids: set[str] | None = None):
|
||||
def __init__(self, collection_name: str, existing_ids: set[str] | None = None) -> None:
|
||||
super().__init__(collection_name)
|
||||
self._existing_ids = existing_ids or set()
|
||||
|
||||
@@ -17,35 +17,35 @@ class _DummyVector(BaseVector):
|
||||
return "dummy"
|
||||
|
||||
@override
|
||||
def create(self, texts: list[Document], embeddings: list[list[float]], **kwargs):
|
||||
def create(self, texts: list[Document], embeddings: list[list[float]], **kwargs: object) -> None:
|
||||
return None
|
||||
|
||||
@override
|
||||
def add_texts(self, documents: list[Document], embeddings: list[list[float]], **kwargs):
|
||||
return None
|
||||
def add_texts(self, documents: list[Document], embeddings: list[list[float]], **kwargs: object) -> list[str]:
|
||||
return []
|
||||
|
||||
@override
|
||||
def text_exists(self, id: str) -> bool:
|
||||
return id in self._existing_ids
|
||||
|
||||
@override
|
||||
def delete_by_ids(self, ids: list[str]):
|
||||
def delete_by_ids(self, ids: list[str]) -> None:
|
||||
return None
|
||||
|
||||
@override
|
||||
def delete_by_metadata_field(self, key: str, value: str):
|
||||
def delete_by_metadata_field(self, key: str, value: str) -> None:
|
||||
return None
|
||||
|
||||
@override
|
||||
def search_by_vector(self, query_vector: list[float], **kwargs):
|
||||
def search_by_vector(self, query_vector: list[float], **kwargs: object) -> list[Document]:
|
||||
return []
|
||||
|
||||
@override
|
||||
def search_by_full_text(self, query: str, **kwargs):
|
||||
def search_by_full_text(self, query: str, **kwargs: object) -> list[Document]:
|
||||
return []
|
||||
|
||||
@override
|
||||
def delete(self):
|
||||
def delete(self) -> None:
|
||||
return None
|
||||
|
||||
|
||||
@@ -64,17 +64,19 @@ class _DummyVector(BaseVector):
|
||||
(BaseVector.delete, ()),
|
||||
],
|
||||
)
|
||||
def test_base_vector_default_methods_raise_not_implemented(base_method, args):
|
||||
def test_base_vector_default_methods_raise_not_implemented(
|
||||
base_method: Callable[..., object], args: tuple[object, ...]
|
||||
) -> None:
|
||||
vector = _DummyVector("collection_1")
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
base_method(vector, *args)
|
||||
|
||||
|
||||
def test_filter_duplicate_texts_removes_existing_docs():
|
||||
def test_filter_duplicate_texts_removes_existing_docs() -> None:
|
||||
vector = _DummyVector("collection_1", existing_ids={"dup"})
|
||||
docs = [
|
||||
SimpleNamespace(page_content="keep-no-meta", metadata=None),
|
||||
Document.model_construct(page_content="keep-no-meta", metadata=None),
|
||||
Document(page_content="keep-no-doc-id", metadata={"document_id": "d1"}),
|
||||
Document(page_content="remove-dup", metadata={"doc_id": "dup"}),
|
||||
Document(page_content="keep-unique", metadata={"doc_id": "unique"}),
|
||||
@@ -85,11 +87,11 @@ def test_filter_duplicate_texts_removes_existing_docs():
|
||||
assert [d.page_content for d in filtered] == ["keep-no-meta", "keep-no-doc-id", "keep-unique"]
|
||||
|
||||
|
||||
def test_get_uuids_and_collection_name_property():
|
||||
def test_get_uuids_and_collection_name_property() -> None:
|
||||
vector = _DummyVector("collection_1")
|
||||
docs = [
|
||||
Document(page_content="a", metadata={"doc_id": "id-1"}),
|
||||
SimpleNamespace(page_content="b", metadata=None),
|
||||
Document.model_construct(page_content="b", metadata=None),
|
||||
Document(page_content="c", metadata={"document_id": "d-1"}),
|
||||
Document(page_content="d", metadata={"doc_id": "id-2"}),
|
||||
]
|
||||
|
||||
@@ -393,12 +393,7 @@ project-excludes = [
|
||||
"core/rag/data_post_processor/test_data_post_processor.py",
|
||||
"core/rag/datasource/keyword/jieba/test_jieba.py",
|
||||
"core/rag/datasource/keyword/jieba/test_jieba_keyword_table_handler.py",
|
||||
"core/rag/datasource/keyword/jieba/test_stopwords.py",
|
||||
"core/rag/datasource/keyword/test_keyword_base.py",
|
||||
"core/rag/datasource/keyword/test_keyword_factory.py",
|
||||
"core/rag/datasource/test_datasource_retrieval.py",
|
||||
"core/rag/datasource/vdb/test_field.py",
|
||||
"core/rag/datasource/vdb/test_vector_base.py",
|
||||
"core/rag/datasource/vdb/test_vector_factory.py",
|
||||
"core/rag/embedding/test_embedding_base.py",
|
||||
"core/rag/embedding/test_embedding_service.py",
|
||||
|
||||
Reference in new issue
Block a user