add metadata and errors to /documents api

This commit is contained in:
phernandez
2024-12-21 19:56:54 -06:00
parent c170aed855
commit ddf2d2fd0e
2 changed files with 44 additions and 70 deletions
+22 -20
View File
@@ -16,19 +16,26 @@ from basic_memory.services.document_service import (
router = APIRouter(prefix="/documents", tags=["documents"])
@router.post("/", response_model=DocumentResponse)
@router.post("/", response_model=DocumentResponse, status_code=201)
async def create_document(
doc: DocumentCreate,
service: DocumentServiceDep,
) -> DocumentResponse:
"""Create a new document."""
"""Create a new document.
The document will be created with appropriate frontmatter including:
- Generated ID
- Creation timestamp
- Last modified timestamp
- Any provided metadata
"""
try:
document = await service.create_document(
path=doc.path,
content=doc.content,
metadata=doc.metadata,
)
return document
return DocumentResponse.from_orm(document)
except DocumentWriteError as e:
raise HTTPException(status_code=400, detail=str(e))
@@ -38,7 +45,8 @@ async def list_documents(
service: DocumentServiceDep,
) -> List[DocumentResponse]:
"""List all documents."""
return await service.list_documents()
documents = await service.list_documents()
return [DocumentResponse.from_orm(doc) for doc in documents]
@router.get("/{path:path}", response_model=DocumentResponse)
@@ -49,12 +57,11 @@ async def get_document(
"""Get a document by path."""
try:
document, content = await service.read_document(path)
# Attach content to response
response = DocumentResponse.from_orm(document)
response.content = content # type: ignore
response.content = content
return response
except DocumentNotFoundError:
raise HTTPException(status_code=404, detail="Document not found")
raise HTTPException(status_code=404, detail=f"Document not found: {path}")
except DocumentWriteError as e:
raise HTTPException(status_code=400, detail=str(e))
@@ -72,9 +79,9 @@ async def update_document(
content=doc.content,
metadata=doc.metadata,
)
return document
return DocumentResponse.model_validate(document)
except DocumentNotFoundError:
raise HTTPException(status_code=404, detail="Document not found")
raise HTTPException(status_code=404, detail=f"Document not found: {path}")
except DocumentWriteError as e:
raise HTTPException(status_code=400, detail=str(e))
@@ -85,17 +92,12 @@ async def patch_document(
patch: DocumentPatch,
service: DocumentServiceDep,
) -> DocumentResponse:
"""
Partially update a document.
TODO: Implement partial content updates to minimize data transfer.
For now, this is stubbed to require full content on update.
"""
# For now, require full content updates
"""Partially update a document."""
# Require full content updates for now
if patch.content is None:
raise HTTPException(
status_code=400,
detail="Partial content updates not yet implemented. Please provide full content.",
detail=("Partial content updates not yet implemented. " "Please provide full content."),
)
try:
@@ -104,9 +106,9 @@ async def patch_document(
content=patch.content,
metadata=patch.metadata,
)
return document
return DocumentResponse.from_orm(document)
except DocumentNotFoundError:
raise HTTPException(status_code=404, detail="Document not found")
raise HTTPException(status_code=404, detail=f"Document not found: {path}")
except DocumentWriteError as e:
raise HTTPException(status_code=400, detail=str(e))
@@ -120,6 +122,6 @@ async def delete_document(
try:
await service.delete_document(path)
except DocumentNotFoundError:
raise HTTPException(status_code=404, detail="Document not found")
raise HTTPException(status_code=404, detail=f"Document not found: {path}")
except DocumentWriteError as e:
raise HTTPException(status_code=400, detail=str(e))
+22 -50
View File
@@ -3,10 +3,7 @@
from datetime import datetime
from typing import List, Optional
from sqlalchemy import (
String, DateTime, ForeignKey, Text, Integer,
text, UniqueConstraint
)
from sqlalchemy import String, DateTime, ForeignKey, Text, Integer, text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from basic_memory.models.base import Base
@@ -24,53 +21,42 @@ class Entity(Base):
- A description (optional)
- A list of observations
"""
__tablename__ = "entity"
__table_args__ = (
UniqueConstraint("entity_type", "name", name="uix_entity_type_name"),
)
__table_args__ = (UniqueConstraint("entity_type", "name", name="uix_entity_type_name"),)
id: Mapped[str] = mapped_column(String, primary_key=True)
name: Mapped[str] = mapped_column(String)
entity_type: Mapped[str] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime,
server_default=text("CURRENT_TIMESTAMP")
)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
updated_at: Mapped[datetime] = mapped_column(
DateTime,
server_default=text("CURRENT_TIMESTAMP"),
onupdate=text("CURRENT_TIMESTAMP")
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP")
)
# Link to source document
doc_id: Mapped[Optional[int]] = mapped_column(
Integer,
ForeignKey("documents.id", ondelete="SET NULL"),
nullable=True
Integer, ForeignKey("documents.id", ondelete="SET NULL"), nullable=True
)
# Relationships
document: Mapped["Document"] = relationship(
"Document",
back_populates="entities"
document: Mapped["Document"] = relationship( # pyright: ignore [reportUndefinedVariable] # noqa: F821
"Document", back_populates="entities"
)
observations: Mapped[List["Observation"]] = relationship(
"Observation",
back_populates="entity",
cascade="all, delete-orphan"
"Observation", back_populates="entity", cascade="all, delete-orphan"
)
outgoing_relations: Mapped[List["Relation"]] = relationship(
"Relation",
foreign_keys="[Relation.from_id]",
back_populates="from_entity",
cascade="all, delete-orphan"
cascade="all, delete-orphan",
)
incoming_relations: Mapped[List["Relation"]] = relationship(
"Relation",
foreign_keys="[Relation.to_id]",
back_populates="to_entity",
cascade="all, delete-orphan"
cascade="all, delete-orphan",
)
@property
@@ -100,19 +86,15 @@ class Observation(Base):
- Can be added or removed independently
- Should be atomic (one fact per observation)
"""
__tablename__ = "observation"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
entity_id: Mapped[str] = mapped_column(
String,
ForeignKey("entity.id", ondelete="CASCADE"),
index=True
String, ForeignKey("entity.id", ondelete="CASCADE"), index=True
)
content: Mapped[str] = mapped_column(String)
created_at: Mapped[datetime] = mapped_column(
DateTime,
server_default=text("CURRENT_TIMESTAMP")
)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
context: Mapped[Optional[str]] = mapped_column(String, nullable=True)
# Relationships
@@ -126,40 +108,30 @@ class Observation(Base):
class Relation(Base):
"""
Relations define directed connections between entities.
They are always stored in active voice and describe how entities
They are always stored in active voice and describe how entities
interact or relate to each other.
"""
__tablename__ = "relation"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
from_id: Mapped[str] = mapped_column(
String,
ForeignKey("entity.id", ondelete="CASCADE"),
index=True
String, ForeignKey("entity.id", ondelete="CASCADE"), index=True
)
to_id: Mapped[str] = mapped_column(
String,
ForeignKey("entity.id", ondelete="CASCADE"),
index=True
String, ForeignKey("entity.id", ondelete="CASCADE"), index=True
)
relation_type: Mapped[str] = mapped_column(String)
created_at: Mapped[datetime] = mapped_column(
DateTime,
server_default=text("CURRENT_TIMESTAMP")
)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
context: Mapped[Optional[str]] = mapped_column(String, nullable=True)
# Relationships
from_entity: Mapped[Entity] = relationship(
"Entity",
foreign_keys=[from_id],
back_populates="outgoing_relations"
"Entity", foreign_keys=[from_id], back_populates="outgoing_relations"
)
to_entity: Mapped[Entity] = relationship(
"Entity",
foreign_keys=[to_id],
back_populates="incoming_relations"
"Entity", foreign_keys=[to_id], back_populates="incoming_relations"
)
def __repr__(self) -> str:
return f"Relation(id={self.id}, from='{self.from_id}', type='{self.relation_type}', to='{self.to_id}')"
return f"Relation(id={self.id}, from='{self.from_id}', type='{self.relation_type}', to='{self.to_id}')"