fix(core): coerce non-string entity ids instead of crashing on .strip() (#6206)
This commit is contained in:
+7
-1
@@ -145,9 +145,10 @@ def _reject_top_level_entity_params(kwargs: Dict[str, Any], method_name: str) ->
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _validate_and_trim_entity_id(value: Optional[str], name: str) -> Optional[str]:
|
def _validate_and_trim_entity_id(value: Optional[Any], name: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Validates and normalizes an entity ID.
|
Validates and normalizes an entity ID.
|
||||||
|
- Coerces non-string values (e.g. integer ids) to str
|
||||||
- Trims leading/trailing whitespace
|
- Trims leading/trailing whitespace
|
||||||
- Rejects empty or whitespace-only strings
|
- Rejects empty or whitespace-only strings
|
||||||
- Rejects strings containing internal whitespace
|
- Rejects strings containing internal whitespace
|
||||||
@@ -164,6 +165,11 @@ def _validate_and_trim_entity_id(value: Optional[str], name: str) -> Optional[st
|
|||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
# Callers commonly pass integer ids (e.g. a database primary key). Coerce
|
||||||
|
# to str at this single validation point so scoping stays consistent across
|
||||||
|
# add/search/get_all/delete_all instead of crashing on `.strip()`.
|
||||||
|
if not isinstance(value, str):
|
||||||
|
value = str(value)
|
||||||
trimmed = value.strip()
|
trimmed = value.strip()
|
||||||
if trimmed == "":
|
if trimmed == "":
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|||||||
+23
-1
@@ -5,7 +5,7 @@ from unittest.mock import Mock, patch
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mem0.configs.base import MemoryConfig
|
from mem0.configs.base import MemoryConfig
|
||||||
from mem0.memory.main import Memory
|
from mem0.memory.main import Memory, _validate_and_trim_entity_id
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@@ -433,6 +433,28 @@ class TestEntityIdValidation:
|
|||||||
|
|
||||||
memory_instance.vector_store.list.assert_called_once_with(filters={"user_id": "alice"})
|
memory_instance.vector_store.list.assert_called_once_with(filters={"user_id": "alice"})
|
||||||
|
|
||||||
|
def test_validate_coerces_non_string_entity_id(self):
|
||||||
|
"""Integer (and other non-string) ids are coerced to str, not crashed on."""
|
||||||
|
assert _validate_and_trim_entity_id(42, "user_id") == "42"
|
||||||
|
assert _validate_and_trim_entity_id(0, "user_id") == "0"
|
||||||
|
|
||||||
|
def test_delete_all_coerces_integer_user_id_before_list(self, memory_instance):
|
||||||
|
"""delete_all should accept an integer user_id and scope by its str form."""
|
||||||
|
memory_instance.vector_store.list = Mock(return_value=([], None))
|
||||||
|
|
||||||
|
memory_instance.delete_all(user_id=42)
|
||||||
|
|
||||||
|
memory_instance.vector_store.list.assert_called_once_with(filters={"user_id": "42"})
|
||||||
|
|
||||||
|
def test_get_all_coerces_integer_user_id(self, memory_instance):
|
||||||
|
"""get_all should accept an integer user_id in filters and scope by its str form."""
|
||||||
|
memory_instance.vector_store.list = Mock(return_value=([], None))
|
||||||
|
|
||||||
|
memory_instance.get_all(filters={"user_id": 42})
|
||||||
|
|
||||||
|
_, kwargs = memory_instance.vector_store.list.call_args
|
||||||
|
assert kwargs["filters"]["user_id"] == "42"
|
||||||
|
|
||||||
|
|
||||||
class TestSearchParamValidation:
|
class TestSearchParamValidation:
|
||||||
"""Tests for search parameter validation (threshold and top_k)."""
|
"""Tests for search parameter validation (threshold and top_k)."""
|
||||||
|
|||||||
Reference in New Issue
Block a user