basic-memory 0.2.21__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of basic-memory might be problematic. Click here for more details.

Files changed (142) hide show
  1. {basic_memory-0.2.21 → basic_memory-0.3.0}/.gitignore +1 -0
  2. {basic_memory-0.2.21 → basic_memory-0.3.0}/CHANGELOG.md +19 -0
  3. {basic_memory-0.2.21 → basic_memory-0.3.0}/PKG-INFO +1 -1
  4. {basic_memory-0.2.21 → basic_memory-0.3.0}/installer/installer.py +1 -0
  5. {basic_memory-0.2.21 → basic_memory-0.3.0}/installer/setup.py +8 -15
  6. {basic_memory-0.2.21 → basic_memory-0.3.0}/pyproject.toml +4 -2
  7. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/__init__.py +1 -1
  8. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/api/app.py +3 -24
  9. basic_memory-0.3.0/src/basic_memory/cli/app.py +13 -0
  10. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/cli/commands/db.py +1 -1
  11. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/cli/commands/mcp.py +2 -2
  12. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/cli/commands/status.py +5 -7
  13. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/cli/commands/sync.py +44 -44
  14. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/db.py +21 -28
  15. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/mcp/async_client.py +1 -1
  16. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/mcp/tools/notes.py +4 -1
  17. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/sync/utils.py +1 -4
  18. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/conftest.py +2 -5
  19. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/mcp/test_tool_notes.py +22 -0
  20. {basic_memory-0.2.21 → basic_memory-0.3.0}/uv.lock +1 -1
  21. basic_memory-0.2.21/src/basic_memory/cli/app.py +0 -3
  22. {basic_memory-0.2.21 → basic_memory-0.3.0}/.github/workflows/pr-title.yml +0 -0
  23. {basic_memory-0.2.21 → basic_memory-0.3.0}/.github/workflows/release.yml +0 -0
  24. {basic_memory-0.2.21 → basic_memory-0.3.0}/.github/workflows/test.yml +0 -0
  25. {basic_memory-0.2.21 → basic_memory-0.3.0}/.python-version +0 -0
  26. {basic_memory-0.2.21 → basic_memory-0.3.0}/CITATION.cff +0 -0
  27. {basic_memory-0.2.21 → basic_memory-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  28. {basic_memory-0.2.21 → basic_memory-0.3.0}/CONTRIBUTING.md +0 -0
  29. {basic_memory-0.2.21 → basic_memory-0.3.0}/LICENSE +0 -0
  30. {basic_memory-0.2.21 → basic_memory-0.3.0}/Makefile +0 -0
  31. {basic_memory-0.2.21 → basic_memory-0.3.0}/README.md +0 -0
  32. {basic_memory-0.2.21 → basic_memory-0.3.0}/alembic.ini +0 -0
  33. {basic_memory-0.2.21 → basic_memory-0.3.0}/basic-memory.md +0 -0
  34. {basic_memory-0.2.21 → basic_memory-0.3.0}/installer/Basic.icns +0 -0
  35. {basic_memory-0.2.21 → basic_memory-0.3.0}/installer/README.md +0 -0
  36. {basic_memory-0.2.21 → basic_memory-0.3.0}/installer/icon.svg +0 -0
  37. {basic_memory-0.2.21 → basic_memory-0.3.0}/installer/make_icons.sh +0 -0
  38. {basic_memory-0.2.21 → basic_memory-0.3.0}/memory.json +0 -0
  39. {basic_memory-0.2.21 → basic_memory-0.3.0}/scripts/install.sh +0 -0
  40. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/alembic/README +0 -0
  41. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/alembic/env.py +0 -0
  42. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/alembic/migrations.py +0 -0
  43. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/alembic/script.py.mako +0 -0
  44. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
  45. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/api/__init__.py +0 -0
  46. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/api/routers/__init__.py +0 -0
  47. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/api/routers/knowledge_router.py +0 -0
  48. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/api/routers/memory_router.py +0 -0
  49. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/api/routers/resource_router.py +0 -0
  50. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/api/routers/search_router.py +0 -0
  51. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/cli/__init__.py +0 -0
  52. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/cli/commands/__init__.py +0 -0
  53. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/cli/commands/import_memory_json.py +0 -0
  54. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/cli/main.py +0 -0
  55. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/config.py +0 -0
  56. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/deps.py +0 -0
  57. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/file_utils.py +0 -0
  58. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/markdown/__init__.py +0 -0
  59. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/markdown/entity_parser.py +0 -0
  60. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/markdown/markdown_processor.py +0 -0
  61. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/markdown/plugins.py +0 -0
  62. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/markdown/schemas.py +0 -0
  63. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/markdown/utils.py +0 -0
  64. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/mcp/__init__.py +0 -0
  65. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/mcp/server.py +0 -0
  66. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/mcp/tools/__init__.py +0 -0
  67. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/mcp/tools/knowledge.py +0 -0
  68. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/mcp/tools/memory.py +0 -0
  69. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/mcp/tools/search.py +0 -0
  70. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/mcp/tools/utils.py +0 -0
  71. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/models/__init__.py +0 -0
  72. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/models/base.py +0 -0
  73. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/models/knowledge.py +0 -0
  74. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/models/search.py +0 -0
  75. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/repository/__init__.py +0 -0
  76. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/repository/entity_repository.py +0 -0
  77. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/repository/observation_repository.py +0 -0
  78. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/repository/relation_repository.py +0 -0
  79. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/repository/repository.py +0 -0
  80. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/repository/search_repository.py +0 -0
  81. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/schemas/__init__.py +0 -0
  82. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/schemas/base.py +0 -0
  83. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/schemas/delete.py +0 -0
  84. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/schemas/discovery.py +0 -0
  85. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/schemas/memory.py +0 -0
  86. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/schemas/request.py +0 -0
  87. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/schemas/response.py +0 -0
  88. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/schemas/search.py +0 -0
  89. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/services/__init__.py +0 -0
  90. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/services/context_service.py +0 -0
  91. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/services/entity_service.py +0 -0
  92. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/services/exceptions.py +0 -0
  93. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/services/file_service.py +0 -0
  94. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/services/link_resolver.py +0 -0
  95. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/services/search_service.py +0 -0
  96. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/services/service.py +0 -0
  97. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/sync/__init__.py +0 -0
  98. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/sync/file_change_scanner.py +0 -0
  99. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/sync/sync_service.py +0 -0
  100. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/sync/watch_service.py +0 -0
  101. {basic_memory-0.2.21 → basic_memory-0.3.0}/src/basic_memory/utils.py +0 -0
  102. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/api/conftest.py +0 -0
  103. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/api/test_knowledge_router.py +0 -0
  104. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/api/test_memory_router.py +0 -0
  105. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/api/test_resource_router.py +0 -0
  106. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/api/test_search_router.py +0 -0
  107. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/cli/test_import_memory_json.py +0 -0
  108. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/cli/test_status.py +0 -0
  109. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/cli/test_sync.py +0 -0
  110. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/edit_file_test.py +0 -0
  111. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/markdown/__init__.py +0 -0
  112. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/markdown/test_entity_parser.py +0 -0
  113. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/markdown/test_markdown_plugins.py +0 -0
  114. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/markdown/test_markdown_processor.py +0 -0
  115. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/markdown/test_observation_edge_cases.py +0 -0
  116. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/markdown/test_parser_edge_cases.py +0 -0
  117. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/markdown/test_relation_edge_cases.py +0 -0
  118. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/markdown/test_task_detection.py +0 -0
  119. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/mcp/conftest.py +0 -0
  120. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/mcp/test_tool_get_entity.py +0 -0
  121. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/mcp/test_tool_knowledge.py +0 -0
  122. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/mcp/test_tool_memory.py +0 -0
  123. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/mcp/test_tool_search.py +0 -0
  124. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/mcp/test_tool_utils.py +0 -0
  125. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/repository/test_entity_repository.py +0 -0
  126. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/repository/test_observation_repository.py +0 -0
  127. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/repository/test_relation_repository.py +0 -0
  128. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/repository/test_repository.py +0 -0
  129. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/schemas/test_memory_url.py +0 -0
  130. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/schemas/test_schemas.py +0 -0
  131. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/schemas/test_search.py +0 -0
  132. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/services/test_context_service.py +0 -0
  133. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/services/test_entity_service.py +0 -0
  134. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/services/test_file_service.py +0 -0
  135. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/services/test_link_resolver.py +0 -0
  136. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/services/test_search_service.py +0 -0
  137. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/sync/test_file_change_scanner.py +0 -0
  138. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/sync/test_sync_service.py +0 -0
  139. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/sync/test_watch_service.py +0 -0
  140. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/test_basic_memory.py +0 -0
  141. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/utils/test_file_utils.py +0 -0
  142. {basic_memory-0.2.21 → basic_memory-0.3.0}/tests/utils/test_permalink_formatting.py +0 -0
@@ -42,3 +42,4 @@ ENV/
42
42
 
43
43
  # macOS
44
44
  .DS_Store
45
+ /.coverage.*
@@ -1,6 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.3.0 (2025-02-15)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Refactor db schema migrate handling
9
+ ([`ca632be`](https://github.com/basicmachines-co/basic-memory/commit/ca632beb6fed5881f4d8ba5ce698bb5bc681e6aa))
10
+
11
+
4
12
  ## v0.2.21 (2025-02-15)
5
13
 
6
14
  ### Bug Fixes
@@ -8,6 +16,17 @@
8
16
  - Fix osx installer github action
9
17
  ([`65ebe5d`](https://github.com/basicmachines-co/basic-memory/commit/65ebe5d19491e5ff047c459d799498ad5dd9cd1a))
10
18
 
19
+ - Handle memory:// url format in read_note tool
20
+ ([`e080373`](https://github.com/basicmachines-co/basic-memory/commit/e0803734e69eeb6c6d7432eea323c7a264cb8347))
21
+
22
+ - Remove create schema from init_db
23
+ ([`674dd1f`](https://github.com/basicmachines-co/basic-memory/commit/674dd1fd47be9e60ac17508476c62254991df288))
24
+
25
+ ### Features
26
+
27
+ - Set version in var, output version at startup
28
+ ([`a91da13`](https://github.com/basicmachines-co/basic-memory/commit/a91da1396710e62587df1284da00137d156fc05e))
29
+
11
30
 
12
31
  ## v0.2.20 (2025-02-14)
13
32
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basic-memory
3
- Version: 0.2.21
3
+ Version: 0.3.0
4
4
  Summary: Local-first knowledge management combining Zettelkasten with knowledge graphs
5
5
  Project-URL: Homepage, https://github.com/basicmachines-co/basic-memory
6
6
  Project-URL: Repository, https://github.com/basicmachines-co/basic-memory
@@ -8,6 +8,7 @@ if sys.platform == "darwin":
8
8
  import tkinter as tk
9
9
  from tkinter import messagebox
10
10
 
11
+
11
12
  def ensure_uv_installed():
12
13
  """Check if uv is installed, install if not."""
13
14
  try:
@@ -4,31 +4,24 @@ import sys
4
4
  # Build options for all platforms
5
5
  build_exe_options = {
6
6
  "packages": ["json", "pathlib"],
7
- "excludes": [
8
- "unittest",
9
- "pydoc",
10
- "test"
11
- ],
7
+ "excludes": ["unittest", "pydoc", "test"],
12
8
  }
13
9
 
14
10
  # Platform-specific options
15
11
  if sys.platform == "win32":
16
12
  base = "Win32GUI" # Use GUI base for Windows
17
- build_exe_options.update({
18
- "include_msvcr": True,
19
- })
13
+ build_exe_options.update(
14
+ {
15
+ "include_msvcr": True,
16
+ }
17
+ )
20
18
  target_name = "Basic Memory Installer.exe"
21
19
  else: # darwin
22
20
  base = None # Don't use GUI base for macOS
23
21
  target_name = "Basic Memory Installer"
24
22
 
25
23
  executables = [
26
- Executable(
27
- script="installer.py",
28
- target_name=target_name,
29
- base=base,
30
- icon="Basic.icns"
31
- )
24
+ Executable(script="installer.py", target_name=target_name, base=base, icon="Basic.icns")
32
25
  ]
33
26
 
34
27
  setup(
@@ -41,7 +34,7 @@ setup(
41
34
  "bundle_name": "Basic Memory Installer",
42
35
  "iconfile": "Basic.icns",
43
36
  "codesign_identity": "-", # Force ad-hoc signing
44
- }
37
+ },
45
38
  },
46
39
  executables=executables,
47
40
  )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "basic-memory"
3
- version = "0.2.21"
3
+ version = "0.3.0"
4
4
  description = "Local-first knowledge management combining Zettelkasten with knowledge graphs"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12.1"
@@ -84,7 +84,9 @@ pythonVersion = "3.12"
84
84
 
85
85
 
86
86
  [tool.semantic_release]
87
- version_variable = "src/basic_memory/__init__.py:__version__"
87
+ version_variables = [
88
+ "src/basic_memory/__init__.py:__version__",
89
+ ]
88
90
  version_toml = [
89
91
  "pyproject.toml:project.version",
90
92
  ]
@@ -1,3 +1,3 @@
1
1
  """basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
2
2
 
3
- __version__ = "0.0.1"
3
+ __version__ = "0.3.0"
@@ -6,38 +6,17 @@ from fastapi import FastAPI, HTTPException
6
6
  from fastapi.exception_handlers import http_exception_handler
7
7
  from loguru import logger
8
8
 
9
+ import basic_memory
9
10
  from basic_memory import db
10
11
  from basic_memory.config import config as app_config
11
12
  from basic_memory.api.routers import knowledge, search, memory, resource
12
- from alembic import command
13
- from alembic.config import Config
14
-
15
- from basic_memory.db import DatabaseType
16
- from basic_memory.repository.search_repository import SearchRepository
17
-
18
-
19
- async def run_migrations(): # pragma: no cover
20
- """Run any pending alembic migrations."""
21
- logger.info("Running database migrations...")
22
- try:
23
- config = Config("alembic.ini")
24
- command.upgrade(config, "head")
25
- logger.info("Migrations completed successfully")
26
-
27
- _, session_maker = await db.get_or_create_db(
28
- app_config.database_path, DatabaseType.FILESYSTEM
29
- )
30
- await SearchRepository(session_maker).init_search_index()
31
- except Exception as e:
32
- logger.error(f"Error running migrations: {e}")
33
- raise
34
13
 
35
14
 
36
15
  @asynccontextmanager
37
16
  async def lifespan(app: FastAPI): # pragma: no cover
38
17
  """Lifecycle manager for the FastAPI app."""
39
- logger.info("Starting Basic Memory API")
40
- await run_migrations()
18
+ logger.info(f"Starting Basic Memory API {basic_memory.__version__}")
19
+ await db.run_migrations(app_config)
41
20
  yield
42
21
  logger.info("Shutting down Basic Memory API")
43
22
  await db.shutdown_db()
@@ -0,0 +1,13 @@
1
+ import asyncio
2
+
3
+ import typer
4
+
5
+ from basic_memory import db
6
+ from basic_memory.config import config
7
+ from basic_memory.utils import setup_logging
8
+
9
+ setup_logging(log_file=".basic-memory/basic-memory-cli.log") # pragma: no cover
10
+
11
+ asyncio.run(db.run_migrations(config))
12
+
13
+ app = typer.Typer()
@@ -22,4 +22,4 @@ def reset(
22
22
  from basic_memory.cli.commands.sync import sync
23
23
 
24
24
  logger.info("Rebuilding search index from filesystem...")
25
- asyncio.run(sync()) # pyright: ignore
25
+ sync(watch=False) # pyright: ignore
@@ -12,9 +12,9 @@ import basic_memory.mcp.tools # noqa: F401 # pragma: no cover
12
12
 
13
13
 
14
14
  @app.command()
15
- def mcp():
15
+ def mcp(): # pragma: no cover
16
16
  """Run the MCP server for Claude Desktop integration."""
17
17
  home_dir = config.home
18
- logger.info("Starting Basic Memory MCP server")
18
+ logger.info(f"Starting Basic Memory MCP server {basic_memory.__version__}")
19
19
  logger.info(f"Home directory: {home_dir}")
20
20
  mcp_server.run()
@@ -25,13 +25,11 @@ async def get_file_change_scanner(
25
25
  db_type=DatabaseType.FILESYSTEM,
26
26
  ) -> FileChangeScanner: # pragma: no cover
27
27
  """Get sync service instance."""
28
- async with db.engine_session_factory(db_path=config.database_path, db_type=db_type) as (
29
- engine,
30
- session_maker,
31
- ):
32
- entity_repository = EntityRepository(session_maker)
33
- file_change_scanner = FileChangeScanner(entity_repository)
34
- return file_change_scanner
28
+ _, session_maker = await db.get_or_create_db(db_path=config.database_path, db_type=db_type)
29
+
30
+ entity_repository = EntityRepository(session_maker)
31
+ file_change_scanner = FileChangeScanner(entity_repository)
32
+ return file_change_scanner
35
33
 
36
34
 
37
35
  def add_files_to_tree(
@@ -14,7 +14,6 @@ from rich.tree import Tree
14
14
  from basic_memory import db
15
15
  from basic_memory.cli.app import app
16
16
  from basic_memory.config import config
17
- from basic_memory.db import DatabaseType
18
17
  from basic_memory.markdown import EntityParser
19
18
  from basic_memory.markdown.markdown_processor import MarkdownProcessor
20
19
  from basic_memory.repository import (
@@ -39,50 +38,50 @@ class ValidationIssue:
39
38
  error: str
40
39
 
41
40
 
42
- async def get_sync_service(db_type=DatabaseType.FILESYSTEM): # pragma: no cover
41
+ async def get_sync_service(): # pragma: no cover
43
42
  """Get sync service instance with all dependencies."""
44
- async with db.engine_session_factory(db_path=config.database_path, db_type=db_type) as (
45
- engine,
46
- session_maker,
47
- ):
48
- entity_parser = EntityParser(config.home)
49
- markdown_processor = MarkdownProcessor(entity_parser)
50
- file_service = FileService(config.home, markdown_processor)
51
-
52
- # Initialize repositories
53
- entity_repository = EntityRepository(session_maker)
54
- observation_repository = ObservationRepository(session_maker)
55
- relation_repository = RelationRepository(session_maker)
56
- search_repository = SearchRepository(session_maker)
57
-
58
- # Initialize services
59
- search_service = SearchService(search_repository, entity_repository, file_service)
60
- link_resolver = LinkResolver(entity_repository, search_service)
61
-
62
- # Initialize scanner
63
- file_change_scanner = FileChangeScanner(entity_repository)
64
-
65
- # Initialize services
66
- entity_service = EntityService(
67
- entity_parser,
68
- entity_repository,
69
- observation_repository,
70
- relation_repository,
71
- file_service,
72
- link_resolver,
73
- )
74
-
75
- # Create sync service
76
- sync_service = SyncService(
77
- scanner=file_change_scanner,
78
- entity_service=entity_service,
79
- entity_parser=entity_parser,
80
- entity_repository=entity_repository,
81
- relation_repository=relation_repository,
82
- search_service=search_service,
83
- )
84
-
85
- return sync_service
43
+ _, session_maker = await db.get_or_create_db(
44
+ db_path=config.database_path, db_type=db.DatabaseType.FILESYSTEM
45
+ )
46
+
47
+ entity_parser = EntityParser(config.home)
48
+ markdown_processor = MarkdownProcessor(entity_parser)
49
+ file_service = FileService(config.home, markdown_processor)
50
+
51
+ # Initialize repositories
52
+ entity_repository = EntityRepository(session_maker)
53
+ observation_repository = ObservationRepository(session_maker)
54
+ relation_repository = RelationRepository(session_maker)
55
+ search_repository = SearchRepository(session_maker)
56
+
57
+ # Initialize services
58
+ search_service = SearchService(search_repository, entity_repository, file_service)
59
+ link_resolver = LinkResolver(entity_repository, search_service)
60
+
61
+ # Initialize scanner
62
+ file_change_scanner = FileChangeScanner(entity_repository)
63
+
64
+ # Initialize services
65
+ entity_service = EntityService(
66
+ entity_parser,
67
+ entity_repository,
68
+ observation_repository,
69
+ relation_repository,
70
+ file_service,
71
+ link_resolver,
72
+ )
73
+
74
+ # Create sync service
75
+ sync_service = SyncService(
76
+ scanner=file_change_scanner,
77
+ entity_service=entity_service,
78
+ entity_parser=entity_parser,
79
+ entity_repository=entity_repository,
80
+ relation_repository=relation_repository,
81
+ search_service=search_service,
82
+ )
83
+
84
+ return sync_service
86
85
 
87
86
 
88
87
  def group_issues_by_directory(issues: List[ValidationIssue]) -> Dict[str, List[ValidationIssue]]:
@@ -154,6 +153,7 @@ def display_detailed_sync_results(knowledge: SyncReport):
154
153
 
155
154
  async def run_sync(verbose: bool = False, watch: bool = False):
156
155
  """Run sync operation."""
156
+
157
157
  sync_service = await get_sync_service()
158
158
 
159
159
  # Start watching if requested
@@ -4,6 +4,10 @@ from enum import Enum, auto
4
4
  from pathlib import Path
5
5
  from typing import AsyncGenerator, Optional
6
6
 
7
+ from basic_memory.config import ProjectConfig
8
+ from alembic import command
9
+ from alembic.config import Config
10
+
7
11
  from loguru import logger
8
12
  from sqlalchemy import text
9
13
  from sqlalchemy.ext.asyncio import (
@@ -14,8 +18,7 @@ from sqlalchemy.ext.asyncio import (
14
18
  async_scoped_session,
15
19
  )
16
20
 
17
- from basic_memory.models import Base
18
- from basic_memory.models.search import CREATE_SEARCH_INDEX
21
+ from basic_memory.repository.search_repository import SearchRepository
19
22
 
20
23
  # Module level state
21
24
  _engine: Optional[AsyncEngine] = None
@@ -35,7 +38,7 @@ class DatabaseType(Enum):
35
38
  logger.info("Using in-memory SQLite database")
36
39
  return "sqlite+aiosqlite://"
37
40
 
38
- return f"sqlite+aiosqlite:///{db_path}"
41
+ return f"sqlite+aiosqlite:///{db_path}" # pragma: no cover
39
42
 
40
43
 
41
44
  def get_scoped_session_factory(
@@ -69,24 +72,6 @@ async def scoped_session(
69
72
  await factory.remove()
70
73
 
71
74
 
72
- async def init_db() -> None:
73
- """Initialize database with required tables."""
74
- if _session_maker is None: # pragma: no cover
75
- raise RuntimeError("Database session maker not initialized")
76
-
77
- logger.info("Initializing database...")
78
-
79
- async with scoped_session(_session_maker) as session:
80
- await session.execute(text("PRAGMA foreign_keys=ON"))
81
- conn = await session.connection()
82
- await conn.run_sync(Base.metadata.create_all)
83
-
84
- # recreate search index
85
- await session.execute(CREATE_SEARCH_INDEX)
86
-
87
- await session.commit()
88
-
89
-
90
75
  async def get_or_create_db(
91
76
  db_path: Path,
92
77
  db_type: DatabaseType = DatabaseType.FILESYSTEM,
@@ -100,9 +85,6 @@ async def get_or_create_db(
100
85
  _engine = create_async_engine(db_url, connect_args={"check_same_thread": False})
101
86
  _session_maker = async_sessionmaker(_engine, expire_on_commit=False)
102
87
 
103
- # Initialize database
104
- await init_db()
105
-
106
88
  assert _engine is not None # for type checker
107
89
  assert _session_maker is not None # for type checker
108
90
  return _engine, _session_maker
@@ -122,7 +104,6 @@ async def shutdown_db() -> None: # pragma: no cover
122
104
  async def engine_session_factory(
123
105
  db_path: Path,
124
106
  db_type: DatabaseType = DatabaseType.MEMORY,
125
- init: bool = True,
126
107
  ) -> AsyncGenerator[tuple[AsyncEngine, async_sessionmaker[AsyncSession]], None]:
127
108
  """Create engine and session factory.
128
109
 
@@ -139,9 +120,6 @@ async def engine_session_factory(
139
120
  try:
140
121
  _session_maker = async_sessionmaker(_engine, expire_on_commit=False)
141
122
 
142
- if init:
143
- await init_db()
144
-
145
123
  assert _engine is not None # for type checker
146
124
  assert _session_maker is not None # for type checker
147
125
  yield _engine, _session_maker
@@ -150,3 +128,18 @@ async def engine_session_factory(
150
128
  await _engine.dispose()
151
129
  _engine = None
152
130
  _session_maker = None
131
+
132
+
133
+ async def run_migrations(app_config: ProjectConfig, database_type=DatabaseType.FILESYSTEM):
134
+ """Run any pending alembic migrations."""
135
+ logger.info("Running database migrations...")
136
+ try:
137
+ config = Config("alembic.ini")
138
+ command.upgrade(config, "head")
139
+ logger.info("Migrations completed successfully")
140
+
141
+ _, session_maker = await get_or_create_db(app_config.database_path, database_type)
142
+ await SearchRepository(session_maker).init_search_index()
143
+ except Exception as e: # pragma: no cover
144
+ logger.error(f"Error running migrations: {e}")
145
+ raise
@@ -2,7 +2,7 @@ from httpx import ASGITransport, AsyncClient
2
2
 
3
3
  from basic_memory.api.app import app as fastapi_app
4
4
 
5
- BASE_URL = "http://test"
5
+ BASE_URL = "memory://"
6
6
 
7
7
  # Create shared async client
8
8
  client = AsyncClient(transport=ASGITransport(app=fastapi_app), base_url=BASE_URL)
@@ -13,6 +13,7 @@ from basic_memory.mcp.async_client import client
13
13
  from basic_memory.schemas import EntityResponse, DeleteEntitiesResponse
14
14
  from basic_memory.schemas.base import Entity
15
15
  from basic_memory.mcp.tools.utils import call_get, call_put, call_delete
16
+ from basic_memory.schemas.memory import memory_url_path
16
17
 
17
18
 
18
19
  @mcp.tool(
@@ -96,7 +97,9 @@ async def read_note(identifier: str) -> str:
96
97
  Raises:
97
98
  ValueError: If the note cannot be found
98
99
  """
99
- response = await call_get(client, f"/resource/{identifier}")
100
+ logger.info(f"Reading note {identifier}")
101
+ url = memory_url_path(identifier)
102
+ response = await call_get(client, f"/resource/{url}")
100
103
  return response.text
101
104
 
102
105
 
@@ -1,10 +1,7 @@
1
1
  """Types and utilities for file sync."""
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import Set, Dict, Optional
5
-
6
- from watchfiles import Change
7
-
4
+ from typing import Set, Dict
8
5
 
9
6
 
10
7
  @dataclass
@@ -7,7 +7,6 @@ from datetime import datetime, timezone
7
7
  import pytest
8
8
  import pytest_asyncio
9
9
  from loguru import logger
10
- from sqlalchemy import text
11
10
  from sqlalchemy.ext.asyncio import AsyncSession, AsyncEngine, async_sessionmaker
12
11
 
13
12
  from basic_memory import db
@@ -59,10 +58,8 @@ async def engine_factory(
59
58
  async with db.engine_session_factory(
60
59
  db_path=test_config.database_path, db_type=DatabaseType.MEMORY
61
60
  ) as (engine, session_maker):
62
- # Initialize database
63
- async with db.scoped_session(session_maker) as session:
64
- await session.execute(text("PRAGMA foreign_keys=ON"))
65
- conn = await session.connection()
61
+ # Create all tables for the DB the engine is connected to
62
+ async with engine.begin() as conn:
66
63
  await conn.run_sync(Base.metadata.create_all)
67
64
 
68
65
  yield engine, session_maker
@@ -236,3 +236,25 @@ async def test_write_note_verbose(app):
236
236
  assert entity.relations[0].from_id == "test/test-note"
237
237
  assert entity.relations[0].to_id is None
238
238
  assert entity.relations[0].to_name == "Knowledge"
239
+
240
+
241
+ @pytest.mark.asyncio
242
+ async def test_read_note_memory_url(app):
243
+ """Test reading a note using a memory:// URL.
244
+
245
+ Should:
246
+ - Handle memory:// URLs correctly
247
+ - Normalize the URL before resolving
248
+ - Return the note content
249
+ """
250
+ # First create a note
251
+ permalink = await notes.write_note(
252
+ title="Memory URL Test",
253
+ folder="test",
254
+ content="Testing memory:// URL handling",
255
+ )
256
+
257
+ # Should be able to read it with a memory:// URL
258
+ memory_url = f"memory://{permalink}"
259
+ content = await notes.read_note(memory_url)
260
+ assert "Testing memory:// URL handling" in content
@@ -70,7 +70,7 @@ wheels = [
70
70
 
71
71
  [[package]]
72
72
  name = "basic-memory"
73
- version = "0.2.19"
73
+ version = "0.2.21"
74
74
  source = { editable = "." }
75
75
  dependencies = [
76
76
  { name = "aiosqlite" },
@@ -1,3 +0,0 @@
1
- import typer
2
-
3
- app = typer.Typer()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes