mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
more validation fixes
This commit is contained in:
@@ -9,14 +9,13 @@ from basic_memory.deps import (
|
||||
)
|
||||
from basic_memory.schemas import (
|
||||
CreateEntityRequest,
|
||||
CreateEntityResponse,
|
||||
EntityListResponse,
|
||||
SearchNodesRequest,
|
||||
SearchNodesResponse,
|
||||
CreateRelationsRequest,
|
||||
EntityResponse,
|
||||
AddObservationsRequest,
|
||||
OpenNodesRequest,
|
||||
OpenNodesResponse,
|
||||
DeleteEntitiesResponse,
|
||||
DeleteObservationsRequest,
|
||||
DeleteRelationsRequest,
|
||||
@@ -30,24 +29,24 @@ router = APIRouter(prefix="/knowledge", tags=["knowledge"])
|
||||
## Create endpoints
|
||||
|
||||
|
||||
@router.post("/entities", response_model=CreateEntityResponse)
|
||||
@router.post("/entities", response_model=EntityListResponse)
|
||||
async def create_entities(
|
||||
data: CreateEntityRequest, knowledge_service: KnowledgeServiceDep
|
||||
) -> CreateEntityResponse:
|
||||
) -> EntityListResponse:
|
||||
"""Create new entities in the knowledge graph."""
|
||||
entities = await knowledge_service.create_entities(data.entities)
|
||||
return CreateEntityResponse(
|
||||
return EntityListResponse(
|
||||
entities=[EntityResponse.model_validate(entity) for entity in entities]
|
||||
)
|
||||
|
||||
|
||||
@router.post("/relations", response_model=CreateEntityResponse)
|
||||
@router.post("/relations", response_model=EntityListResponse)
|
||||
async def create_relations(
|
||||
data: CreateRelationsRequest, knowledge_service: KnowledgeServiceDep
|
||||
) -> CreateEntityResponse:
|
||||
) -> EntityListResponse:
|
||||
"""Create relations between entities."""
|
||||
updated_entities = await knowledge_service.create_relations(data.relations)
|
||||
return CreateEntityResponse(
|
||||
return EntityListResponse(
|
||||
entities=[EntityResponse.model_validate(entity) for entity in updated_entities]
|
||||
)
|
||||
|
||||
@@ -91,11 +90,11 @@ async def search_nodes(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/nodes", response_model=OpenNodesResponse)
|
||||
async def open_nodes(data: OpenNodesRequest, entity_service: EntityServiceDep) -> OpenNodesResponse:
|
||||
@router.post("/nodes", response_model=EntityListResponse)
|
||||
async def open_nodes(data: OpenNodesRequest, entity_service: EntityServiceDep) -> EntityListResponse:
|
||||
"""Open specific nodes by their names."""
|
||||
entities = await entity_service.open_nodes(data.entity_ids)
|
||||
return OpenNodesResponse(
|
||||
return EntityListResponse(
|
||||
entities=[EntityResponse.model_validate(entity) for entity in entities]
|
||||
)
|
||||
|
||||
@@ -122,12 +121,12 @@ async def delete_observations(
|
||||
return EntityResponse.model_validate(updated_entity)
|
||||
|
||||
|
||||
@router.post("/relations/delete", response_model=CreateEntityResponse)
|
||||
@router.post("/relations/delete", response_model=EntityListResponse)
|
||||
async def delete_relations(
|
||||
data: DeleteRelationsRequest, knowledge_service: KnowledgeServiceDep
|
||||
) -> CreateEntityResponse:
|
||||
) -> EntityListResponse:
|
||||
"""Delete relations between entities."""
|
||||
updated_entities = await knowledge_service.delete_relations(data.relations)
|
||||
return CreateEntityResponse(
|
||||
return EntityListResponse(
|
||||
entities=[EntityResponse.model_validate(entity) for entity in updated_entities]
|
||||
)
|
||||
|
||||
@@ -36,9 +36,8 @@ from basic_memory.schemas.response import (
|
||||
ObservationResponse,
|
||||
RelationResponse,
|
||||
EntityResponse,
|
||||
CreateEntityResponse,
|
||||
EntityListResponse,
|
||||
SearchNodesResponse,
|
||||
OpenNodesResponse,
|
||||
DeleteEntitiesResponse,
|
||||
)
|
||||
|
||||
@@ -56,17 +55,13 @@ __all__ = [
|
||||
"SearchNodesRequest",
|
||||
"OpenNodesRequest",
|
||||
"CreateRelationsRequest",
|
||||
"DocumentCreateRequest",
|
||||
"DocumentUpdateRequest",
|
||||
# Responses
|
||||
"SQLAlchemyModel",
|
||||
"ObservationResponse",
|
||||
"ObservationsResponse",
|
||||
"RelationResponse",
|
||||
"EntityResponse",
|
||||
"CreateEntityResponse",
|
||||
"EntityListResponse",
|
||||
"SearchNodesResponse",
|
||||
"OpenNodesResponse",
|
||||
"DeleteEntitiesResponse",
|
||||
# Delete Operations
|
||||
"DeleteEntitiesRequest",
|
||||
|
||||
@@ -48,8 +48,10 @@ def to_snake_case(name: str) -> str:
|
||||
memory-service -> memory_service
|
||||
Memory_Service -> memory_service
|
||||
"""
|
||||
# Replace spaces and hyphens with underscores
|
||||
s1 = re.sub(r"[\s\-]", "_", name)
|
||||
name = name.strip()
|
||||
|
||||
# Replace spaces and hyphens and . with underscores
|
||||
s1 = re.sub(r"[\s\-\\.]", "_", name)
|
||||
|
||||
# Insert underscore between camelCase
|
||||
s2 = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1)
|
||||
@@ -58,7 +60,22 @@ def to_snake_case(name: str) -> str:
|
||||
return s2.lower()
|
||||
|
||||
|
||||
PathId = Annotated[str, BeforeValidator(to_snake_case)]
|
||||
def validate_path_format(path: str) -> str:
|
||||
"""Validate path has the correct format: type/name."""
|
||||
if not path or not isinstance(path, str):
|
||||
raise ValueError("Path must be a non-empty string")
|
||||
|
||||
parts = path.split('/')
|
||||
if len(parts) != 2:
|
||||
raise ValueError("Path must be in format: type/name")
|
||||
|
||||
type_part, name_part = parts
|
||||
if not type_part or not name_part:
|
||||
raise ValueError("Both type and name must be non-empty")
|
||||
|
||||
return path
|
||||
|
||||
PathId = Annotated[str, BeforeValidator(to_snake_case), BeforeValidator(validate_path_format)]
|
||||
"""Unique identifier in format '{path}/{normalized_name}'."""
|
||||
|
||||
Observation = Annotated[str, MinLen(1), MaxLen(1000)]
|
||||
|
||||
@@ -16,7 +16,7 @@ from typing import List, Optional, Dict, Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, AliasPath, AliasChoices
|
||||
|
||||
from basic_memory.schemas.base import Observation, Relation, PathId
|
||||
from basic_memory.schemas.base import Observation, Relation, PathId, Entity, EntityType
|
||||
|
||||
|
||||
class SQLAlchemyModel(BaseModel):
|
||||
@@ -38,7 +38,6 @@ class ObservationResponse(SQLAlchemyModel):
|
||||
|
||||
Example Response:
|
||||
{
|
||||
"id": 123,
|
||||
"content": "Implements SQLite storage for persistence"
|
||||
}
|
||||
"""
|
||||
@@ -112,19 +111,20 @@ class EntityResponse(SQLAlchemyModel):
|
||||
}
|
||||
"""
|
||||
|
||||
# Note this Class does not inherit form Entity because of the Entity.path_id semantics
|
||||
path_id: PathId
|
||||
name: str
|
||||
entity_type: str
|
||||
entity_type: EntityType
|
||||
description: Optional[str] = None
|
||||
observations: List[ObservationResponse] = []
|
||||
relations: List[RelationResponse] = []
|
||||
|
||||
|
||||
class CreateEntityResponse(SQLAlchemyModel):
|
||||
class EntityListResponse(SQLAlchemyModel):
|
||||
"""Response for create_entities operation.
|
||||
|
||||
Returns complete information about all created entities,
|
||||
including their generated IDs, initial observations,
|
||||
Returns complete information about entities returned from the service,
|
||||
including their path_ids, observations,
|
||||
and any established relations.
|
||||
|
||||
Example Response:
|
||||
@@ -137,7 +137,6 @@ class CreateEntityResponse(SQLAlchemyModel):
|
||||
"description": "Knowledge graph search",
|
||||
"observations": [
|
||||
{
|
||||
"id": 125,
|
||||
"content": "Implements full-text search"
|
||||
}
|
||||
],
|
||||
@@ -150,7 +149,6 @@ class CreateEntityResponse(SQLAlchemyModel):
|
||||
"description": "API Reference",
|
||||
"observations": [
|
||||
{
|
||||
"id": 126,
|
||||
"content": "Documents REST endpoints"
|
||||
}
|
||||
],
|
||||
@@ -192,29 +190,6 @@ class SearchNodesResponse(SQLAlchemyModel):
|
||||
query: str
|
||||
|
||||
|
||||
class OpenNodesResponse(SQLAlchemyModel):
|
||||
"""Response for retrieving specific entities.
|
||||
|
||||
Returns complete Entity objects for all found entities.
|
||||
Entities that don't exist are silently skipped.
|
||||
|
||||
Example Response:
|
||||
{
|
||||
"entities": [
|
||||
{
|
||||
"path_id": "component/memory_service",
|
||||
"name": "MemoryService",
|
||||
"entity_type": "component",
|
||||
"description": "Core service",
|
||||
"observations": [...],
|
||||
"relations": [...]
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
entities: List[EntityResponse]
|
||||
|
||||
|
||||
class DeleteEntitiesResponse(SQLAlchemyModel):
|
||||
"""Response indicating successful entity deletion.
|
||||
|
||||
Reference in New Issue
Block a user