mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
add metadata and errors to /documents api
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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}')"
|
||||
|
||||
Reference in New Issue
Block a user