Refactor created/modified handling in markdown entities.

Replace frontmatter properties with top-level attributes for `created` and `modified` to simplify handling and ensure consistent attribute usage. Added a utility function for schema-to-markdown conversion and updated related tests and code to reflect these changes.
This commit is contained in:
phernandez
2025-01-31 13:59:06 -06:00
parent 3e064cbbed
commit 9983271e94
7 changed files with 50 additions and 54 deletions
+10 -15
View File
@@ -53,6 +53,13 @@ def parse(content: str) -> EntityContent:
relations=relations,
)
def parse_tags(tags: Any) -> list[str]:
"""Parse tags into list of strings."""
if isinstance(tags, str):
return [t.strip() for t in tags.split(",") if t.strip()]
if isinstance(tags, (list, tuple)):
return [str(t).strip() for t in tags if str(t).strip()]
return []
class EntityParser:
"""Parser for markdown files into Entity objects."""
@@ -107,17 +114,10 @@ class EntityParser:
# Extract file stat info
file_stats = absolute_path.stat()
metadata = post.metadata
metadata["title"] = post.metadata.get("title", file_path.name)
metadata["type"] = metadata.get("type", "note")
metadata["created"] = self.parse_date(
post.metadata.get("created")
) or datetime.fromtimestamp(file_stats.st_ctime)
metadata["modified"] = self.parse_date(
post.metadata.get("modified")
) or datetime.fromtimestamp(file_stats.st_mtime)
metadata["tags"] = self.parse_tags(post.metadata.get("tags", []))
metadata["tags"] = parse_tags(post.metadata.get("tags", []))
# frontmatter
entity_frontmatter = EntityFrontmatter(
@@ -131,12 +131,7 @@ class EntityParser:
content=post.content,
observations=entity_content.observations,
relations=entity_content.relations,
created=datetime.fromtimestamp(file_stats.st_ctime),
modified=datetime.fromtimestamp(file_stats.st_mtime),
)
def parse_tags(self, tags: Any) -> list[str]:
"""Parse tags into list of strings."""
if isinstance(tags, str):
return [t.strip() for t in tags.split(",") if t.strip()]
if isinstance(tags, (list, tuple)):
return [str(t).strip() for t in tags if str(t).strip()]
return []
@@ -105,10 +105,10 @@ class MarkdownProcessor:
"type": markdown.frontmatter.type,
"permalink": markdown.frontmatter.permalink,
"created": markdown.frontmatter.created.isoformat()
if markdown.frontmatter.created
if markdown.created
else None,
"modified": markdown.frontmatter.modified.isoformat()
if markdown.frontmatter.modified
if markdown.modified
else None,
**metadata,
}
+4 -7
View File
@@ -58,13 +58,6 @@ class EntityFrontmatter(BaseModel):
def permalink(self) -> str:
return self.metadata.get("permalink") if self.metadata else None
@property
def created(self) -> datetime:
return self.metadata.get("created") if self.metadata else None
@property
def modified(self) -> datetime:
return self.metadata.get("modified") if self.metadata else None
class EntityMarkdown(BaseModel):
@@ -74,3 +67,7 @@ class EntityMarkdown(BaseModel):
content: Optional[str] = None
observations: List[Observation] = []
relations: List[Relation] = []
# created, updated will have values after a read
created: Optional[datetime] = None
modified: Optional[datetime] = None
+22 -2
View File
@@ -1,6 +1,8 @@
from pathlib import Path
from typing import Optional
from frontmatter import Post
from basic_memory.markdown import EntityMarkdown, EntityFrontmatter, Observation, Relation
from basic_memory.markdown.entity_parser import parse
from basic_memory.models import Entity, ObservationCategory, Observation as ObservationModel
@@ -105,8 +107,8 @@ def entity_model_from_markdown(file_path: Path, markdown: EntityMarkdown, entity
model.permalink=permalink
model.file_path=str(file_path)
model.content_type="text/markdown"
model.created_at=markdown.frontmatter.created
model.updated_at=markdown.frontmatter.modified
model.created_at=markdown.created
model.updated_at=markdown.modified
model.entity_metadata={k:str(v) for k,v in markdown.frontmatter.metadata.items()}
model.observations=[
ObservationModel(
@@ -119,3 +121,21 @@ def entity_model_from_markdown(file_path: Path, markdown: EntityMarkdown, entity
]
return model
async def schema_to_markdown(schema):
"""
Convert schema to markdown.
:param schema: the schema to convert
:return: Post
"""
# Add metadata to dict
frontmatter_dict = schema.entity_metadata or {}
# set permalink and type
frontmatter_dict["permalink"] = schema.permalink
frontmatter_dict["type"] = schema.entity_type
# Create Post object
content = schema.content or ""
post = Post(content, **frontmatter_dict)
return post
+6 -17
View File
@@ -9,7 +9,7 @@ from loguru import logger
from sqlalchemy.exc import IntegrityError
from basic_memory.markdown import EntityMarkdown
from basic_memory.markdown.utils import entity_model_from_markdown
from basic_memory.markdown.utils import entity_model_from_markdown, schema_to_markdown
from basic_memory.models import Entity as EntityModel, Observation, Relation
from basic_memory.repository import ObservationRepository, RelationRepository
from basic_memory.repository.entity_repository import EntityRepository
@@ -69,14 +69,7 @@ class EntityService(BaseService[EntityModel]):
f"file_path {file_path} for entity {schema.permalink} already exists: {file_path}"
)
# Convert frontmatter to dict
frontmatter_dict = schema.entity_metadata or {}
frontmatter_dict["permalink"] = schema.permalink
frontmatter_dict["type"] = schema.entity_type
# Create Post object for frontmatter
content = schema.content or ""
post = Post(content, **frontmatter_dict)
post = await schema_to_markdown(schema)
# write file
final_content = frontmatter.dumps(post)
@@ -84,6 +77,8 @@ class EntityService(BaseService[EntityModel]):
# parse entity from file
entity_markdown = await self.entity_parser.parse_file(file_path)
# create entity
created_entity = await self.create_entity_from_markdown(
file_path, entity_markdown
)
@@ -94,6 +89,7 @@ class EntityService(BaseService[EntityModel]):
# Set final checksum to mark complete
return await self.repository.update(entity.id, {"checksum": checksum})
async def update_entity(self, schema: EntitySchema) -> EntityModel:
"""Update an entity's content and metadata."""
logger.debug(f"Updating entity with permalink: {schema.permalink}")
@@ -101,14 +97,7 @@ class EntityService(BaseService[EntityModel]):
# get file path
file_path = Path(schema.file_path)
# Convert frontmatter to dict
frontmatter_dict = schema.entity_metadata or {}
frontmatter_dict["permalink"] = schema.permalink
frontmatter_dict["type"] = schema.entity_type
# Create Post object for frontmatter
content = schema.content or ""
post = Post(content, **frontmatter_dict)
post = await schema_to_markdown(schema)
# write file
final_content = frontmatter.dumps(post)