mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix: fastmcp deprecation warning (#150)
Signed-off-by: Drew Cain <groksrc@gmail.com> Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com> Co-authored-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
This commit is contained in:
@@ -78,6 +78,7 @@ def mcp(
|
||||
if transport == "stdio":
|
||||
mcp_server.run(
|
||||
transport=transport,
|
||||
log_level="INFO",
|
||||
)
|
||||
elif transport == "streamable-http" or transport == "sse":
|
||||
mcp_server.run(
|
||||
@@ -85,4 +86,5 @@ def mcp(
|
||||
host=host,
|
||||
port=port,
|
||||
path=path,
|
||||
log_level="INFO",
|
||||
)
|
||||
|
||||
@@ -95,11 +95,12 @@ async def get_or_create_db(
|
||||
|
||||
if _engine is None:
|
||||
_engine, _session_maker = _create_engine_and_session(db_path, db_type)
|
||||
|
||||
|
||||
# Run migrations automatically unless explicitly disabled
|
||||
if ensure_migrations:
|
||||
if app_config is None:
|
||||
from basic_memory.config import app_config as global_app_config
|
||||
|
||||
app_config = global_app_config
|
||||
await run_migrations(app_config, db_type)
|
||||
|
||||
@@ -170,12 +171,12 @@ async def run_migrations(
|
||||
): # pragma: no cover
|
||||
"""Run any pending alembic migrations."""
|
||||
global _migrations_completed
|
||||
|
||||
|
||||
# Skip if migrations already completed unless forced
|
||||
if _migrations_completed and not force:
|
||||
logger.debug("Migrations already completed in this session, skipping")
|
||||
return
|
||||
|
||||
|
||||
logger.info("Running database migrations...")
|
||||
try:
|
||||
# Get the absolute path to the alembic directory relative to this file
|
||||
@@ -206,7 +207,7 @@ async def run_migrations(
|
||||
# initialize the search Index schema
|
||||
# the project_id is not used for init_search_index, so we pass a dummy value
|
||||
await SearchRepository(session_maker, 1).init_search_index()
|
||||
|
||||
|
||||
# Mark migrations as completed
|
||||
_migrations_completed = True
|
||||
except Exception as e: # pragma: no cover
|
||||
|
||||
@@ -105,6 +105,5 @@ auth_settings, auth_provider = create_auth_config()
|
||||
# Create the shared server instance
|
||||
mcp = FastMCP(
|
||||
name="Basic Memory",
|
||||
log_level="DEBUG",
|
||||
auth=auth_provider,
|
||||
)
|
||||
|
||||
@@ -102,14 +102,14 @@ class EntityRepository(Repository[Entity]):
|
||||
|
||||
async def upsert_entity(self, entity: Entity) -> Entity:
|
||||
"""Insert or update entity using a hybrid approach.
|
||||
|
||||
|
||||
This method provides a cleaner alternative to the try/catch approach
|
||||
for handling permalink and file_path conflicts. It first tries direct
|
||||
for handling permalink and file_path conflicts. It first tries direct
|
||||
insertion, then handles conflicts intelligently.
|
||||
|
||||
|
||||
Args:
|
||||
entity: The entity to insert or update
|
||||
|
||||
|
||||
Returns:
|
||||
The inserted or updated entity
|
||||
"""
|
||||
@@ -117,29 +117,28 @@ class EntityRepository(Repository[Entity]):
|
||||
async with db.scoped_session(self.session_maker) as session:
|
||||
# Set project_id if applicable and not already set
|
||||
self._set_project_id_if_needed(entity)
|
||||
|
||||
|
||||
# Check for existing entity with same file_path first
|
||||
existing_by_path = await session.execute(
|
||||
select(Entity).where(
|
||||
Entity.file_path == entity.file_path,
|
||||
Entity.project_id == entity.project_id
|
||||
Entity.file_path == entity.file_path, Entity.project_id == entity.project_id
|
||||
)
|
||||
)
|
||||
existing_path_entity = existing_by_path.scalar_one_or_none()
|
||||
|
||||
|
||||
if existing_path_entity:
|
||||
# Update existing entity with same file path
|
||||
for key, value in {
|
||||
'title': entity.title,
|
||||
'entity_type': entity.entity_type,
|
||||
'entity_metadata': entity.entity_metadata,
|
||||
'content_type': entity.content_type,
|
||||
'permalink': entity.permalink,
|
||||
'checksum': entity.checksum,
|
||||
'updated_at': entity.updated_at,
|
||||
"title": entity.title,
|
||||
"entity_type": entity.entity_type,
|
||||
"entity_metadata": entity.entity_metadata,
|
||||
"content_type": entity.content_type,
|
||||
"permalink": entity.permalink,
|
||||
"checksum": entity.checksum,
|
||||
"updated_at": entity.updated_at,
|
||||
}.items():
|
||||
setattr(existing_path_entity, key, value)
|
||||
|
||||
|
||||
await session.flush()
|
||||
# Return with relationships loaded
|
||||
query = (
|
||||
@@ -150,15 +149,17 @@ class EntityRepository(Repository[Entity]):
|
||||
result = await session.execute(query)
|
||||
found = result.scalar_one_or_none()
|
||||
if not found: # pragma: no cover
|
||||
raise RuntimeError(f"Failed to retrieve entity after update: {entity.file_path}")
|
||||
raise RuntimeError(
|
||||
f"Failed to retrieve entity after update: {entity.file_path}"
|
||||
)
|
||||
return found
|
||||
|
||||
|
||||
# No existing entity with same file_path, try insert
|
||||
try:
|
||||
# Simple insert for new entity
|
||||
session.add(entity)
|
||||
await session.flush()
|
||||
|
||||
|
||||
# Return with relationships loaded
|
||||
query = (
|
||||
select(Entity)
|
||||
@@ -168,36 +169,37 @@ class EntityRepository(Repository[Entity]):
|
||||
result = await session.execute(query)
|
||||
found = result.scalar_one_or_none()
|
||||
if not found: # pragma: no cover
|
||||
raise RuntimeError(f"Failed to retrieve entity after insert: {entity.file_path}")
|
||||
raise RuntimeError(
|
||||
f"Failed to retrieve entity after insert: {entity.file_path}"
|
||||
)
|
||||
return found
|
||||
|
||||
|
||||
except IntegrityError:
|
||||
# Could be either file_path or permalink conflict
|
||||
await session.rollback()
|
||||
|
||||
|
||||
# Check if it's a file_path conflict (race condition)
|
||||
existing_by_path_check = await session.execute(
|
||||
select(Entity).where(
|
||||
Entity.file_path == entity.file_path,
|
||||
Entity.project_id == entity.project_id
|
||||
Entity.file_path == entity.file_path, Entity.project_id == entity.project_id
|
||||
)
|
||||
)
|
||||
race_condition_entity = existing_by_path_check.scalar_one_or_none()
|
||||
|
||||
|
||||
if race_condition_entity:
|
||||
# Race condition: file_path conflict detected after our initial check
|
||||
# Update the existing entity instead
|
||||
for key, value in {
|
||||
'title': entity.title,
|
||||
'entity_type': entity.entity_type,
|
||||
'entity_metadata': entity.entity_metadata,
|
||||
'content_type': entity.content_type,
|
||||
'permalink': entity.permalink,
|
||||
'checksum': entity.checksum,
|
||||
'updated_at': entity.updated_at,
|
||||
"title": entity.title,
|
||||
"entity_type": entity.entity_type,
|
||||
"entity_metadata": entity.entity_metadata,
|
||||
"content_type": entity.content_type,
|
||||
"permalink": entity.permalink,
|
||||
"checksum": entity.checksum,
|
||||
"updated_at": entity.updated_at,
|
||||
}.items():
|
||||
setattr(race_condition_entity, key, value)
|
||||
|
||||
|
||||
await session.flush()
|
||||
# Return the updated entity with relationships loaded
|
||||
query = (
|
||||
@@ -208,7 +210,9 @@ class EntityRepository(Repository[Entity]):
|
||||
result = await session.execute(query)
|
||||
found = result.scalar_one_or_none()
|
||||
if not found: # pragma: no cover
|
||||
raise RuntimeError(f"Failed to retrieve entity after race condition update: {entity.file_path}")
|
||||
raise RuntimeError(
|
||||
f"Failed to retrieve entity after race condition update: {entity.file_path}"
|
||||
)
|
||||
return found
|
||||
else:
|
||||
# Must be permalink conflict - generate unique permalink
|
||||
@@ -218,14 +222,13 @@ class EntityRepository(Repository[Entity]):
|
||||
"""Handle permalink conflicts by generating a unique permalink."""
|
||||
base_permalink = entity.permalink
|
||||
suffix = 1
|
||||
|
||||
|
||||
# Find a unique permalink
|
||||
while True:
|
||||
test_permalink = f"{base_permalink}-{suffix}"
|
||||
existing = await session.execute(
|
||||
select(Entity).where(
|
||||
Entity.permalink == test_permalink,
|
||||
Entity.project_id == entity.project_id
|
||||
Entity.permalink == test_permalink, Entity.project_id == entity.project_id
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none() is None:
|
||||
@@ -233,11 +236,11 @@ class EntityRepository(Repository[Entity]):
|
||||
entity.permalink = test_permalink
|
||||
break
|
||||
suffix += 1
|
||||
|
||||
|
||||
# Insert with unique permalink (no conflict possible now)
|
||||
session.add(entity)
|
||||
await session.flush()
|
||||
|
||||
|
||||
# Return the inserted entity with relationships loaded
|
||||
query = (
|
||||
select(Entity)
|
||||
|
||||
@@ -302,7 +302,7 @@ class EntityService(BaseService[EntityModel]):
|
||||
|
||||
Creates the entity with null checksum to indicate sync not complete.
|
||||
Relations will be added in second pass.
|
||||
|
||||
|
||||
Uses UPSERT approach to handle permalink/file_path conflicts cleanly.
|
||||
"""
|
||||
logger.debug(f"Creating entity: {markdown.frontmatter.title} file_path: {file_path}")
|
||||
@@ -310,7 +310,7 @@ class EntityService(BaseService[EntityModel]):
|
||||
|
||||
# Mark as incomplete because we still need to add relations
|
||||
model.checksum = None
|
||||
|
||||
|
||||
# Use UPSERT to handle conflicts cleanly
|
||||
try:
|
||||
return await self.repository.upsert_entity(model)
|
||||
|
||||
@@ -21,9 +21,9 @@ async def initialize_database(app_config: BasicMemoryConfig) -> None:
|
||||
|
||||
Args:
|
||||
app_config: The Basic Memory project configuration
|
||||
|
||||
|
||||
Note:
|
||||
Database migrations are now handled automatically when the database
|
||||
Database migrations are now handled automatically when the database
|
||||
connection is first established via get_or_create_db().
|
||||
"""
|
||||
# Trigger database initialization and migrations by getting the database connection
|
||||
@@ -50,7 +50,9 @@ async def reconcile_projects_with_config(app_config: BasicMemoryConfig):
|
||||
|
||||
# Get database session - migrations handled centrally
|
||||
_, session_maker = await db.get_or_create_db(
|
||||
db_path=app_config.database_path, db_type=db.DatabaseType.FILESYSTEM, ensure_migrations=False
|
||||
db_path=app_config.database_path,
|
||||
db_type=db.DatabaseType.FILESYSTEM,
|
||||
ensure_migrations=False,
|
||||
)
|
||||
project_repository = ProjectRepository(session_maker)
|
||||
|
||||
@@ -71,7 +73,9 @@ async def reconcile_projects_with_config(app_config: BasicMemoryConfig):
|
||||
async def migrate_legacy_projects(app_config: BasicMemoryConfig):
|
||||
# Get database session - migrations handled centrally
|
||||
_, session_maker = await db.get_or_create_db(
|
||||
db_path=app_config.database_path, db_type=db.DatabaseType.FILESYSTEM, ensure_migrations=False
|
||||
db_path=app_config.database_path,
|
||||
db_type=db.DatabaseType.FILESYSTEM,
|
||||
ensure_migrations=False,
|
||||
)
|
||||
logger.info("Migrating legacy projects...")
|
||||
project_repository = ProjectRepository(session_maker)
|
||||
@@ -140,7 +144,9 @@ async def initialize_file_sync(
|
||||
|
||||
# Load app configuration - migrations handled centrally
|
||||
_, session_maker = await db.get_or_create_db(
|
||||
db_path=app_config.database_path, db_type=db.DatabaseType.FILESYSTEM, ensure_migrations=False
|
||||
db_path=app_config.database_path,
|
||||
db_type=db.DatabaseType.FILESYSTEM,
|
||||
ensure_migrations=False,
|
||||
)
|
||||
project_repository = ProjectRepository(session_maker)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user