basic-memory 0.16.1__py3-none-any.whl → 0.17.4__py3-none-any.whl
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.
- basic_memory/__init__.py +1 -1
- basic_memory/alembic/env.py +112 -26
- basic_memory/alembic/versions/314f1ea54dc4_add_postgres_full_text_search_support_.py +131 -0
- basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +15 -3
- basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +44 -36
- basic_memory/alembic/versions/6830751f5fb6_merge_multiple_heads.py +24 -0
- basic_memory/alembic/versions/a2b3c4d5e6f7_add_search_index_entity_cascade.py +56 -0
- basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +13 -0
- basic_memory/alembic/versions/f8a9b2c3d4e5_add_pg_trgm_for_fuzzy_link_resolution.py +239 -0
- basic_memory/alembic/versions/g9a0b3c4d5e6_add_external_id_to_project_and_entity.py +173 -0
- basic_memory/api/app.py +45 -24
- basic_memory/api/container.py +133 -0
- basic_memory/api/routers/knowledge_router.py +17 -5
- basic_memory/api/routers/project_router.py +68 -14
- basic_memory/api/routers/resource_router.py +37 -27
- basic_memory/api/routers/utils.py +53 -14
- basic_memory/api/v2/__init__.py +35 -0
- basic_memory/api/v2/routers/__init__.py +21 -0
- basic_memory/api/v2/routers/directory_router.py +93 -0
- basic_memory/api/v2/routers/importer_router.py +181 -0
- basic_memory/api/v2/routers/knowledge_router.py +427 -0
- basic_memory/api/v2/routers/memory_router.py +130 -0
- basic_memory/api/v2/routers/project_router.py +359 -0
- basic_memory/api/v2/routers/prompt_router.py +269 -0
- basic_memory/api/v2/routers/resource_router.py +286 -0
- basic_memory/api/v2/routers/search_router.py +73 -0
- basic_memory/cli/app.py +43 -7
- basic_memory/cli/auth.py +27 -4
- basic_memory/cli/commands/__init__.py +3 -1
- basic_memory/cli/commands/cloud/api_client.py +20 -5
- basic_memory/cli/commands/cloud/cloud_utils.py +13 -6
- basic_memory/cli/commands/cloud/rclone_commands.py +110 -14
- basic_memory/cli/commands/cloud/rclone_installer.py +18 -4
- basic_memory/cli/commands/cloud/upload.py +10 -3
- basic_memory/cli/commands/command_utils.py +52 -4
- basic_memory/cli/commands/db.py +78 -19
- basic_memory/cli/commands/format.py +198 -0
- basic_memory/cli/commands/import_chatgpt.py +12 -8
- basic_memory/cli/commands/import_claude_conversations.py +12 -8
- basic_memory/cli/commands/import_claude_projects.py +12 -8
- basic_memory/cli/commands/import_memory_json.py +12 -8
- basic_memory/cli/commands/mcp.py +8 -26
- basic_memory/cli/commands/project.py +22 -9
- basic_memory/cli/commands/status.py +3 -2
- basic_memory/cli/commands/telemetry.py +81 -0
- basic_memory/cli/container.py +84 -0
- basic_memory/cli/main.py +7 -0
- basic_memory/config.py +177 -77
- basic_memory/db.py +183 -77
- basic_memory/deps/__init__.py +293 -0
- basic_memory/deps/config.py +26 -0
- basic_memory/deps/db.py +56 -0
- basic_memory/deps/importers.py +200 -0
- basic_memory/deps/projects.py +238 -0
- basic_memory/deps/repositories.py +179 -0
- basic_memory/deps/services.py +480 -0
- basic_memory/deps.py +14 -409
- basic_memory/file_utils.py +212 -3
- basic_memory/ignore_utils.py +5 -5
- basic_memory/importers/base.py +40 -19
- basic_memory/importers/chatgpt_importer.py +17 -4
- basic_memory/importers/claude_conversations_importer.py +27 -12
- basic_memory/importers/claude_projects_importer.py +50 -14
- basic_memory/importers/memory_json_importer.py +36 -16
- basic_memory/importers/utils.py +5 -2
- basic_memory/markdown/entity_parser.py +62 -23
- basic_memory/markdown/markdown_processor.py +67 -4
- basic_memory/markdown/plugins.py +4 -2
- basic_memory/markdown/utils.py +10 -1
- basic_memory/mcp/async_client.py +1 -0
- basic_memory/mcp/clients/__init__.py +28 -0
- basic_memory/mcp/clients/directory.py +70 -0
- basic_memory/mcp/clients/knowledge.py +176 -0
- basic_memory/mcp/clients/memory.py +120 -0
- basic_memory/mcp/clients/project.py +89 -0
- basic_memory/mcp/clients/resource.py +71 -0
- basic_memory/mcp/clients/search.py +65 -0
- basic_memory/mcp/container.py +110 -0
- basic_memory/mcp/project_context.py +47 -33
- basic_memory/mcp/prompts/ai_assistant_guide.py +2 -2
- basic_memory/mcp/prompts/recent_activity.py +2 -2
- basic_memory/mcp/prompts/utils.py +3 -3
- basic_memory/mcp/server.py +58 -0
- basic_memory/mcp/tools/build_context.py +14 -14
- basic_memory/mcp/tools/canvas.py +34 -12
- basic_memory/mcp/tools/chatgpt_tools.py +4 -1
- basic_memory/mcp/tools/delete_note.py +31 -7
- basic_memory/mcp/tools/edit_note.py +14 -9
- basic_memory/mcp/tools/list_directory.py +7 -17
- basic_memory/mcp/tools/move_note.py +35 -31
- basic_memory/mcp/tools/project_management.py +29 -25
- basic_memory/mcp/tools/read_content.py +13 -3
- basic_memory/mcp/tools/read_note.py +24 -14
- basic_memory/mcp/tools/recent_activity.py +32 -38
- basic_memory/mcp/tools/search.py +17 -10
- basic_memory/mcp/tools/utils.py +28 -0
- basic_memory/mcp/tools/view_note.py +2 -1
- basic_memory/mcp/tools/write_note.py +37 -14
- basic_memory/models/knowledge.py +15 -2
- basic_memory/models/project.py +7 -1
- basic_memory/models/search.py +58 -2
- basic_memory/project_resolver.py +222 -0
- basic_memory/repository/entity_repository.py +210 -3
- basic_memory/repository/observation_repository.py +1 -0
- basic_memory/repository/postgres_search_repository.py +451 -0
- basic_memory/repository/project_repository.py +38 -1
- basic_memory/repository/relation_repository.py +58 -2
- basic_memory/repository/repository.py +1 -0
- basic_memory/repository/search_index_row.py +95 -0
- basic_memory/repository/search_repository.py +77 -615
- basic_memory/repository/search_repository_base.py +241 -0
- basic_memory/repository/sqlite_search_repository.py +437 -0
- basic_memory/runtime.py +61 -0
- basic_memory/schemas/base.py +36 -6
- basic_memory/schemas/directory.py +2 -1
- basic_memory/schemas/memory.py +9 -2
- basic_memory/schemas/project_info.py +2 -0
- basic_memory/schemas/response.py +84 -27
- basic_memory/schemas/search.py +5 -0
- basic_memory/schemas/sync_report.py +1 -1
- basic_memory/schemas/v2/__init__.py +27 -0
- basic_memory/schemas/v2/entity.py +133 -0
- basic_memory/schemas/v2/resource.py +47 -0
- basic_memory/services/context_service.py +219 -43
- basic_memory/services/directory_service.py +26 -11
- basic_memory/services/entity_service.py +68 -33
- basic_memory/services/file_service.py +131 -16
- basic_memory/services/initialization.py +51 -26
- basic_memory/services/link_resolver.py +1 -0
- basic_memory/services/project_service.py +68 -43
- basic_memory/services/search_service.py +75 -16
- basic_memory/sync/__init__.py +2 -1
- basic_memory/sync/coordinator.py +160 -0
- basic_memory/sync/sync_service.py +135 -115
- basic_memory/sync/watch_service.py +32 -12
- basic_memory/telemetry.py +249 -0
- basic_memory/utils.py +96 -75
- {basic_memory-0.16.1.dist-info → basic_memory-0.17.4.dist-info}/METADATA +129 -5
- basic_memory-0.17.4.dist-info/RECORD +193 -0
- {basic_memory-0.16.1.dist-info → basic_memory-0.17.4.dist-info}/WHEEL +1 -1
- basic_memory-0.16.1.dist-info/RECORD +0 -148
- {basic_memory-0.16.1.dist-info → basic_memory-0.17.4.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.16.1.dist-info → basic_memory-0.17.4.dist-info}/licenses/LICENSE +0 -0
basic_memory/deps/db.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Database dependency injection for basic-memory.
|
|
2
|
+
|
|
3
|
+
This module provides database-related dependencies:
|
|
4
|
+
- Engine and session maker factories
|
|
5
|
+
- Session dependencies for request handling
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
from fastapi import Depends, Request
|
|
11
|
+
from loguru import logger
|
|
12
|
+
from sqlalchemy.ext.asyncio import (
|
|
13
|
+
AsyncEngine,
|
|
14
|
+
AsyncSession,
|
|
15
|
+
async_sessionmaker,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from basic_memory import db
|
|
19
|
+
from basic_memory.deps.config import get_app_config
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def get_engine_factory(
|
|
23
|
+
request: Request,
|
|
24
|
+
) -> tuple[AsyncEngine, async_sessionmaker[AsyncSession]]: # pragma: no cover
|
|
25
|
+
"""Get cached engine and session maker from app state.
|
|
26
|
+
|
|
27
|
+
For API requests, returns cached connections from app.state for optimal performance.
|
|
28
|
+
For non-API contexts (CLI), falls back to direct database connection.
|
|
29
|
+
"""
|
|
30
|
+
# Try to get cached connections from app state (API context)
|
|
31
|
+
if (
|
|
32
|
+
hasattr(request, "app")
|
|
33
|
+
and hasattr(request.app.state, "engine")
|
|
34
|
+
and hasattr(request.app.state, "session_maker")
|
|
35
|
+
):
|
|
36
|
+
return request.app.state.engine, request.app.state.session_maker
|
|
37
|
+
|
|
38
|
+
# Fallback for non-API contexts (CLI)
|
|
39
|
+
logger.debug("Using fallback database connection for non-API context")
|
|
40
|
+
app_config = get_app_config()
|
|
41
|
+
engine, session_maker = await db.get_or_create_db(app_config.database_path)
|
|
42
|
+
return engine, session_maker
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
EngineFactoryDep = Annotated[
|
|
46
|
+
tuple[AsyncEngine, async_sessionmaker[AsyncSession]], Depends(get_engine_factory)
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def get_session_maker(engine_factory: EngineFactoryDep) -> async_sessionmaker[AsyncSession]:
|
|
51
|
+
"""Get session maker."""
|
|
52
|
+
_, session_maker = engine_factory
|
|
53
|
+
return session_maker
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
SessionMakerDep = Annotated[async_sessionmaker, Depends(get_session_maker)]
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Importer dependency injection for basic-memory.
|
|
2
|
+
|
|
3
|
+
This module provides importer dependencies:
|
|
4
|
+
- ChatGPTImporter
|
|
5
|
+
- ClaudeConversationsImporter
|
|
6
|
+
- ClaudeProjectsImporter
|
|
7
|
+
- MemoryJsonImporter
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
from fastapi import Depends
|
|
13
|
+
|
|
14
|
+
from basic_memory.deps.projects import (
|
|
15
|
+
ProjectConfigDep,
|
|
16
|
+
ProjectConfigV2Dep,
|
|
17
|
+
ProjectConfigV2ExternalDep,
|
|
18
|
+
)
|
|
19
|
+
from basic_memory.deps.services import (
|
|
20
|
+
FileServiceDep,
|
|
21
|
+
FileServiceV2Dep,
|
|
22
|
+
FileServiceV2ExternalDep,
|
|
23
|
+
MarkdownProcessorDep,
|
|
24
|
+
MarkdownProcessorV2Dep,
|
|
25
|
+
MarkdownProcessorV2ExternalDep,
|
|
26
|
+
)
|
|
27
|
+
from basic_memory.importers import (
|
|
28
|
+
ChatGPTImporter,
|
|
29
|
+
ClaudeConversationsImporter,
|
|
30
|
+
ClaudeProjectsImporter,
|
|
31
|
+
MemoryJsonImporter,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# --- ChatGPT Importer ---
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def get_chatgpt_importer(
|
|
39
|
+
project_config: ProjectConfigDep,
|
|
40
|
+
markdown_processor: MarkdownProcessorDep,
|
|
41
|
+
file_service: FileServiceDep,
|
|
42
|
+
) -> ChatGPTImporter:
|
|
43
|
+
"""Create ChatGPTImporter with dependencies."""
|
|
44
|
+
return ChatGPTImporter(project_config.home, markdown_processor, file_service)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
ChatGPTImporterDep = Annotated[ChatGPTImporter, Depends(get_chatgpt_importer)]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def get_chatgpt_importer_v2( # pragma: no cover
|
|
51
|
+
project_config: ProjectConfigV2Dep,
|
|
52
|
+
markdown_processor: MarkdownProcessorV2Dep,
|
|
53
|
+
file_service: FileServiceV2Dep,
|
|
54
|
+
) -> ChatGPTImporter:
|
|
55
|
+
"""Create ChatGPTImporter with v2 dependencies."""
|
|
56
|
+
return ChatGPTImporter(project_config.home, markdown_processor, file_service)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
ChatGPTImporterV2Dep = Annotated[ChatGPTImporter, Depends(get_chatgpt_importer_v2)]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def get_chatgpt_importer_v2_external(
|
|
63
|
+
project_config: ProjectConfigV2ExternalDep,
|
|
64
|
+
markdown_processor: MarkdownProcessorV2ExternalDep,
|
|
65
|
+
file_service: FileServiceV2ExternalDep,
|
|
66
|
+
) -> ChatGPTImporter:
|
|
67
|
+
"""Create ChatGPTImporter with v2 external_id dependencies."""
|
|
68
|
+
return ChatGPTImporter(project_config.home, markdown_processor, file_service)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
ChatGPTImporterV2ExternalDep = Annotated[ChatGPTImporter, Depends(get_chatgpt_importer_v2_external)]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# --- Claude Conversations Importer ---
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def get_claude_conversations_importer(
|
|
78
|
+
project_config: ProjectConfigDep,
|
|
79
|
+
markdown_processor: MarkdownProcessorDep,
|
|
80
|
+
file_service: FileServiceDep,
|
|
81
|
+
) -> ClaudeConversationsImporter:
|
|
82
|
+
"""Create ClaudeConversationsImporter with dependencies."""
|
|
83
|
+
return ClaudeConversationsImporter(project_config.home, markdown_processor, file_service)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
ClaudeConversationsImporterDep = Annotated[
|
|
87
|
+
ClaudeConversationsImporter, Depends(get_claude_conversations_importer)
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def get_claude_conversations_importer_v2( # pragma: no cover
|
|
92
|
+
project_config: ProjectConfigV2Dep,
|
|
93
|
+
markdown_processor: MarkdownProcessorV2Dep,
|
|
94
|
+
file_service: FileServiceV2Dep,
|
|
95
|
+
) -> ClaudeConversationsImporter:
|
|
96
|
+
"""Create ClaudeConversationsImporter with v2 dependencies."""
|
|
97
|
+
return ClaudeConversationsImporter(project_config.home, markdown_processor, file_service)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
ClaudeConversationsImporterV2Dep = Annotated[
|
|
101
|
+
ClaudeConversationsImporter, Depends(get_claude_conversations_importer_v2)
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def get_claude_conversations_importer_v2_external(
|
|
106
|
+
project_config: ProjectConfigV2ExternalDep,
|
|
107
|
+
markdown_processor: MarkdownProcessorV2ExternalDep,
|
|
108
|
+
file_service: FileServiceV2ExternalDep,
|
|
109
|
+
) -> ClaudeConversationsImporter:
|
|
110
|
+
"""Create ClaudeConversationsImporter with v2 external_id dependencies."""
|
|
111
|
+
return ClaudeConversationsImporter(project_config.home, markdown_processor, file_service)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
ClaudeConversationsImporterV2ExternalDep = Annotated[
|
|
115
|
+
ClaudeConversationsImporter, Depends(get_claude_conversations_importer_v2_external)
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# --- Claude Projects Importer ---
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def get_claude_projects_importer(
|
|
123
|
+
project_config: ProjectConfigDep,
|
|
124
|
+
markdown_processor: MarkdownProcessorDep,
|
|
125
|
+
file_service: FileServiceDep,
|
|
126
|
+
) -> ClaudeProjectsImporter:
|
|
127
|
+
"""Create ClaudeProjectsImporter with dependencies."""
|
|
128
|
+
return ClaudeProjectsImporter(project_config.home, markdown_processor, file_service)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
ClaudeProjectsImporterDep = Annotated[ClaudeProjectsImporter, Depends(get_claude_projects_importer)]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
async def get_claude_projects_importer_v2( # pragma: no cover
|
|
135
|
+
project_config: ProjectConfigV2Dep,
|
|
136
|
+
markdown_processor: MarkdownProcessorV2Dep,
|
|
137
|
+
file_service: FileServiceV2Dep,
|
|
138
|
+
) -> ClaudeProjectsImporter:
|
|
139
|
+
"""Create ClaudeProjectsImporter with v2 dependencies."""
|
|
140
|
+
return ClaudeProjectsImporter(project_config.home, markdown_processor, file_service)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
ClaudeProjectsImporterV2Dep = Annotated[
|
|
144
|
+
ClaudeProjectsImporter, Depends(get_claude_projects_importer_v2)
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def get_claude_projects_importer_v2_external(
|
|
149
|
+
project_config: ProjectConfigV2ExternalDep,
|
|
150
|
+
markdown_processor: MarkdownProcessorV2ExternalDep,
|
|
151
|
+
file_service: FileServiceV2ExternalDep,
|
|
152
|
+
) -> ClaudeProjectsImporter:
|
|
153
|
+
"""Create ClaudeProjectsImporter with v2 external_id dependencies."""
|
|
154
|
+
return ClaudeProjectsImporter(project_config.home, markdown_processor, file_service)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
ClaudeProjectsImporterV2ExternalDep = Annotated[
|
|
158
|
+
ClaudeProjectsImporter, Depends(get_claude_projects_importer_v2_external)
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# --- Memory JSON Importer ---
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def get_memory_json_importer(
|
|
166
|
+
project_config: ProjectConfigDep,
|
|
167
|
+
markdown_processor: MarkdownProcessorDep,
|
|
168
|
+
file_service: FileServiceDep,
|
|
169
|
+
) -> MemoryJsonImporter:
|
|
170
|
+
"""Create MemoryJsonImporter with dependencies."""
|
|
171
|
+
return MemoryJsonImporter(project_config.home, markdown_processor, file_service)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
MemoryJsonImporterDep = Annotated[MemoryJsonImporter, Depends(get_memory_json_importer)]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def get_memory_json_importer_v2( # pragma: no cover
|
|
178
|
+
project_config: ProjectConfigV2Dep,
|
|
179
|
+
markdown_processor: MarkdownProcessorV2Dep,
|
|
180
|
+
file_service: FileServiceV2Dep,
|
|
181
|
+
) -> MemoryJsonImporter:
|
|
182
|
+
"""Create MemoryJsonImporter with v2 dependencies."""
|
|
183
|
+
return MemoryJsonImporter(project_config.home, markdown_processor, file_service)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
MemoryJsonImporterV2Dep = Annotated[MemoryJsonImporter, Depends(get_memory_json_importer_v2)]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
async def get_memory_json_importer_v2_external(
|
|
190
|
+
project_config: ProjectConfigV2ExternalDep,
|
|
191
|
+
markdown_processor: MarkdownProcessorV2ExternalDep,
|
|
192
|
+
file_service: FileServiceV2ExternalDep,
|
|
193
|
+
) -> MemoryJsonImporter:
|
|
194
|
+
"""Create MemoryJsonImporter with v2 external_id dependencies."""
|
|
195
|
+
return MemoryJsonImporter(project_config.home, markdown_processor, file_service)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
MemoryJsonImporterV2ExternalDep = Annotated[
|
|
199
|
+
MemoryJsonImporter, Depends(get_memory_json_importer_v2_external)
|
|
200
|
+
]
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Project dependency injection for basic-memory.
|
|
2
|
+
|
|
3
|
+
This module provides project-related dependencies:
|
|
4
|
+
- Project path extraction from URL
|
|
5
|
+
- Project config resolution
|
|
6
|
+
- Project ID validation
|
|
7
|
+
- Project repository
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import pathlib
|
|
11
|
+
from typing import Annotated
|
|
12
|
+
|
|
13
|
+
from fastapi import Depends, HTTPException, Path, status
|
|
14
|
+
|
|
15
|
+
from basic_memory.config import ProjectConfig
|
|
16
|
+
from basic_memory.deps.db import SessionMakerDep
|
|
17
|
+
from basic_memory.repository.project_repository import ProjectRepository
|
|
18
|
+
from basic_memory.utils import generate_permalink
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# --- Project Repository ---
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def get_project_repository(
|
|
25
|
+
session_maker: SessionMakerDep,
|
|
26
|
+
) -> ProjectRepository:
|
|
27
|
+
"""Get the project repository."""
|
|
28
|
+
return ProjectRepository(session_maker)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
ProjectRepositoryDep = Annotated[ProjectRepository, Depends(get_project_repository)]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# --- Path Extraction ---
|
|
35
|
+
|
|
36
|
+
# V1 API: Project name from URL path
|
|
37
|
+
ProjectPathDep = Annotated[str, Path()]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# --- Project ID Resolution (V1 API) ---
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def get_project_id(
|
|
44
|
+
project_repository: ProjectRepositoryDep,
|
|
45
|
+
project: ProjectPathDep,
|
|
46
|
+
) -> int:
|
|
47
|
+
"""Get the current project ID from request state.
|
|
48
|
+
|
|
49
|
+
When using sub-applications with /{project} mounting, the project value
|
|
50
|
+
is stored in request.state by middleware.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
project_repository: Repository for project operations
|
|
54
|
+
project: The project name from URL path
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The resolved project ID
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
HTTPException: If project is not found
|
|
61
|
+
"""
|
|
62
|
+
# Convert project name to permalink for lookup
|
|
63
|
+
project_permalink = generate_permalink(str(project))
|
|
64
|
+
project_obj = await project_repository.get_by_permalink(project_permalink)
|
|
65
|
+
if project_obj:
|
|
66
|
+
return project_obj.id
|
|
67
|
+
|
|
68
|
+
# Try by name if permalink lookup fails
|
|
69
|
+
project_obj = await project_repository.get_by_name(str(project)) # pragma: no cover
|
|
70
|
+
if project_obj: # pragma: no cover
|
|
71
|
+
return project_obj.id
|
|
72
|
+
|
|
73
|
+
# Not found
|
|
74
|
+
raise HTTPException( # pragma: no cover
|
|
75
|
+
status_code=status.HTTP_404_NOT_FOUND, detail=f"Project '{project}' not found."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
ProjectIdDep = Annotated[int, Depends(get_project_id)]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --- Project Config Resolution (V1 API) ---
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def get_project_config(
|
|
86
|
+
project: ProjectPathDep, project_repository: ProjectRepositoryDep
|
|
87
|
+
) -> ProjectConfig: # pragma: no cover
|
|
88
|
+
"""Get the current project referenced from request state.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
project: The project name from URL path
|
|
92
|
+
project_repository: Repository for project operations
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
The resolved project config
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
HTTPException: If project is not found
|
|
99
|
+
"""
|
|
100
|
+
# Convert project name to permalink for lookup
|
|
101
|
+
project_permalink = generate_permalink(str(project))
|
|
102
|
+
project_obj = await project_repository.get_by_permalink(project_permalink)
|
|
103
|
+
if project_obj:
|
|
104
|
+
return ProjectConfig(name=project_obj.name, home=pathlib.Path(project_obj.path))
|
|
105
|
+
|
|
106
|
+
# Not found
|
|
107
|
+
raise HTTPException( # pragma: no cover
|
|
108
|
+
status_code=status.HTTP_404_NOT_FOUND, detail=f"Project '{project}' not found."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
ProjectConfigDep = Annotated[ProjectConfig, Depends(get_project_config)]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# --- V2 API: Integer Project ID from Path ---
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def validate_project_id(
|
|
119
|
+
project_id: int,
|
|
120
|
+
project_repository: ProjectRepositoryDep,
|
|
121
|
+
) -> int:
|
|
122
|
+
"""Validate that a numeric project ID exists in the database.
|
|
123
|
+
|
|
124
|
+
This is used for v2 API endpoints that take project IDs as integers in the path.
|
|
125
|
+
The project_id parameter will be automatically extracted from the URL path by FastAPI.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
project_id: The numeric project ID from the URL path
|
|
129
|
+
project_repository: Repository for project operations
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The validated project ID
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
HTTPException: If project with that ID is not found
|
|
136
|
+
"""
|
|
137
|
+
project_obj = await project_repository.get_by_id(project_id)
|
|
138
|
+
if not project_obj:
|
|
139
|
+
raise HTTPException(
|
|
140
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
141
|
+
detail=f"Project with ID {project_id} not found.",
|
|
142
|
+
)
|
|
143
|
+
return project_id
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
ProjectIdPathDep = Annotated[int, Depends(validate_project_id)]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def get_project_config_v2(
|
|
150
|
+
project_id: ProjectIdPathDep, project_repository: ProjectRepositoryDep
|
|
151
|
+
) -> ProjectConfig: # pragma: no cover
|
|
152
|
+
"""Get the project config for v2 API (uses integer project_id from path).
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
project_id: The validated numeric project ID from the URL path
|
|
156
|
+
project_repository: Repository for project operations
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
The resolved project config
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
HTTPException: If project is not found
|
|
163
|
+
"""
|
|
164
|
+
project_obj = await project_repository.get_by_id(project_id)
|
|
165
|
+
if project_obj:
|
|
166
|
+
return ProjectConfig(name=project_obj.name, home=pathlib.Path(project_obj.path))
|
|
167
|
+
|
|
168
|
+
# Not found (this should not happen since ProjectIdPathDep already validates existence)
|
|
169
|
+
raise HTTPException( # pragma: no cover
|
|
170
|
+
status_code=status.HTTP_404_NOT_FOUND, detail=f"Project with ID {project_id} not found."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
ProjectConfigV2Dep = Annotated[ProjectConfig, Depends(get_project_config_v2)]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# --- V2 API: External UUID Project ID from Path ---
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def validate_project_external_id(
|
|
181
|
+
project_id: str,
|
|
182
|
+
project_repository: ProjectRepositoryDep,
|
|
183
|
+
) -> int:
|
|
184
|
+
"""Validate that a project external_id (UUID) exists in the database.
|
|
185
|
+
|
|
186
|
+
This is used for v2 API endpoints that take project external_ids as strings in the path.
|
|
187
|
+
The project_id parameter will be automatically extracted from the URL path by FastAPI.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
project_id: The external UUID from the URL path (named project_id for URL consistency)
|
|
191
|
+
project_repository: Repository for project operations
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The internal numeric project ID (for use by repositories)
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
HTTPException: If project with that external_id is not found
|
|
198
|
+
"""
|
|
199
|
+
project_obj = await project_repository.get_by_external_id(project_id)
|
|
200
|
+
if not project_obj:
|
|
201
|
+
raise HTTPException(
|
|
202
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
203
|
+
detail=f"Project with external_id '{project_id}' not found.",
|
|
204
|
+
)
|
|
205
|
+
return project_obj.id
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
ProjectExternalIdPathDep = Annotated[int, Depends(validate_project_external_id)]
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def get_project_config_v2_external(
|
|
212
|
+
project_id: ProjectExternalIdPathDep, project_repository: ProjectRepositoryDep
|
|
213
|
+
) -> ProjectConfig: # pragma: no cover
|
|
214
|
+
"""Get the project config for v2 API (uses external_id UUID from path).
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
project_id: The internal project ID resolved from external_id
|
|
218
|
+
project_repository: Repository for project operations
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
The resolved project config
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
HTTPException: If project is not found
|
|
225
|
+
"""
|
|
226
|
+
project_obj = await project_repository.get_by_id(project_id)
|
|
227
|
+
if project_obj:
|
|
228
|
+
return ProjectConfig(name=project_obj.name, home=pathlib.Path(project_obj.path))
|
|
229
|
+
|
|
230
|
+
# Not found (this should not happen since ProjectExternalIdPathDep already validates)
|
|
231
|
+
raise HTTPException( # pragma: no cover
|
|
232
|
+
status_code=status.HTTP_404_NOT_FOUND, detail=f"Project with ID {project_id} not found."
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
ProjectConfigV2ExternalDep = Annotated[
|
|
237
|
+
ProjectConfig, Depends(get_project_config_v2_external)
|
|
238
|
+
]
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Repository dependency injection for basic-memory.
|
|
2
|
+
|
|
3
|
+
This module provides repository dependencies:
|
|
4
|
+
- EntityRepository
|
|
5
|
+
- ObservationRepository
|
|
6
|
+
- RelationRepository
|
|
7
|
+
- SearchRepository
|
|
8
|
+
|
|
9
|
+
Each repository is scoped to a project ID from the request.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
from fastapi import Depends
|
|
15
|
+
|
|
16
|
+
from basic_memory.deps.db import SessionMakerDep
|
|
17
|
+
from basic_memory.deps.projects import (
|
|
18
|
+
ProjectIdDep,
|
|
19
|
+
ProjectIdPathDep,
|
|
20
|
+
ProjectExternalIdPathDep,
|
|
21
|
+
)
|
|
22
|
+
from basic_memory.repository.entity_repository import EntityRepository
|
|
23
|
+
from basic_memory.repository.observation_repository import ObservationRepository
|
|
24
|
+
from basic_memory.repository.relation_repository import RelationRepository
|
|
25
|
+
from basic_memory.repository.search_repository import SearchRepository, create_search_repository
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# --- Entity Repository ---
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def get_entity_repository(
|
|
32
|
+
session_maker: SessionMakerDep,
|
|
33
|
+
project_id: ProjectIdDep,
|
|
34
|
+
) -> EntityRepository:
|
|
35
|
+
"""Create an EntityRepository instance for the current project."""
|
|
36
|
+
return EntityRepository(session_maker, project_id=project_id)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
EntityRepositoryDep = Annotated[EntityRepository, Depends(get_entity_repository)]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def get_entity_repository_v2( # pragma: no cover
|
|
43
|
+
session_maker: SessionMakerDep,
|
|
44
|
+
project_id: ProjectIdPathDep,
|
|
45
|
+
) -> EntityRepository:
|
|
46
|
+
"""Create an EntityRepository instance for v2 API (uses integer project_id from path)."""
|
|
47
|
+
return EntityRepository(session_maker, project_id=project_id)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
EntityRepositoryV2Dep = Annotated[EntityRepository, Depends(get_entity_repository_v2)]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def get_entity_repository_v2_external(
|
|
54
|
+
session_maker: SessionMakerDep,
|
|
55
|
+
project_id: ProjectExternalIdPathDep,
|
|
56
|
+
) -> EntityRepository:
|
|
57
|
+
"""Create an EntityRepository instance for v2 API (uses external_id from path)."""
|
|
58
|
+
return EntityRepository(session_maker, project_id=project_id)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
EntityRepositoryV2ExternalDep = Annotated[EntityRepository, Depends(get_entity_repository_v2_external)]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# --- Observation Repository ---
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def get_observation_repository(
|
|
68
|
+
session_maker: SessionMakerDep,
|
|
69
|
+
project_id: ProjectIdDep,
|
|
70
|
+
) -> ObservationRepository:
|
|
71
|
+
"""Create an ObservationRepository instance for the current project."""
|
|
72
|
+
return ObservationRepository(session_maker, project_id=project_id)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
ObservationRepositoryDep = Annotated[ObservationRepository, Depends(get_observation_repository)]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
async def get_observation_repository_v2( # pragma: no cover
|
|
79
|
+
session_maker: SessionMakerDep,
|
|
80
|
+
project_id: ProjectIdPathDep,
|
|
81
|
+
) -> ObservationRepository:
|
|
82
|
+
"""Create an ObservationRepository instance for v2 API."""
|
|
83
|
+
return ObservationRepository(session_maker, project_id=project_id)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
ObservationRepositoryV2Dep = Annotated[
|
|
87
|
+
ObservationRepository, Depends(get_observation_repository_v2)
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def get_observation_repository_v2_external(
|
|
92
|
+
session_maker: SessionMakerDep,
|
|
93
|
+
project_id: ProjectExternalIdPathDep,
|
|
94
|
+
) -> ObservationRepository:
|
|
95
|
+
"""Create an ObservationRepository instance for v2 API (uses external_id)."""
|
|
96
|
+
return ObservationRepository(session_maker, project_id=project_id)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
ObservationRepositoryV2ExternalDep = Annotated[
|
|
100
|
+
ObservationRepository, Depends(get_observation_repository_v2_external)
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# --- Relation Repository ---
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def get_relation_repository(
|
|
108
|
+
session_maker: SessionMakerDep,
|
|
109
|
+
project_id: ProjectIdDep,
|
|
110
|
+
) -> RelationRepository:
|
|
111
|
+
"""Create a RelationRepository instance for the current project."""
|
|
112
|
+
return RelationRepository(session_maker, project_id=project_id)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
RelationRepositoryDep = Annotated[RelationRepository, Depends(get_relation_repository)]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def get_relation_repository_v2( # pragma: no cover
|
|
119
|
+
session_maker: SessionMakerDep,
|
|
120
|
+
project_id: ProjectIdPathDep,
|
|
121
|
+
) -> RelationRepository:
|
|
122
|
+
"""Create a RelationRepository instance for v2 API."""
|
|
123
|
+
return RelationRepository(session_maker, project_id=project_id)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
RelationRepositoryV2Dep = Annotated[RelationRepository, Depends(get_relation_repository_v2)]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def get_relation_repository_v2_external(
|
|
130
|
+
session_maker: SessionMakerDep,
|
|
131
|
+
project_id: ProjectExternalIdPathDep,
|
|
132
|
+
) -> RelationRepository:
|
|
133
|
+
"""Create a RelationRepository instance for v2 API (uses external_id)."""
|
|
134
|
+
return RelationRepository(session_maker, project_id=project_id)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
RelationRepositoryV2ExternalDep = Annotated[
|
|
138
|
+
RelationRepository, Depends(get_relation_repository_v2_external)
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# --- Search Repository ---
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async def get_search_repository(
|
|
146
|
+
session_maker: SessionMakerDep,
|
|
147
|
+
project_id: ProjectIdDep,
|
|
148
|
+
) -> SearchRepository:
|
|
149
|
+
"""Create a backend-specific SearchRepository instance for the current project.
|
|
150
|
+
|
|
151
|
+
Uses factory function to return SQLiteSearchRepository or PostgresSearchRepository
|
|
152
|
+
based on database backend configuration.
|
|
153
|
+
"""
|
|
154
|
+
return create_search_repository(session_maker, project_id=project_id)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
SearchRepositoryDep = Annotated[SearchRepository, Depends(get_search_repository)]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def get_search_repository_v2( # pragma: no cover
|
|
161
|
+
session_maker: SessionMakerDep,
|
|
162
|
+
project_id: ProjectIdPathDep,
|
|
163
|
+
) -> SearchRepository:
|
|
164
|
+
"""Create a SearchRepository instance for v2 API."""
|
|
165
|
+
return create_search_repository(session_maker, project_id=project_id)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
SearchRepositoryV2Dep = Annotated[SearchRepository, Depends(get_search_repository_v2)]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
async def get_search_repository_v2_external(
|
|
172
|
+
session_maker: SessionMakerDep,
|
|
173
|
+
project_id: ProjectExternalIdPathDep,
|
|
174
|
+
) -> SearchRepository:
|
|
175
|
+
"""Create a SearchRepository instance for v2 API (uses external_id)."""
|
|
176
|
+
return create_search_repository(session_maker, project_id=project_id)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
SearchRepositoryV2ExternalDep = Annotated[SearchRepository, Depends(get_search_repository_v2_external)]
|