mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
feat: expose external_id in EntityResponse and link resolver (#569)
Signed-off-by: Drew Cain <groksrc@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -195,6 +195,7 @@ class EntityResponse(SQLAlchemyModel):
|
||||
entity_metadata: Optional[Dict] = None
|
||||
checksum: Optional[str] = None
|
||||
content_type: ContentType
|
||||
external_id: Optional[str] = None
|
||||
observations: List[ObservationResponse] = []
|
||||
relations: List[RelationResponse] = []
|
||||
created_at: datetime
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Service for resolving markdown links to permalinks."""
|
||||
|
||||
import uuid as uuid_mod
|
||||
from typing import Optional, Tuple, Dict
|
||||
|
||||
from loguru import logger
|
||||
@@ -63,6 +64,19 @@ class LinkResolver:
|
||||
explicit_project_reference = "::" in clean_text
|
||||
clean_text = normalize_project_reference(clean_text)
|
||||
|
||||
# --- External ID Resolution ---
|
||||
# Try external_id first if identifier looks like a UUID.
|
||||
# Canonicalize to lowercase-hyphen form so uppercase or unhyphenated
|
||||
# UUIDs also match the stored external_id values.
|
||||
try:
|
||||
canonical_id = str(uuid_mod.UUID(clean_text))
|
||||
entity = await self.entity_repository.get_by_external_id(canonical_id)
|
||||
if entity:
|
||||
logger.debug(f"Found entity by external_id: {entity.permalink}")
|
||||
return entity
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Trigger: link uses project namespace syntax (project::note)
|
||||
# Why: treat it as an explicit cross-project reference
|
||||
# Outcome: resolve only within the referenced project scope
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for link resolution service."""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
@@ -857,3 +858,44 @@ async def test_simple_link_no_slash_skips_relative_resolution(relative_path_reso
|
||||
# testing/nested/ is not the same folder as testing/, but it's closer than nested/
|
||||
# The context-aware resolution will pick the closest match
|
||||
assert result.file_path == "testing/nested/deep-note.md"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# External ID (UUID) resolution tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_link_by_external_id(link_resolver, test_entities):
|
||||
"""Test resolving a link using a valid external_id (UUID)."""
|
||||
entity = test_entities[0]
|
||||
result = await link_resolver.resolve_link(entity.external_id)
|
||||
assert result is not None
|
||||
assert result.id == entity.id
|
||||
assert result.external_id == entity.external_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_link_by_external_id_uppercase(link_resolver, test_entities):
|
||||
"""Test that uppercase UUID is canonicalized and resolves correctly."""
|
||||
entity = test_entities[0]
|
||||
upper_id = entity.external_id.upper()
|
||||
result = await link_resolver.resolve_link(upper_id)
|
||||
assert result is not None
|
||||
assert result.id == entity.id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_link_by_external_id_nonexistent(link_resolver):
|
||||
"""Test that a valid UUID format that doesn't match any entity returns None."""
|
||||
fake_id = str(uuid.uuid4())
|
||||
result = await link_resolver.resolve_link(fake_id)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_link_non_uuid_falls_through(link_resolver, test_entities, project_prefix):
|
||||
"""Test that non-UUID strings skip UUID resolution and use normal lookup."""
|
||||
result = await link_resolver.resolve_link("Core Service")
|
||||
assert result is not None
|
||||
assert result.permalink == f"{project_prefix}/components/core-service"
|
||||
|
||||
Reference in New Issue
Block a user