mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
update_entity
This commit is contained in:
@@ -21,6 +21,7 @@ from basic_memory.schemas import (
|
||||
DeleteObservationsRequest,
|
||||
DeleteRelationsRequest,
|
||||
DeleteEntitiesRequest,
|
||||
UpdateEntityRequest,
|
||||
)
|
||||
from basic_memory.schemas.base import PathId
|
||||
from basic_memory.services.exceptions import EntityNotFoundError
|
||||
@@ -49,6 +50,31 @@ async def create_entities(
|
||||
)
|
||||
|
||||
|
||||
@router.put("/entities/{path_id:path}", response_model=EntityResponse)
|
||||
async def update_entity(
|
||||
path_id: PathId,
|
||||
data: UpdateEntityRequest,
|
||||
background_tasks: BackgroundTasks,
|
||||
knowledge_service: KnowledgeServiceDep,
|
||||
search_service = Depends(get_search_service)
|
||||
) -> EntityResponse:
|
||||
"""Update an existing entity and reindex it."""
|
||||
try:
|
||||
# Convert request to dict, excluding None values
|
||||
update_data = data.model_dump(exclude_none=True)
|
||||
|
||||
# Update the entity
|
||||
updated_entity = await knowledge_service.update_entity(path_id, update_data)
|
||||
|
||||
# Reindex since content changed
|
||||
await search_service.index_entity(updated_entity, background_tasks=background_tasks)
|
||||
|
||||
return EntityResponse.model_validate(updated_entity)
|
||||
|
||||
except EntityNotFoundError:
|
||||
raise HTTPException(status_code=404, detail=f"Entity with {path_id} not found")
|
||||
|
||||
|
||||
@router.post("/relations", response_model=EntityListResponse)
|
||||
async def create_relations(
|
||||
data: CreateRelationsRequest,
|
||||
@@ -100,7 +126,6 @@ async def get_entity(path_id: PathId, entity_service: EntityServiceDep) -> Entit
|
||||
raise HTTPException(status_code=404, detail=f"Entity with {path_id} not found")
|
||||
|
||||
|
||||
|
||||
@router.post("/nodes", response_model=EntityListResponse)
|
||||
async def open_nodes(data: OpenNodesRequest, entity_service: EntityServiceDep) -> EntityListResponse:
|
||||
"""Open specific nodes by their names."""
|
||||
|
||||
@@ -27,7 +27,7 @@ from basic_memory.schemas.request import (
|
||||
CreateEntityRequest,
|
||||
SearchNodesRequest,
|
||||
OpenNodesRequest,
|
||||
CreateRelationsRequest,
|
||||
CreateRelationsRequest, UpdateEntityRequest,
|
||||
)
|
||||
|
||||
# Response models
|
||||
@@ -61,6 +61,7 @@ __all__ = [
|
||||
"SearchNodesRequest",
|
||||
"OpenNodesRequest",
|
||||
"CreateRelationsRequest",
|
||||
"UpdateEntityRequest",
|
||||
# Responses
|
||||
"SQLAlchemyModel",
|
||||
"ObservationResponse",
|
||||
|
||||
@@ -251,6 +251,7 @@ class Entity(BaseModel):
|
||||
name: str
|
||||
entity_type: EntityType
|
||||
entity_metadata: Optional[Dict] = Field(default=None, description="Optional metadata")
|
||||
content: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
observations: List[Observation] = []
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from annotated_types import MaxLen, MinLen
|
||||
|
||||
from pydantic import BaseModel, StringConstraints
|
||||
|
||||
from basic_memory.schemas.base import Observation, Entity, Relation, PathId, ObservationCategory
|
||||
from basic_memory.schemas.base import Observation, Entity, Relation, PathId, ObservationCategory, EntityType
|
||||
|
||||
|
||||
class ObservationCreate(BaseModel):
|
||||
@@ -88,7 +88,15 @@ class CreateRelationsRequest(BaseModel):
|
||||
relations: List[Relation]
|
||||
|
||||
|
||||
## document
|
||||
## update
|
||||
|
||||
class UpdateEntityRequest(BaseModel):
|
||||
"""Request to update an existing entity."""
|
||||
name: Optional[str] = None
|
||||
entity_type: Optional[EntityType] = None
|
||||
description: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
entity_metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
DocumentPathId = Annotated[
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Entity operations for knowledge service."""
|
||||
|
||||
from typing import Sequence, List
|
||||
from datetime import datetime, UTC
|
||||
from typing import Sequence, List, Dict, Any, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -63,6 +64,72 @@ class EntityOperations:
|
||||
|
||||
return created
|
||||
|
||||
async def update_entity(
|
||||
self,
|
||||
path_id: str,
|
||||
content: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**update_fields: Any
|
||||
) -> EntityModel:
|
||||
"""Update an entity's content and metadata.
|
||||
|
||||
Args:
|
||||
path_id: Entity's path ID
|
||||
content: Optional new content
|
||||
metadata: Optional metadata updates
|
||||
**update_fields: Additional entity fields to update
|
||||
|
||||
Returns:
|
||||
Updated entity
|
||||
|
||||
Raises:
|
||||
EntityNotFoundError: If entity doesn't exist
|
||||
"""
|
||||
logger.debug(f"Updating entity with path_id: {path_id}")
|
||||
|
||||
# Get existing entity
|
||||
entity = await self.entity_service.get_by_path_id(path_id)
|
||||
if not entity:
|
||||
raise EntityNotFoundError(f"Entity not found: {path_id}")
|
||||
|
||||
try:
|
||||
# Build update data
|
||||
update_data = {}
|
||||
|
||||
# Add any direct field updates
|
||||
if update_fields:
|
||||
update_data.update(update_fields)
|
||||
|
||||
# Handle metadata update
|
||||
if metadata is not None:
|
||||
# Update existing metadata
|
||||
new_metadata = dict(entity.entity_metadata or {})
|
||||
new_metadata.update(metadata)
|
||||
update_data["entity_metadata"] = new_metadata
|
||||
|
||||
# Update entity in database if we have changes
|
||||
if update_data:
|
||||
entity = await self.entity_service.update_entity(
|
||||
entity.path_id, update_data
|
||||
)
|
||||
|
||||
# Always write file if we have any updates
|
||||
if update_data or content is not None:
|
||||
_, checksum = await self.file_operations.write_entity_file(
|
||||
entity=entity,
|
||||
content=content
|
||||
)
|
||||
# Update checksum in DB
|
||||
entity = await self.entity_service.update_entity(
|
||||
entity.path_id, {"checksum": checksum}
|
||||
)
|
||||
|
||||
return entity
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update entity: {e}")
|
||||
raise
|
||||
|
||||
async def delete_entity(self, path_id: str) -> bool:
|
||||
"""Delete entity and its file."""
|
||||
logger.debug(f"Deleting entity: {path_id}")
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""Main knowledge service implementation."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Sequence, Tuple
|
||||
from typing import List, Sequence, Tuple, Dict, Any, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from basic_memory.markdown.knowledge_writer import KnowledgeWriter
|
||||
from basic_memory.markdown.note_writer import NoteWriter
|
||||
from basic_memory.models import Entity as EntityModel
|
||||
from basic_memory.schemas import Entity as EntitySchema
|
||||
from basic_memory.schemas import Relation as RelationSchema
|
||||
@@ -19,7 +19,6 @@ from .file_operations import FileOperations
|
||||
from .entity_operations import EntityOperations
|
||||
from .relation_operations import RelationOperations
|
||||
from .observation_operations import ObservationOperations
|
||||
from ...markdown.note_writer import NoteWriter
|
||||
|
||||
|
||||
class KnowledgeService:
|
||||
@@ -31,7 +30,7 @@ class KnowledgeService:
|
||||
- Entity CRUD operations
|
||||
- Relations between entities
|
||||
- Observations about entities
|
||||
|
||||
|
||||
Acts as the main coordinator for all knowledge operations, ensuring
|
||||
consistency between database and filesystem.
|
||||
"""
|
||||
@@ -46,39 +45,37 @@ class KnowledgeService:
|
||||
note_writer: NoteWriter,
|
||||
base_path: Path,
|
||||
):
|
||||
|
||||
self.base_path = base_path
|
||||
|
||||
|
||||
# Initialize operations in dependency order
|
||||
self.file_ops = FileOperations(
|
||||
entity_service=entity_service,
|
||||
file_service=file_service,
|
||||
knowledge_writer=knowledge_writer,
|
||||
note_writer=note_writer,
|
||||
base_path=base_path
|
||||
base_path=base_path,
|
||||
)
|
||||
|
||||
self.entity_ops = EntityOperations(
|
||||
entity_service=entity_service,
|
||||
file_operations=self.file_ops
|
||||
entity_service=entity_service, file_operations=self.file_ops
|
||||
)
|
||||
|
||||
self.relation_ops = RelationOperations(
|
||||
relation_service=relation_service,
|
||||
entity_service=entity_service,
|
||||
file_operations=self.file_ops
|
||||
file_operations=self.file_ops,
|
||||
)
|
||||
|
||||
self.observation_ops = ObservationOperations(
|
||||
observation_service=observation_service,
|
||||
entity_service=entity_service,
|
||||
file_operations=self.file_ops
|
||||
file_operations=self.file_ops,
|
||||
)
|
||||
|
||||
# Entity operations
|
||||
async def get_entity_by_path_id(self, path_id: str) -> EntityModel:
|
||||
return await self.entity_ops.get_by_path_id(path_id)
|
||||
|
||||
|
||||
async def create_entity(self, entity: EntitySchema) -> EntityModel:
|
||||
"""Create a new entity."""
|
||||
return await self.entity_ops.create_entity(entity)
|
||||
@@ -87,6 +84,28 @@ class KnowledgeService:
|
||||
"""Create multiple entities."""
|
||||
return await self.entity_ops.create_entities(entities)
|
||||
|
||||
async def update_entity(
|
||||
self,
|
||||
path_id: str,
|
||||
content: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**update_fields: Any,
|
||||
) -> EntityModel:
|
||||
"""Update an entity's content and metadata.
|
||||
|
||||
Args:
|
||||
path_id: Entity's path ID
|
||||
content: Optional new content
|
||||
metadata: Optional metadata updates
|
||||
**update_fields: Additional entity fields to update
|
||||
|
||||
Returns:
|
||||
Updated entity
|
||||
"""
|
||||
return await self.entity_ops.update_entity(
|
||||
path_id=path_id, content=content, metadata=metadata, **update_fields
|
||||
)
|
||||
|
||||
async def delete_entity(self, path_id: str) -> bool:
|
||||
"""Delete an entity and its file."""
|
||||
return await self.entity_ops.delete_entity(path_id)
|
||||
@@ -114,19 +133,12 @@ class KnowledgeService:
|
||||
|
||||
# Observation operations
|
||||
async def add_observations(
|
||||
self,
|
||||
path_id: str,
|
||||
observations: List[ObservationCreate],
|
||||
context: str | None = None
|
||||
self, path_id: str, observations: List[ObservationCreate], context: str | None = None
|
||||
) -> EntityModel:
|
||||
"""Add observations to an entity."""
|
||||
return await self.observation_ops.add_observations(path_id, observations, context)
|
||||
|
||||
async def delete_observations(
|
||||
self,
|
||||
path_id: str,
|
||||
observations: List[str]
|
||||
) -> EntityModel:
|
||||
async def delete_observations(self, path_id: str, observations: List[str]) -> EntityModel:
|
||||
"""Delete observations from an entity."""
|
||||
return await self.observation_ops.delete_observations(path_id, observations)
|
||||
|
||||
@@ -138,4 +150,4 @@ class KnowledgeService:
|
||||
async def write_entity_file(self, entity: EntityModel) -> Path:
|
||||
"""Write entity to filesystem."""
|
||||
path, _ = await self.file_ops.write_entity_file(entity)
|
||||
return path
|
||||
return path
|
||||
|
||||
Reference in New Issue
Block a user