mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
split up repository logic
This commit is contained in:
@@ -4,7 +4,9 @@ from pathlib import Path
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, AsyncEngine
|
||||
|
||||
from basic_memory.models import Entity as DbEntity, Observation as DbObservation, Relation as DbRelation
|
||||
from basic_memory.repository import EntityRepository, ObservationRepository, RelationRepository
|
||||
from basic_memory.repository.entity_repository import EntityRepository
|
||||
from basic_memory.repository.observation_repository import ObservationRepository
|
||||
from basic_memory.repository.relation_repository import RelationRepository
|
||||
from basic_memory.services import EntityService, ObservationService, RelationService, MemoryService
|
||||
from basic_memory.db import DatabaseType, get_database_url, init_database, get_session
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"""Repository implementations for basic-memory models."""
|
||||
from typing import Type, Optional, Any, Sequence
|
||||
from sqlalchemy import select, func, Select, Executable, inspect, Result, Column, and_
|
||||
"""Base repository implementation."""
|
||||
from typing import Type, Optional, Any, Sequence, TypeVar
|
||||
from sqlalchemy import select, func, Select, Executable, inspect, Result, Column
|
||||
from sqlalchemy.exc import NoResultFound
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Mapped, selectinload
|
||||
from sqlalchemy.orm import Mapped
|
||||
|
||||
from basic_memory.models import Entity, Observation, Relation, Base
|
||||
from basic_memory.models import Base
|
||||
|
||||
T = TypeVar('T', bound=Base)
|
||||
|
||||
class Repository[T: Base]:
|
||||
"""Base repository implementation with generic CRUD operations."""
|
||||
@@ -39,12 +40,7 @@ class Repository[T: Base]:
|
||||
return None
|
||||
|
||||
async def create(self, entity_data: dict, model: Type[Base] | None = None) -> T:
|
||||
"""Create a new entity in the database from the provided data.
|
||||
|
||||
Args:
|
||||
entity_data: Dictionary containing the data to insert
|
||||
model: Optional model class to use (defaults to self.Model)
|
||||
"""
|
||||
"""Create a new entity in the database from the provided data."""
|
||||
model = model or self.Model
|
||||
model_data = {k: v for k, v in entity_data.items() if k in self.valid_columns}
|
||||
entity = model(**model_data)
|
||||
@@ -96,78 +92,3 @@ class Repository[T: Base]:
|
||||
"""Execute a query and retrieve a single record."""
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().one_or_none()
|
||||
|
||||
|
||||
class EntityRepository(Repository[Entity]):
|
||||
"""Repository for Entity model with memory-specific operations."""
|
||||
|
||||
async def find_by_id(self, entity_id: str) -> Optional[Entity]:
|
||||
"""Find entity by ID with all relationships eagerly loaded."""
|
||||
try:
|
||||
# First load base entity
|
||||
result = await self.session.execute(
|
||||
select(Entity).filter(Entity.id == entity_id)
|
||||
)
|
||||
entity = result.scalars().one()
|
||||
|
||||
# Force refresh of all relationships
|
||||
await self.refresh(entity, ['observations', 'outgoing_relations', 'incoming_relations'])
|
||||
|
||||
return entity
|
||||
except NoResultFound:
|
||||
return None
|
||||
|
||||
async def find_by_name(self, name: str) -> Optional[Entity]:
|
||||
"""Find an entity by its unique name."""
|
||||
query = (
|
||||
select(Entity)
|
||||
.filter(Entity.name == name)
|
||||
)
|
||||
result = await self.session.execute(query)
|
||||
entity = result.scalars().one_or_none()
|
||||
if entity:
|
||||
await self.refresh(entity, ['observations', 'outgoing_relations', 'incoming_relations'])
|
||||
return entity
|
||||
|
||||
async def search_by_type(self, entity_type: str, skip: int = 0, limit: int = 100) -> Sequence[Entity]:
|
||||
"""Search for entities of a specific type."""
|
||||
query = select(Entity).filter(Entity.entity_type == entity_type).offset(skip).limit(limit)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
class ObservationRepository(Repository[Observation]):
|
||||
"""Repository for Observation model with memory-specific operations."""
|
||||
|
||||
async def find_by_entity(self, entity_id: str) -> Sequence[Observation]:
|
||||
"""Find all observations for a specific entity."""
|
||||
query = select(Observation).filter(Observation.entity_id == entity_id)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def find_by_context(self, context: str) -> Sequence[Observation]:
|
||||
"""Find observations with a specific context."""
|
||||
query = select(Observation).filter(Observation.context == context)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
class RelationRepository(Repository[Relation]):
|
||||
"""Repository for Relation model with memory-specific operations."""
|
||||
|
||||
async def find_by_entities(self, from_id: str, to_id: str) -> Sequence[Relation]:
|
||||
"""Find all relations between two entities."""
|
||||
query = select(Relation).filter(
|
||||
and_(
|
||||
Relation.from_id == from_id,
|
||||
Relation.to_id == to_id
|
||||
)
|
||||
)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def find_by_type(self, relation_type: str) -> Sequence[Relation]:
|
||||
"""Find all relations of a specific type."""
|
||||
query = select(Relation).filter(Relation.relation_type == relation_type)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Repository for managing Entity objects."""
|
||||
from typing import Optional, Sequence
|
||||
from sqlalchemy import select, or_
|
||||
from sqlalchemy.exc import NoResultFound
|
||||
|
||||
from basic_memory.models import Entity, Observation
|
||||
from basic_memory.repository import Repository
|
||||
|
||||
|
||||
class EntityRepository(Repository[Entity]):
|
||||
"""Repository for Entity model with memory-specific operations."""
|
||||
|
||||
def __init__(self, session):
|
||||
super().__init__(session, Entity)
|
||||
|
||||
async def find_by_id(self, entity_id: str) -> Optional[Entity]:
|
||||
"""Find entity by ID with all relationships eagerly loaded."""
|
||||
try:
|
||||
# First load base entity
|
||||
result = await self.session.execute(
|
||||
select(Entity).filter(Entity.id == entity_id)
|
||||
)
|
||||
entity = result.scalars().one()
|
||||
|
||||
# Force refresh of all relationships
|
||||
await self.refresh(entity, ['observations', 'outgoing_relations', 'incoming_relations'])
|
||||
|
||||
return entity
|
||||
except NoResultFound:
|
||||
return None
|
||||
|
||||
async def find_by_name(self, name: str) -> Optional[Entity]:
|
||||
"""Find an entity by its unique name."""
|
||||
query = (
|
||||
select(Entity)
|
||||
.filter(Entity.name == name)
|
||||
)
|
||||
result = await self.session.execute(query)
|
||||
entity = result.scalars().one_or_none()
|
||||
if entity:
|
||||
await self.refresh(entity, ['observations', 'outgoing_relations', 'incoming_relations'])
|
||||
return entity
|
||||
|
||||
async def search_by_type(self, entity_type: str, skip: int = 0, limit: int = 100) -> Sequence[Entity]:
|
||||
"""Search for entities of a specific type."""
|
||||
query = select(Entity).filter(Entity.entity_type == entity_type).offset(skip).limit(limit)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def search(self, query: str) -> Sequence[Entity]:
|
||||
"""Search entities using LIKE pattern matching."""
|
||||
stmt = select(Entity).distinct().where(
|
||||
or_(
|
||||
Entity.name.ilike(f"%{query}%"),
|
||||
Entity.entity_type.ilike(f"%{query}%"),
|
||||
Entity.observations.any(
|
||||
Observation.content.ilike(f"%{query}%")
|
||||
)
|
||||
)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return list(result.scalars())
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Repository for managing Observation objects."""
|
||||
from typing import Sequence
|
||||
from sqlalchemy import select
|
||||
|
||||
from basic_memory.models import Observation
|
||||
from basic_memory.repository import Repository
|
||||
|
||||
|
||||
class ObservationRepository(Repository[Observation]):
|
||||
"""Repository for Observation model with memory-specific operations."""
|
||||
|
||||
def __init__(self, session):
|
||||
super().__init__(session, Observation)
|
||||
|
||||
async def find_by_entity(self, entity_id: str) -> Sequence[Observation]:
|
||||
"""Find all observations for a specific entity."""
|
||||
query = select(Observation).filter(Observation.entity_id == entity_id)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def find_by_context(self, context: str) -> Sequence[Observation]:
|
||||
"""Find observations with a specific context."""
|
||||
query = select(Observation).filter(Observation.context == context)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Repository for managing Relation objects."""
|
||||
from typing import Sequence
|
||||
from sqlalchemy import select, and_
|
||||
|
||||
from basic_memory.models import Relation
|
||||
from basic_memory.repository import Repository
|
||||
|
||||
|
||||
class RelationRepository(Repository[Relation]):
|
||||
"""Repository for Relation model with memory-specific operations."""
|
||||
|
||||
def __init__(self, session):
|
||||
super().__init__(session, Relation)
|
||||
|
||||
async def find_by_entities(self, from_id: str, to_id: str) -> Sequence[Relation]:
|
||||
"""Find all relations between two entities."""
|
||||
query = select(Relation).filter(
|
||||
and_(
|
||||
Relation.from_id == from_id,
|
||||
Relation.to_id == to_id
|
||||
)
|
||||
)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def find_by_type(self, relation_type: str) -> Sequence[Relation]:
|
||||
"""Find all relations of a specific type."""
|
||||
query = select(Relation).filter(Relation.relation_type == relation_type)
|
||||
result = await self.execute_query(query)
|
||||
return result.scalars().all()
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Service for managing entities in the database."""
|
||||
from datetime import datetime, UTC
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from basic_memory.repository import EntityRepository
|
||||
from basic_memory.repository.entity_repository import EntityRepository
|
||||
from basic_memory.schemas import EntityIn, ObservationIn
|
||||
from basic_memory.models import Entity, Observation
|
||||
from basic_memory.fileio import EntityNotFoundError
|
||||
@@ -19,6 +20,10 @@ class EntityService:
|
||||
self.project_path = project_path
|
||||
self.entity_repo = entity_repo
|
||||
|
||||
async def search(self, query: str) -> List[Entity]:
|
||||
"""Search entities using LIKE pattern matching."""
|
||||
return await self.entity_repo.search(query)
|
||||
|
||||
async def create_entity(self, entity: EntityIn) -> Entity:
|
||||
"""Create a new entity in the database.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
from basic_memory.models import Entity, Observation
|
||||
from basic_memory.schemas import (
|
||||
ObservationsIn, ObservationsOut, ObservationOut, EntityIn, RelationIn, RelationOut
|
||||
ObservationsIn, EntityIn, RelationIn, RelationOut
|
||||
)
|
||||
from basic_memory.fileio import write_entity_file, read_entity_file, delete_entity_file
|
||||
from basic_memory.services import EntityService, RelationService, ObservationService
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import List
|
||||
from sqlalchemy import select, delete
|
||||
|
||||
from basic_memory.models import Observation
|
||||
from basic_memory.repository import ObservationRepository
|
||||
from basic_memory.repository.observation_repository import ObservationRepository
|
||||
from basic_memory.schemas import EntityIn, ObservationIn
|
||||
from . import DatabaseSyncError
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Dict, Any
|
||||
from sqlalchemy import delete
|
||||
|
||||
from basic_memory.models import Relation as DbRelation, Relation
|
||||
from basic_memory.repository import RelationRepository
|
||||
from basic_memory.repository.relation_repository import RelationRepository
|
||||
from basic_memory.schemas import EntityIn, RelationIn
|
||||
from . import ServiceError, DatabaseSyncError, RelationError
|
||||
|
||||
|
||||
Reference in New Issue
Block a user