mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
change entity.path_id to entity.permalink
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
from typing import List, Optional, Sequence
|
||||
|
||||
from sqlalchemy import select, or_, asc, desc
|
||||
from sqlalchemy import select, or_, asc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
@@ -18,16 +18,16 @@ class EntityRepository(Repository[Entity]):
|
||||
"""Initialize with session maker."""
|
||||
super().__init__(session_maker, Entity)
|
||||
|
||||
async def get_by_path_id(self, path_id: str) -> Optional[Entity]:
|
||||
"""Get entity by path_id."""
|
||||
query = self.select().where(Entity.path_id == path_id).options(*self.get_load_options())
|
||||
async def get_by_permalink(self, permalink: str) -> Optional[Entity]:
|
||||
"""Get entity by permalink."""
|
||||
query = self.select().where(Entity.permalink == permalink).options(*self.get_load_options())
|
||||
return await self.find_one(query)
|
||||
|
||||
async def list_entities(
|
||||
self,
|
||||
entity_type: Optional[str] = None,
|
||||
sort_by: Optional[str] = "updated_at",
|
||||
include_related: bool = False,
|
||||
self,
|
||||
entity_type: Optional[str] = None,
|
||||
sort_by: Optional[str] = "updated_at",
|
||||
include_related: bool = False,
|
||||
) -> Sequence[Entity]:
|
||||
"""List all entities, optionally filtered by type and sorted."""
|
||||
query = self.select()
|
||||
@@ -44,8 +44,12 @@ class EntityRepository(Repository[Entity]):
|
||||
query = query.where(
|
||||
or_(
|
||||
Entity.entity_type == entity_type,
|
||||
Entity.outgoing_relations.any(Relation.to_entity.has(entity_type=entity_type)),
|
||||
Entity.incoming_relations.any(Relation.from_entity.has(entity_type=entity_type))
|
||||
Entity.outgoing_relations.any(
|
||||
Relation.to_entity.has(entity_type=entity_type)
|
||||
),
|
||||
Entity.incoming_relations.any(
|
||||
Relation.from_entity.has(entity_type=entity_type)
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -111,28 +115,30 @@ class EntityRepository(Repository[Entity]):
|
||||
selectinload(Entity.incoming_relations).selectinload(Relation.to_entity),
|
||||
]
|
||||
|
||||
async def find_by_path_ids(self, path_ids: List[str]) -> Sequence[Entity]:
|
||||
"""Find multiple entities by their path_id."""
|
||||
async def find_by_permalinks(self, permalinks: List[str]) -> Sequence[Entity]:
|
||||
"""Find multiple entities by their permalink."""
|
||||
|
||||
# Handle empty input explicitly
|
||||
if not path_ids:
|
||||
if not permalinks:
|
||||
return []
|
||||
|
||||
# Use existing select pattern
|
||||
query = self.select().options(*self.get_load_options()).where(Entity.path_id.in_(path_ids))
|
||||
query = (
|
||||
self.select().options(*self.get_load_options()).where(Entity.permalink.in_(permalinks))
|
||||
)
|
||||
|
||||
result = await self.execute_query(query)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def delete_by_path_ids(self, path_ids: List[str]) -> int:
|
||||
"""Delete multiple entities by path_id."""
|
||||
async def delete_by_permalinks(self, permalinks: List[str]) -> int:
|
||||
"""Delete multiple entities by permalink."""
|
||||
|
||||
# Handle empty input explicitly
|
||||
if not path_ids:
|
||||
if not permalinks:
|
||||
return 0
|
||||
|
||||
# Find matching entities
|
||||
entities = await self.find_by_path_ids(path_ids)
|
||||
entities = await self.find_by_permalinks(permalinks)
|
||||
if not entities:
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Repository for managing Relation objects."""
|
||||
|
||||
from sqlalchemy import and_, delete
|
||||
from typing import Sequence, List, Optional
|
||||
|
||||
@@ -18,7 +19,9 @@ class RelationRepository(Repository[Relation]):
|
||||
def __init__(self, session_maker: async_sessionmaker):
|
||||
super().__init__(session_maker, Relation)
|
||||
|
||||
async def find_relation(self, from_path_id: str, to_path_id: str, relation_type: str) -> Optional[Relation]:
|
||||
async def find_relation(
|
||||
self, from_permalink: str, to_permalink: str, relation_type: str
|
||||
) -> Optional[Relation]:
|
||||
"""Find a relation by its from and to path IDs."""
|
||||
from_entity = aliased(Entity)
|
||||
to_entity = aliased(Entity)
|
||||
@@ -29,9 +32,9 @@ class RelationRepository(Repository[Relation]):
|
||||
.join(to_entity, Relation.to_id == to_entity.id)
|
||||
.where(
|
||||
and_(
|
||||
from_entity.path_id == from_path_id,
|
||||
to_entity.path_id == to_path_id,
|
||||
Relation.relation_type == relation_type
|
||||
from_entity.permalink == from_permalink,
|
||||
to_entity.permalink == to_permalink,
|
||||
Relation.relation_type == relation_type,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -62,9 +65,7 @@ class RelationRepository(Repository[Relation]):
|
||||
as these are the ones owned by this entity's markdown file.
|
||||
"""
|
||||
async with db.scoped_session(self.session_maker) as session:
|
||||
await session.execute(
|
||||
delete(Relation).where(Relation.from_id == entity_id)
|
||||
)
|
||||
await session.execute(delete(Relation).where(Relation.from_id == entity_id))
|
||||
|
||||
def get_load_options(self) -> List[LoaderOption]:
|
||||
return [selectinload(Relation.from_entity), selectinload(Relation.to_entity)]
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
|
||||
import json
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
from loguru import logger
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
|
||||
from basic_memory import db
|
||||
from basic_memory.repository.repository import Repository
|
||||
from basic_memory.schemas.search import SearchQuery, SearchResult, SearchItemType
|
||||
from basic_memory.models.search import CREATE_SEARCH_INDEX
|
||||
|
||||
|
||||
class SearchRepository():
|
||||
class SearchRepository:
|
||||
"""Repository for search index operations."""
|
||||
|
||||
def __init__(self, session_maker: async_sessionmaker[AsyncSession]):
|
||||
@@ -27,9 +25,7 @@ class SearchRepository():
|
||||
await session.commit()
|
||||
|
||||
async def search(
|
||||
self,
|
||||
query: SearchQuery,
|
||||
context: Optional[List[str]] = None
|
||||
self, query: SearchQuery, context: Optional[List[str]] = None
|
||||
) -> List[SearchResult]:
|
||||
"""Search across all indexed content."""
|
||||
conditions = []
|
||||
@@ -48,23 +44,19 @@ class SearchRepository():
|
||||
# Handle entity type filter
|
||||
if query.entity_types:
|
||||
entity_type_list = ", ".join(f"'{t}'" for t in query.entity_types)
|
||||
conditions.append(
|
||||
f"json_extract(metadata, '$.entity_type') IN ({entity_type_list})"
|
||||
)
|
||||
conditions.append(f"json_extract(metadata, '$.entity_type') IN ({entity_type_list})")
|
||||
|
||||
# Handle date filter
|
||||
if query.after_date:
|
||||
params["after_date"] = query.after_date
|
||||
conditions.append(
|
||||
"json_extract(metadata, '$.created_at') > :after_date"
|
||||
)
|
||||
conditions.append("json_extract(metadata, '$.created_at') > :after_date")
|
||||
|
||||
# Build WHERE clause
|
||||
where_clause = " AND ".join(conditions) if conditions else "1=1"
|
||||
|
||||
|
||||
sql = f"""
|
||||
SELECT
|
||||
path_id,
|
||||
permalink,
|
||||
file_path,
|
||||
type,
|
||||
metadata,
|
||||
@@ -80,11 +72,11 @@ class SearchRepository():
|
||||
|
||||
return [
|
||||
SearchResult(
|
||||
path_id=row.path_id,
|
||||
permalink=row.permalink,
|
||||
file_path=row.file_path,
|
||||
type=SearchItemType(row.type), # Convert string to enum
|
||||
score=row.score,
|
||||
metadata=json.loads(row.metadata)
|
||||
metadata=json.loads(row.metadata),
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
@@ -92,44 +84,44 @@ class SearchRepository():
|
||||
async def index_item(
|
||||
self,
|
||||
content: str,
|
||||
path_id: str,
|
||||
permalink: str,
|
||||
file_path: str,
|
||||
type: SearchItemType, # Now accepts enum
|
||||
metadata: dict
|
||||
metadata: dict,
|
||||
):
|
||||
"""Index or update a single item."""
|
||||
async with db.scoped_session(self.session_maker) as session:
|
||||
# Delete existing record if any
|
||||
await session.execute(
|
||||
text("DELETE FROM search_index WHERE path_id = :path_id"),
|
||||
{"path_id": path_id}
|
||||
text("DELETE FROM search_index WHERE permalink = :permalink"),
|
||||
{"permalink": permalink},
|
||||
)
|
||||
|
||||
# Insert new record
|
||||
await session.execute(
|
||||
text("""
|
||||
INSERT INTO search_index (
|
||||
content, path_id, file_path, type, metadata
|
||||
content, permalink, file_path, type, metadata
|
||||
) VALUES (
|
||||
:content, :path_id, :file_path, :type, :metadata
|
||||
:content, :permalink, :file_path, :type, :metadata
|
||||
)
|
||||
"""),
|
||||
{
|
||||
"content": content,
|
||||
"path_id": path_id,
|
||||
"permalink": permalink,
|
||||
"file_path": file_path,
|
||||
"type": type.value, # Store the string value
|
||||
"metadata": json.dumps(metadata)
|
||||
}
|
||||
"metadata": json.dumps(metadata),
|
||||
},
|
||||
)
|
||||
logger.debug(f"indexed {path_id}")
|
||||
logger.debug(f"indexed {permalink}")
|
||||
await session.commit()
|
||||
|
||||
async def delete_by_path_id(self, path_id: str):
|
||||
async def delete_by_permalink(self, permalink: str):
|
||||
"""Delete an item from the search index."""
|
||||
async with db.scoped_session(self.session_maker) as session:
|
||||
await session.execute(
|
||||
text("DELETE FROM search_index WHERE path_id = :path_id"),
|
||||
{"path_id": path_id}
|
||||
text("DELETE FROM search_index WHERE permalink = :permalink"),
|
||||
{"permalink": permalink},
|
||||
)
|
||||
await session.commit()
|
||||
await session.commit()
|
||||
|
||||
Reference in New Issue
Block a user