Skip to content

Commit 1bf3482

Browse files
committed
fix formatting on files
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 224e4bf commit 1bf3482

7 files changed

Lines changed: 65 additions & 63 deletions

File tree

src/basic_memory/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ def model_post_init(self, __context: Any) -> None:
9494
"""Ensure configuration is valid after initialization."""
9595
# Ensure main project exists
9696
if "main" not in self.projects: # pragma: no cover
97-
self.projects["main"] = str(Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory")))
97+
self.projects["main"] = str(
98+
Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory"))
99+
)
98100

99101
# Ensure default project is valid
100102
if self.default_project not in self.projects: # pragma: no cover

src/basic_memory/mcp/tools/move_note.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ async def _detect_cross_project_move_attempt(
1717
identifier: str, destination_path: str, current_project: str
1818
) -> Optional[str]:
1919
"""Detect potential cross-project move attempts and return guidance.
20-
20+
2121
Args:
2222
identifier: The note identifier being moved
2323
destination_path: The destination path
2424
current_project: The current active project
25-
25+
2626
Returns:
2727
Error message with guidance if cross-project move is detected, None otherwise
2828
"""
@@ -31,36 +31,40 @@ async def _detect_cross_project_move_attempt(
3131
response = await call_get(client, "/projects/projects")
3232
project_list = ProjectList.model_validate(response.json())
3333
project_names = [p.name.lower() for p in project_list.projects]
34-
34+
3535
# Check if destination path contains any project names
3636
dest_lower = destination_path.lower()
3737
path_parts = dest_lower.split("/")
38-
38+
3939
# Look for project names in the destination path
4040
for part in path_parts:
4141
if part in project_names and part != current_project.lower():
4242
# Found a different project name in the path
43-
matching_project = next(p.name for p in project_list.projects if p.name.lower() == part)
43+
matching_project = next(
44+
p.name for p in project_list.projects if p.name.lower() == part
45+
)
4446
return _format_cross_project_error_response(
4547
identifier, destination_path, current_project, matching_project
4648
)
47-
49+
4850
# Check if the destination path looks like it might be trying to reference another project
4951
# (e.g., contains common project-like patterns)
5052
if any(keyword in dest_lower for keyword in ["project", "workspace", "repo"]):
5153
# This might be a cross-project attempt, but we can't be sure
5254
# Return a general guidance message
53-
available_projects = [p.name for p in project_list.projects if p.name != current_project]
55+
available_projects = [
56+
p.name for p in project_list.projects if p.name != current_project
57+
]
5458
if available_projects:
5559
return _format_potential_cross_project_guidance(
5660
identifier, destination_path, current_project, available_projects
5761
)
58-
62+
5963
except Exception as e:
6064
# If we can't detect, don't interfere with normal error handling
6165
logger.debug(f"Could not check for cross-project move: {e}")
6266
return None
63-
67+
6468
return None
6569

6670

@@ -116,7 +120,7 @@ def _format_potential_cross_project_guidance(
116120
other_projects = ", ".join(available_projects[:3]) # Show first 3 projects
117121
if len(available_projects) > 3:
118122
other_projects += f" (and {len(available_projects) - 3} others)"
119-
123+
120124
return dedent(f"""
121125
# Move Failed - Check Project Context
122126

src/basic_memory/services/entity_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,9 @@ async def move_entity(
694694

695695
updates["permalink"] = new_permalink
696696
if old_permalink is None:
697-
logger.info(f"Generated permalink for entity with null permalink: {new_permalink}")
697+
logger.info(
698+
f"Generated permalink for entity with null permalink: {new_permalink}"
699+
)
698700
else:
699701
logger.info(f"Updated permalink: {old_permalink} -> {new_permalink}")
700702

test-int/mcp/test_move_note_integration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ async def test_move_note_cross_project_detection(mcp_server, app):
519519
"create_memory_project",
520520
{
521521
"project_name": "test-project-b",
522-
"project_path": "/tmp/test-project-b",
522+
"project_path": "/tmp/test-project-b",
523523
"set_default": False,
524524
},
525525
)
@@ -598,7 +598,7 @@ async def test_move_note_potential_cross_project_guidance(mcp_server, app):
598598
assert "switch_project" in error_message
599599

600600

601-
@pytest.mark.asyncio
601+
@pytest.mark.asyncio
602602
async def test_move_note_normal_moves_still_work(mcp_server, app):
603603
"""Test that normal within-project moves still work after cross-project detection."""
604604

tests/repository/test_entity_repository_upsert.py

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from datetime import datetime, timezone
55

66
from basic_memory.models.knowledge import Entity
7-
from basic_memory.models.project import Project
87
from basic_memory.repository.entity_repository import EntityRepository
98
from basic_memory.repository.project_repository import ProjectRepository
109

@@ -246,22 +245,22 @@ async def test_upsert_entity_gap_in_suffixes(entity_repository: EntityRepository
246245

247246
# Should get the next available suffix - our implementation finds gaps
248247
# so it should be "test/gap-2" (filling the gap)
249-
assert result.permalink == "test/gap-2"
248+
assert result.permalink == "test/gap-2"
250249
assert result.title == "Gap Filler"
251250

252251

253252
@pytest.mark.asyncio
254253
async def test_upsert_entity_project_scoping_isolation(session_maker):
255254
"""Test that upsert_entity properly scopes entities by project_id.
256-
255+
257256
This test ensures that the fix for issue #167 works correctly by verifying:
258257
1. Entities with same permalinks/file_paths can exist in different projects
259258
2. Upsert operations properly scope queries by project_id
260259
3. No "multiple rows" errors occur when similar entities exist across projects
261260
"""
262261
# Create two separate projects
263262
project_repository = ProjectRepository(session_maker)
264-
263+
265264
project1_data = {
266265
"name": "project-1",
267266
"description": "First test project",
@@ -270,20 +269,20 @@ async def test_upsert_entity_project_scoping_isolation(session_maker):
270269
"is_default": False,
271270
}
272271
project1 = await project_repository.create(project1_data)
273-
272+
274273
project2_data = {
275-
"name": "project-2",
274+
"name": "project-2",
276275
"description": "Second test project",
277276
"path": "/tmp/project2",
278277
"is_active": True,
279278
"is_default": False,
280279
}
281280
project2 = await project_repository.create(project2_data)
282-
281+
283282
# Create entity repositories for each project
284283
repo1 = EntityRepository(session_maker, project_id=project1.id)
285284
repo2 = EntityRepository(session_maker, project_id=project2.id)
286-
285+
287286
# Create entities with identical permalinks and file_paths in different projects
288287
entity1 = Entity(
289288
project_id=project1.id,
@@ -295,22 +294,22 @@ async def test_upsert_entity_project_scoping_isolation(session_maker):
295294
created_at=datetime.now(timezone.utc),
296295
updated_at=datetime.now(timezone.utc),
297296
)
298-
297+
299298
entity2 = Entity(
300299
project_id=project2.id,
301300
title="Shared Entity",
302-
entity_type="note",
301+
entity_type="note",
303302
permalink="docs/shared-name", # Same permalink
304303
file_path="docs/shared-name.md", # Same file_path
305304
content_type="text/markdown",
306305
created_at=datetime.now(timezone.utc),
307306
updated_at=datetime.now(timezone.utc),
308307
)
309-
308+
310309
# These should succeed without "multiple rows" errors
311310
result1 = await repo1.upsert_entity(entity1)
312311
result2 = await repo2.upsert_entity(entity2)
313-
312+
314313
# Verify both entities were created successfully
315314
assert result1.id is not None
316315
assert result2.id is not None
@@ -319,7 +318,7 @@ async def test_upsert_entity_project_scoping_isolation(session_maker):
319318
assert result2.project_id == project2.id
320319
assert result1.permalink == "docs/shared-name"
321320
assert result2.permalink == "docs/shared-name"
322-
321+
323322
# Test updating entities in different projects (should also work without conflicts)
324323
entity1_update = Entity(
325324
project_id=project1.id,
@@ -331,7 +330,7 @@ async def test_upsert_entity_project_scoping_isolation(session_maker):
331330
created_at=datetime.now(timezone.utc),
332331
updated_at=datetime.now(timezone.utc),
333332
)
334-
333+
335334
entity2_update = Entity(
336335
project_id=project2.id,
337336
title="Also Updated Shared Entity",
@@ -342,21 +341,21 @@ async def test_upsert_entity_project_scoping_isolation(session_maker):
342341
created_at=datetime.now(timezone.utc),
343342
updated_at=datetime.now(timezone.utc),
344343
)
345-
344+
346345
# Updates should work without conflicts
347346
updated1 = await repo1.upsert_entity(entity1_update)
348347
updated2 = await repo2.upsert_entity(entity2_update)
349-
348+
350349
# Should update existing entities (same IDs)
351350
assert updated1.id == result1.id
352351
assert updated2.id == result2.id
353352
assert updated1.title == "Updated Shared Entity"
354353
assert updated2.title == "Also Updated Shared Entity"
355-
354+
356355
# Verify cross-project queries don't interfere
357356
found_in_project1 = await repo1.get_by_permalink("docs/shared-name")
358357
found_in_project2 = await repo2.get_by_permalink("docs/shared-name")
359-
358+
360359
assert found_in_project1 is not None
361360
assert found_in_project2 is not None
362361
assert found_in_project1.id == updated1.id
@@ -368,14 +367,14 @@ async def test_upsert_entity_project_scoping_isolation(session_maker):
368367
@pytest.mark.asyncio
369368
async def test_upsert_entity_permalink_conflict_within_project_only(session_maker):
370369
"""Test that permalink conflicts only occur within the same project.
371-
370+
372371
This ensures that the project scoping fix allows entities with identical
373-
permalinks to exist across different projects without triggering
372+
permalinks to exist across different projects without triggering
374373
permalink conflict resolution.
375374
"""
376375
# Create two separate projects
377376
project_repository = ProjectRepository(session_maker)
378-
377+
379378
project1_data = {
380379
"name": "conflict-project-1",
381380
"description": "First conflict test project",
@@ -384,20 +383,20 @@ async def test_upsert_entity_permalink_conflict_within_project_only(session_make
384383
"is_default": False,
385384
}
386385
project1 = await project_repository.create(project1_data)
387-
386+
388387
project2_data = {
389388
"name": "conflict-project-2",
390-
"description": "Second conflict test project",
389+
"description": "Second conflict test project",
391390
"path": "/tmp/conflict2",
392391
"is_active": True,
393392
"is_default": False,
394393
}
395394
project2 = await project_repository.create(project2_data)
396-
395+
397396
# Create entity repositories for each project
398397
repo1 = EntityRepository(session_maker, project_id=project1.id)
399398
repo2 = EntityRepository(session_maker, project_id=project2.id)
400-
399+
401400
# Create first entity in project1
402401
entity1 = Entity(
403402
project_id=project1.id,
@@ -409,10 +408,10 @@ async def test_upsert_entity_permalink_conflict_within_project_only(session_make
409408
created_at=datetime.now(timezone.utc),
410409
updated_at=datetime.now(timezone.utc),
411410
)
412-
411+
413412
result1 = await repo1.upsert_entity(entity1)
414413
assert result1.permalink == "test/conflict-permalink"
415-
414+
416415
# Create entity with same permalink in project2 (should NOT get suffix)
417416
entity2 = Entity(
418417
project_id=project2.id,
@@ -424,11 +423,11 @@ async def test_upsert_entity_permalink_conflict_within_project_only(session_make
424423
created_at=datetime.now(timezone.utc),
425424
updated_at=datetime.now(timezone.utc),
426425
)
427-
426+
428427
result2 = await repo2.upsert_entity(entity2)
429428
# Should keep original permalink (no suffix) since it's in a different project
430429
assert result2.permalink == "test/conflict-permalink"
431-
430+
432431
# Now create entity with same permalink in project1 (should get suffix)
433432
entity3 = Entity(
434433
project_id=project1.id,
@@ -440,11 +439,11 @@ async def test_upsert_entity_permalink_conflict_within_project_only(session_make
440439
created_at=datetime.now(timezone.utc),
441440
updated_at=datetime.now(timezone.utc),
442441
)
443-
442+
444443
result3 = await repo1.upsert_entity(entity3)
445444
# Should get suffix since it conflicts within the same project
446445
assert result3.permalink == "test/conflict-permalink-1"
447-
446+
448447
# Verify all entities exist correctly
449448
assert result1.id != result2.id != result3.id
450449
assert result1.project_id == project1.id

tests/services/test_entity_service.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,16 +1737,15 @@ async def test_move_entity_with_null_permalink_generates_permalink(
17371737
entity_repository: EntityRepository,
17381738
):
17391739
"""Test that moving entity with null permalink generates a new permalink automatically.
1740-
1741-
This tests the fix for issue #155 where entities with null permalinks from the database
1742-
migration would fail validation when being moved. The fix ensures that entities with
1743-
null permalinks get a generated permalink during move operations, regardless of the
1740+
1741+
This tests the fix for issue #155 where entities with null permalinks from the database
1742+
migration would fail validation when being moved. The fix ensures that entities with
1743+
null permalinks get a generated permalink during move operations, regardless of the
17441744
update_permalinks_on_move setting.
17451745
"""
17461746
# Create entity through direct database insertion to simulate migrated entity with null permalink
1747-
from basic_memory.models.knowledge import Entity as EntityModel
17481747
from datetime import datetime, timezone
1749-
1748+
17501749
# Create an entity with null permalink directly in database (simulating migrated data)
17511750
entity_data = {
17521751
"title": "Test Entity",
@@ -1757,38 +1756,39 @@ async def test_move_entity_with_null_permalink_generates_permalink(
17571756
"created_at": datetime.now(timezone.utc),
17581757
"updated_at": datetime.now(timezone.utc),
17591758
}
1760-
1759+
17611760
# Create the entity directly in database
17621761
created_entity = await entity_repository.create(entity_data)
17631762
assert created_entity.permalink is None
1764-
1763+
17651764
# Create the physical file
17661765
file_path = project_config.home / created_entity.file_path
17671766
file_path.parent.mkdir(parents=True, exist_ok=True)
17681767
file_path.write_text("# Test Entity\n\nContent here.")
1769-
1768+
17701769
# Configure move without permalink updates (the default setting that previously triggered the bug)
17711770
app_config = BasicMemoryConfig(update_permalinks_on_move=False)
1772-
1771+
17731772
# Move entity - this should now succeed and generate a permalink
17741773
moved_entity = await entity_service.move_entity(
17751774
identifier=created_entity.title, # Use title since permalink is None
17761775
destination_path="moved/test-entity.md",
17771776
project_config=project_config,
17781777
app_config=app_config,
17791778
)
1780-
1779+
17811780
# Verify the move succeeded and a permalink was generated
17821781
assert moved_entity is not None
17831782
assert moved_entity.file_path == "moved/test-entity.md"
17841783
assert moved_entity.permalink is not None
17851784
assert moved_entity.permalink != ""
1786-
1785+
17871786
# Verify the moved entity can be used to create an EntityResponse without validation errors
17881787
from basic_memory.schemas.response import EntityResponse
1788+
17891789
response = EntityResponse.model_validate(moved_entity)
17901790
assert response.permalink == moved_entity.permalink
1791-
1791+
17921792
# Verify the physical file was moved
17931793
old_path = project_config.home / "test/null-permalink-entity.md"
17941794
new_path = project_config.home / "moved/test-entity.md"

0 commit comments

Comments
 (0)