add title/folder to EntitySchema

This commit is contained in:
phernandez
2025-01-31 19:29:16 -06:00
parent f0fd1ee942
commit eafe563cfa
13 changed files with 230 additions and 158 deletions
+11 -15
View File
@@ -14,6 +14,7 @@ The file format has two distinct types of content:
from pathlib import Path
from typing import Optional
from collections import OrderedDict
import frontmatter
from frontmatter import Post
@@ -99,21 +100,16 @@ class MarkdownProcessor:
if current_checksum != expected_checksum:
raise DirtyFileError(f"File {path} has been modified")
# Convert frontmatter to dict, dropping None values
# Convert frontmatter to dict
frontmatter_dict = OrderedDict()
frontmatter_dict["title"] = markdown.frontmatter.title
frontmatter_dict["type"] = markdown.frontmatter.type
frontmatter_dict["permalink"] = markdown.frontmatter.permalink
metadata = markdown.frontmatter.metadata or {}
frontmatter_dict = {
"type": markdown.frontmatter.type,
"permalink": markdown.frontmatter.permalink,
"created": markdown.frontmatter.created.isoformat()
if markdown.created
else None,
"modified": markdown.frontmatter.modified.isoformat()
if markdown.modified
else None,
**metadata,
}
frontmatter_dict = {k: v for k, v in frontmatter_dict.items() if v is not None}
for k,v in metadata.items():
frontmatter_dict[k] = v
# Start with user content (or minimal title for new files)
content = markdown.content or f"# {markdown.frontmatter.title}\n"
@@ -131,7 +127,7 @@ class MarkdownProcessor:
# Create Post object for frontmatter
post = Post(content, **frontmatter_dict)
final_content = frontmatter.dumps(post)
final_content = frontmatter.dumps(post, sort_keys=False)
logger.debug(f"writing file {path} with content:\n{final_content}")
+15 -12
View File
@@ -29,11 +29,9 @@ def entity_model_to_markdown(entity: Entity, content: Optional[str] = None) -> E
:rtype: EntityMarkdown
"""
metadata = entity.entity_metadata or {}
metadata["permalink"] = entity.permalink
metadata["type"] = entity.entity_type or "note"
metadata["title"] = entity.title
metadata["created"] = entity.created_at
metadata["modified"] = entity.updated_at
metadata["permalink"] = entity.permalink
# convert model to markdown
entity_observations = [
@@ -80,6 +78,8 @@ def entity_model_to_markdown(entity: Entity, content: Optional[str] = None) -> E
content=content,
observations=observations,
relations=relations,
created = entity.created_at,
modified = entity.updated_at,
)
@@ -102,7 +102,7 @@ def entity_model_from_markdown(file_path: Path, markdown: EntityMarkdown, entity
permalink = markdown.frontmatter.permalink or generate_permalink(file_path)
model = entity or Entity()
model.title=markdown.frontmatter.title or file_path.stem
model.title=markdown.frontmatter.title
model.entity_type=markdown.frontmatter.type
model.permalink=permalink
model.file_path=str(file_path)
@@ -128,14 +128,17 @@ async def schema_to_markdown(schema):
: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)
frontmatter_metadata = schema.entity_metadata or {}
# remove from map so we can define ordering in frontmatter
if "type" in frontmatter_metadata:
del frontmatter_metadata["type"]
if "title" in frontmatter_metadata:
del frontmatter_metadata["title"]
if "permalink" in frontmatter_metadata:
del frontmatter_metadata["permalink"]
post = Post(content, title=schema.title, type=schema.entity_type, permalink=schema.permalink, **frontmatter_metadata)
return post
+14 -9
View File
@@ -19,15 +19,17 @@ from basic_memory.mcp.tools.utils import call_get, call_put, call_delete
description="Create or update a markdown note. Returns the permalink for referencing.",
)
async def write_note(
file_path: str,
title: str,
content: str,
folder: str,
tags: Optional[List[str]] = None,
) -> str:
"""Write a markdown note to the knowledge base.
Args:
file_path: The note's title
title: The title of the note
content: Markdown content for the note
folder: the folder where the file should be saved
tags: Optional list of tags to categorize the note
Returns:
@@ -36,23 +38,26 @@ async def write_note(
Examples:
# Create a simple note
write_note(
file_path="Meeting Notes: Project Planning",
file_path="Meeting Notes: Project Planning.md",
content="# Key Points\\n\\n- Discussed timeline\\n- Set priorities"
folder="notes"
)
# Create note with tags
write_note(
file_path="Security Review",
file_path="Security Review.md",
content="# Findings\\n\\n1. Updated auth flow\\n2. Added rate limiting",
tags=["security", "development"]
folder="security",
tags=["security", "development"]
)
"""
logger.info(f"Writing note: {file_path}")
logger.info(f"Writing note folder:'{folder}' title: '{title}'")
# Create the entity request
metadata = {"tags": [f"#{tag}" for tag in tags]} if tags else None
entity = Entity(
file_path=file_path,
title=title,
folder=folder,
entity_type="note",
content_type="text/markdown",
content=content,
@@ -79,8 +84,8 @@ async def read_note(identifier: str) -> str:
The note's markdown content
Examples:
# Read by title
read_note("Meeting Notes: Project Planning")
# Read by file path
read_note("Meeting Notes: Project Planning.md")
# Read by permalink
read_note("notes/project-planning")
@@ -169,7 +169,7 @@ class SearchRepository:
LIMIT :limit
"""
logger.debug(f"Search {sql} params: {params}")
#logger.debug(f"Search {sql} params: {params}")
async with db.scoped_session(self.session_maker) as session:
result = await session.execute(text(sql), params)
rows = result.fetchall()
@@ -195,8 +195,8 @@ class SearchRepository:
for row in rows
]
for r in results:
logger.debug(f"Search result: type:{r.type} title: {r.title} permalink: {r.permalink} score: {r.score}")
#for r in results:
# logger.debug(f"Search result: type:{r.type} title: {r.title} permalink: {r.permalink} score: {r.score}")
return results
async def index_item(
+8 -2
View File
@@ -182,15 +182,21 @@ class Entity(BaseModel):
- Optional description for high-level overview
"""
file_path: str
entity_type: EntityType = "note"
title: str
content: Optional[str] = None
folder: str
entity_type: EntityType = "note"
entity_metadata: Optional[Dict] = Field(default=None, description="Optional metadata")
content_type: ContentType = Field(
description="MIME type of the content (e.g. text/markdown, image/jpeg)",
examples=["text/markdown", "image/jpeg"], default="text/markdown"
)
@property
def file_path(self):
"""Get the file path for this entity based on its permalink."""
return f"{self.folder}/{self.title}.md" if self.folder else f"{self.title}.md"
@property
def permalink(self) -> PathId:
"""Get the path ID in format {snake_case_title}."""
+1
View File
@@ -117,6 +117,7 @@ class EntityResponse(SQLAlchemyModel):
"""
permalink: PathId
title: str
file_path: str
entity_type: EntityType
entity_metadata: Optional[Dict] = None
+1 -1
View File
@@ -72,7 +72,7 @@ class EntityService(BaseService[EntityModel]):
post = await schema_to_markdown(schema)
# write file
final_content = frontmatter.dumps(post)
final_content = frontmatter.dumps(post, sort_keys=False)
checksum = await self.file_service.write_file(file_path, final_content)
# parse entity from file