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:
Drew Cain
2025-06-19 19:59:17 -05:00
committed by GitHub
parent 3269a2f33a
commit 7be001ca68
9 changed files with 126 additions and 121 deletions
+2
View File
@@ -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",
)
+5 -4
View File
@@ -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
-1
View File
@@ -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)
+2 -2
View File
@@ -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)
+11 -5
View File
@@ -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)