change entity.path_id to entity.permalink

This commit is contained in:
phernandez
2025-01-12 16:47:21 -06:00
parent 1f374e0133
commit c5d21276de
52 changed files with 714 additions and 687 deletions
@@ -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()