fix: fix permalink uniqueness violations on create/update/sync

This commit is contained in:
phernandez
2025-02-04 16:43:18 -06:00
parent c429d6487f
commit 135bec181d
6 changed files with 199 additions and 11 deletions
+5 -2
View File
@@ -181,6 +181,9 @@ class Entity(BaseModel):
- Optional relations to other entities
- Optional description for high-level overview
"""
# private field to override permalink
_permalink: Optional[str] = None
title: str
content: Optional[str] = None
@@ -199,8 +202,8 @@ class Entity(BaseModel):
@property
def permalink(self) -> PathId:
"""Get the path ID in format {snake_case_title}."""
return generate_permalink(self.file_path)
"""Get a url friendly path}."""
return self._permalink or generate_permalink(self.file_path)
@model_validator(mode="after")
@classmethod
+41 -2
View File
@@ -19,6 +19,7 @@ from basic_memory.services import FileService
from basic_memory.services import BaseService
from basic_memory.services.link_resolver import LinkResolver
from basic_memory.markdown.entity_parser import EntityParser
from basic_memory.utils import generate_permalink
class EntityService(BaseService[EntityModel]):
@@ -40,6 +41,40 @@ class EntityService(BaseService[EntityModel]):
self.file_service = file_service
self.link_resolver = link_resolver
async def resolve_permalink(
self,
file_path: Path,
markdown: Optional[EntityMarkdown] = None
) -> str:
"""Get or generate unique permalink for an entity.
Priority:
1. Use explicit permalink from markdown frontmatter if present
2. For existing files, keep current permalink
3. Generate new unique permalink for new files
"""
# If markdown has explicit permalink, try to use it
if markdown and markdown.frontmatter.permalink:
desired_permalink = markdown.frontmatter.permalink
else:
# For existing files, try to find current permalink
existing = await self.repository.get_by_file_path(str(file_path))
if existing:
return existing.permalink
# New file - generate permalink
desired_permalink = generate_permalink(file_path)
# Make unique if needed
permalink = desired_permalink
suffix = 1
while await self.repository.get_by_permalink(permalink):
permalink = f"{desired_permalink}-{suffix}"
suffix += 1
logger.debug(f"creating unique permalink: {permalink}")
return permalink
async def create_or_update_entity(self, schema: EntitySchema) -> (EntityModel, bool):
"""Create new entity or update existing one.
if a new entity is created, the return value is (entity, True)
@@ -66,9 +101,13 @@ class EntityService(BaseService[EntityModel]):
if await self.file_service.exists(file_path):
raise EntityCreationError(
f"file_path {file_path} for entity {schema.permalink} already exists: {file_path}"
f"file for entity {schema.folder}/{schema.title} already exists: {file_path}"
)
# Get unique permalink
permalink = await self.resolve_permalink(schema.permalink or file_path)
schema._permalink = permalink
post = await schema_to_markdown(schema)
# write file
@@ -184,7 +223,7 @@ class EntityService(BaseService[EntityModel]):
Creates the entity with null checksum to indicate sync not complete.
Relations will be added in second pass.
"""
logger.debug(f"Creating entity: {markdown.frontmatter.title}")
logger.debug(f"Creating entity: {markdown.frontmatter.title}")
model = entity_model_from_markdown(file_path, markdown)
# Mark as incomplete sync
@@ -35,7 +35,6 @@ class FileService:
"""Generate absolute filesystem path for entity."""
return self.base_path / f"{entity.file_path}"
# TODO move to tests
async def write_entity_file(
self,
entity: EntityModel,
+20
View File
@@ -92,8 +92,28 @@ class SyncService:
# First pass: Create/update entities
# entities will have a null checksum to indicate they are not complete
for file_path, entity_markdown in parsed_entities.items():
# Get unique permalink and update markdown if needed
permalink = await self.entity_service.resolve_permalink(
file_path,
markdown=entity_markdown
)
if permalink != entity_markdown.frontmatter.permalink:
# Permalink changed - update markdown and rewrite file
entity_markdown.frontmatter.metadata["permalink"] = permalink
# update file
logger.info(f"Adding permalink '{permalink}' to file: {file_path}")
updated_checksum = await self.entity_service.file_service.markdown_processor.write_file(
directory / file_path, entity_markdown)
# Update checksum in changes report since file was modified
changes.checksums[file_path] = updated_checksum
# if the file is new, create an entity
if file_path in changes.new:
# Create entity with final permalink
logger.debug(f"Creating new entity_markdown: {file_path}")
await self.entity_service.create_entity_from_markdown(
file_path, entity_markdown