basic-memory 0.17.1__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.
- basic_memory/__init__.py +7 -0
- basic_memory/alembic/alembic.ini +119 -0
- basic_memory/alembic/env.py +185 -0
- basic_memory/alembic/migrations.py +24 -0
- basic_memory/alembic/script.py.mako +26 -0
- basic_memory/alembic/versions/314f1ea54dc4_add_postgres_full_text_search_support_.py +131 -0
- basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +93 -0
- basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
- basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +120 -0
- basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +112 -0
- basic_memory/alembic/versions/9d9c1cb7d8f5_add_mtime_and_size_columns_to_entity_.py +49 -0
- basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +49 -0
- basic_memory/alembic/versions/a2b3c4d5e6f7_add_search_index_entity_cascade.py +56 -0
- basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
- basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +113 -0
- basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
- basic_memory/alembic/versions/f8a9b2c3d4e5_add_pg_trgm_for_fuzzy_link_resolution.py +239 -0
- basic_memory/api/__init__.py +5 -0
- basic_memory/api/app.py +131 -0
- basic_memory/api/routers/__init__.py +11 -0
- basic_memory/api/routers/directory_router.py +84 -0
- basic_memory/api/routers/importer_router.py +152 -0
- basic_memory/api/routers/knowledge_router.py +318 -0
- basic_memory/api/routers/management_router.py +80 -0
- basic_memory/api/routers/memory_router.py +90 -0
- basic_memory/api/routers/project_router.py +448 -0
- basic_memory/api/routers/prompt_router.py +260 -0
- basic_memory/api/routers/resource_router.py +249 -0
- basic_memory/api/routers/search_router.py +36 -0
- basic_memory/api/routers/utils.py +169 -0
- basic_memory/api/template_loader.py +292 -0
- 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 +182 -0
- basic_memory/api/v2/routers/knowledge_router.py +413 -0
- basic_memory/api/v2/routers/memory_router.py +130 -0
- basic_memory/api/v2/routers/project_router.py +342 -0
- basic_memory/api/v2/routers/prompt_router.py +270 -0
- basic_memory/api/v2/routers/resource_router.py +286 -0
- basic_memory/api/v2/routers/search_router.py +73 -0
- basic_memory/cli/__init__.py +1 -0
- basic_memory/cli/app.py +84 -0
- basic_memory/cli/auth.py +277 -0
- basic_memory/cli/commands/__init__.py +18 -0
- basic_memory/cli/commands/cloud/__init__.py +6 -0
- basic_memory/cli/commands/cloud/api_client.py +112 -0
- basic_memory/cli/commands/cloud/bisync_commands.py +110 -0
- basic_memory/cli/commands/cloud/cloud_utils.py +101 -0
- basic_memory/cli/commands/cloud/core_commands.py +195 -0
- basic_memory/cli/commands/cloud/rclone_commands.py +371 -0
- basic_memory/cli/commands/cloud/rclone_config.py +110 -0
- basic_memory/cli/commands/cloud/rclone_installer.py +263 -0
- basic_memory/cli/commands/cloud/upload.py +233 -0
- basic_memory/cli/commands/cloud/upload_command.py +124 -0
- basic_memory/cli/commands/command_utils.py +77 -0
- basic_memory/cli/commands/db.py +44 -0
- basic_memory/cli/commands/format.py +198 -0
- basic_memory/cli/commands/import_chatgpt.py +84 -0
- basic_memory/cli/commands/import_claude_conversations.py +87 -0
- basic_memory/cli/commands/import_claude_projects.py +86 -0
- basic_memory/cli/commands/import_memory_json.py +87 -0
- basic_memory/cli/commands/mcp.py +76 -0
- basic_memory/cli/commands/project.py +889 -0
- basic_memory/cli/commands/status.py +174 -0
- basic_memory/cli/commands/telemetry.py +81 -0
- basic_memory/cli/commands/tool.py +341 -0
- basic_memory/cli/main.py +28 -0
- basic_memory/config.py +616 -0
- basic_memory/db.py +394 -0
- basic_memory/deps.py +705 -0
- basic_memory/file_utils.py +478 -0
- basic_memory/ignore_utils.py +297 -0
- basic_memory/importers/__init__.py +27 -0
- basic_memory/importers/base.py +79 -0
- basic_memory/importers/chatgpt_importer.py +232 -0
- basic_memory/importers/claude_conversations_importer.py +180 -0
- basic_memory/importers/claude_projects_importer.py +148 -0
- basic_memory/importers/memory_json_importer.py +108 -0
- basic_memory/importers/utils.py +61 -0
- basic_memory/markdown/__init__.py +21 -0
- basic_memory/markdown/entity_parser.py +279 -0
- basic_memory/markdown/markdown_processor.py +160 -0
- basic_memory/markdown/plugins.py +242 -0
- basic_memory/markdown/schemas.py +70 -0
- basic_memory/markdown/utils.py +117 -0
- basic_memory/mcp/__init__.py +1 -0
- basic_memory/mcp/async_client.py +139 -0
- basic_memory/mcp/project_context.py +141 -0
- basic_memory/mcp/prompts/__init__.py +19 -0
- basic_memory/mcp/prompts/ai_assistant_guide.py +70 -0
- basic_memory/mcp/prompts/continue_conversation.py +62 -0
- basic_memory/mcp/prompts/recent_activity.py +188 -0
- basic_memory/mcp/prompts/search.py +57 -0
- basic_memory/mcp/prompts/utils.py +162 -0
- basic_memory/mcp/resources/ai_assistant_guide.md +283 -0
- basic_memory/mcp/resources/project_info.py +71 -0
- basic_memory/mcp/server.py +81 -0
- basic_memory/mcp/tools/__init__.py +48 -0
- basic_memory/mcp/tools/build_context.py +120 -0
- basic_memory/mcp/tools/canvas.py +152 -0
- basic_memory/mcp/tools/chatgpt_tools.py +190 -0
- basic_memory/mcp/tools/delete_note.py +242 -0
- basic_memory/mcp/tools/edit_note.py +324 -0
- basic_memory/mcp/tools/list_directory.py +168 -0
- basic_memory/mcp/tools/move_note.py +551 -0
- basic_memory/mcp/tools/project_management.py +201 -0
- basic_memory/mcp/tools/read_content.py +281 -0
- basic_memory/mcp/tools/read_note.py +267 -0
- basic_memory/mcp/tools/recent_activity.py +534 -0
- basic_memory/mcp/tools/search.py +385 -0
- basic_memory/mcp/tools/utils.py +540 -0
- basic_memory/mcp/tools/view_note.py +78 -0
- basic_memory/mcp/tools/write_note.py +230 -0
- basic_memory/models/__init__.py +15 -0
- basic_memory/models/base.py +10 -0
- basic_memory/models/knowledge.py +226 -0
- basic_memory/models/project.py +87 -0
- basic_memory/models/search.py +85 -0
- basic_memory/repository/__init__.py +11 -0
- basic_memory/repository/entity_repository.py +503 -0
- basic_memory/repository/observation_repository.py +73 -0
- basic_memory/repository/postgres_search_repository.py +379 -0
- basic_memory/repository/project_info_repository.py +10 -0
- basic_memory/repository/project_repository.py +128 -0
- basic_memory/repository/relation_repository.py +146 -0
- basic_memory/repository/repository.py +385 -0
- basic_memory/repository/search_index_row.py +95 -0
- basic_memory/repository/search_repository.py +94 -0
- basic_memory/repository/search_repository_base.py +241 -0
- basic_memory/repository/sqlite_search_repository.py +439 -0
- basic_memory/schemas/__init__.py +86 -0
- basic_memory/schemas/base.py +297 -0
- basic_memory/schemas/cloud.py +50 -0
- basic_memory/schemas/delete.py +37 -0
- basic_memory/schemas/directory.py +30 -0
- basic_memory/schemas/importer.py +35 -0
- basic_memory/schemas/memory.py +285 -0
- basic_memory/schemas/project_info.py +212 -0
- basic_memory/schemas/prompt.py +90 -0
- basic_memory/schemas/request.py +112 -0
- basic_memory/schemas/response.py +229 -0
- basic_memory/schemas/search.py +117 -0
- basic_memory/schemas/sync_report.py +72 -0
- basic_memory/schemas/v2/__init__.py +27 -0
- basic_memory/schemas/v2/entity.py +129 -0
- basic_memory/schemas/v2/resource.py +46 -0
- basic_memory/services/__init__.py +8 -0
- basic_memory/services/context_service.py +601 -0
- basic_memory/services/directory_service.py +308 -0
- basic_memory/services/entity_service.py +864 -0
- basic_memory/services/exceptions.py +37 -0
- basic_memory/services/file_service.py +541 -0
- basic_memory/services/initialization.py +216 -0
- basic_memory/services/link_resolver.py +121 -0
- basic_memory/services/project_service.py +880 -0
- basic_memory/services/search_service.py +404 -0
- basic_memory/services/service.py +15 -0
- basic_memory/sync/__init__.py +6 -0
- basic_memory/sync/background_sync.py +26 -0
- basic_memory/sync/sync_service.py +1259 -0
- basic_memory/sync/watch_service.py +510 -0
- basic_memory/telemetry.py +249 -0
- basic_memory/templates/prompts/continue_conversation.hbs +110 -0
- basic_memory/templates/prompts/search.hbs +101 -0
- basic_memory/utils.py +468 -0
- basic_memory-0.17.1.dist-info/METADATA +617 -0
- basic_memory-0.17.1.dist-info/RECORD +171 -0
- basic_memory-0.17.1.dist-info/WHEEL +4 -0
- basic_memory-0.17.1.dist-info/entry_points.txt +3 -0
- basic_memory-0.17.1.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Response schemas for knowledge graph operations.
|
|
2
|
+
|
|
3
|
+
This module defines the response formats for all knowledge graph operations.
|
|
4
|
+
Each response includes complete information about the affected entities,
|
|
5
|
+
including IDs that can be used in subsequent operations.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
1. Every created/updated object gets an ID
|
|
9
|
+
2. Relations are included with their parent entities
|
|
10
|
+
3. Responses include everything needed for next operations
|
|
11
|
+
4. Bulk operations return all affected items
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import List, Optional, Dict
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, ConfigDict, Field, AliasPath, AliasChoices
|
|
18
|
+
|
|
19
|
+
from basic_memory.schemas.base import Relation, Permalink, EntityType, ContentType, Observation
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SQLAlchemyModel(BaseModel):
|
|
23
|
+
"""Base class for models that read from SQLAlchemy attributes.
|
|
24
|
+
|
|
25
|
+
This base class handles conversion of SQLAlchemy model attributes
|
|
26
|
+
to Pydantic model fields. All response models extend this to ensure
|
|
27
|
+
proper handling of database results.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
model_config = ConfigDict(from_attributes=True)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ObservationResponse(Observation, SQLAlchemyModel):
|
|
34
|
+
"""Schema for observation data returned from the service.
|
|
35
|
+
|
|
36
|
+
Each observation gets a unique ID that can be used for later
|
|
37
|
+
reference or deletion.
|
|
38
|
+
|
|
39
|
+
Example Response:
|
|
40
|
+
{
|
|
41
|
+
"category": "feature",
|
|
42
|
+
"content": "Added support for async operations",
|
|
43
|
+
"context": "Initial database design meeting"
|
|
44
|
+
}
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
permalink: Permalink
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RelationResponse(Relation, SQLAlchemyModel):
|
|
51
|
+
"""Response schema for relation operations.
|
|
52
|
+
|
|
53
|
+
Extends the base Relation model with a unique ID that can be
|
|
54
|
+
used for later modification or deletion.
|
|
55
|
+
|
|
56
|
+
Example Response:
|
|
57
|
+
{
|
|
58
|
+
"from_id": "test/memory_test",
|
|
59
|
+
"to_id": "component/memory-service",
|
|
60
|
+
"relation_type": "validates",
|
|
61
|
+
"context": "Comprehensive test suite"
|
|
62
|
+
}
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
permalink: Permalink
|
|
66
|
+
|
|
67
|
+
from_id: Permalink = Field(
|
|
68
|
+
# use the permalink from the associated Entity
|
|
69
|
+
# or the from_id value
|
|
70
|
+
validation_alias=AliasChoices(
|
|
71
|
+
AliasPath("from_entity", "permalink"),
|
|
72
|
+
"from_id",
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
to_id: Optional[Permalink] = Field( # pyright: ignore
|
|
76
|
+
# use the permalink from the associated Entity
|
|
77
|
+
# or the to_id value
|
|
78
|
+
validation_alias=AliasChoices(
|
|
79
|
+
AliasPath("to_entity", "permalink"),
|
|
80
|
+
"to_id",
|
|
81
|
+
),
|
|
82
|
+
default=None,
|
|
83
|
+
)
|
|
84
|
+
to_name: Optional[Permalink] = Field(
|
|
85
|
+
# use the permalink from the associated Entity
|
|
86
|
+
# or the to_id value
|
|
87
|
+
validation_alias=AliasChoices(
|
|
88
|
+
AliasPath("to_entity", "title"),
|
|
89
|
+
"to_name",
|
|
90
|
+
),
|
|
91
|
+
default=None,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class EntityResponse(SQLAlchemyModel):
|
|
96
|
+
"""Complete entity data returned from the service.
|
|
97
|
+
|
|
98
|
+
This is the most comprehensive entity view, including:
|
|
99
|
+
1. Basic entity details (id, name, type)
|
|
100
|
+
2. All observations with their IDs
|
|
101
|
+
3. All relations with their IDs
|
|
102
|
+
4. Optional description
|
|
103
|
+
|
|
104
|
+
Example Response:
|
|
105
|
+
{
|
|
106
|
+
"permalink": "component/memory-service",
|
|
107
|
+
"file_path": "MemoryService",
|
|
108
|
+
"entity_type": "component",
|
|
109
|
+
"entity_metadata": {}
|
|
110
|
+
"content_type: "text/markdown"
|
|
111
|
+
"observations": [
|
|
112
|
+
{
|
|
113
|
+
"category": "feature",
|
|
114
|
+
"content": "Uses SQLite storage"
|
|
115
|
+
"context": "Initial design"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"category": "feature",
|
|
119
|
+
"content": "Implements async operations"
|
|
120
|
+
"context": "Initial design"
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
"relations": [
|
|
124
|
+
{
|
|
125
|
+
"from_id": "test/memory-test",
|
|
126
|
+
"to_id": "component/memory-service",
|
|
127
|
+
"relation_type": "validates",
|
|
128
|
+
"context": "Main test suite"
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
permalink: Optional[Permalink]
|
|
135
|
+
title: str
|
|
136
|
+
file_path: str
|
|
137
|
+
entity_type: EntityType
|
|
138
|
+
entity_metadata: Optional[Dict] = None
|
|
139
|
+
checksum: Optional[str] = None
|
|
140
|
+
content_type: ContentType
|
|
141
|
+
observations: List[ObservationResponse] = []
|
|
142
|
+
relations: List[RelationResponse] = []
|
|
143
|
+
created_at: datetime
|
|
144
|
+
updated_at: datetime
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class EntityListResponse(SQLAlchemyModel):
|
|
148
|
+
"""Response for create_entities operation.
|
|
149
|
+
|
|
150
|
+
Returns complete information about entities returned from the service,
|
|
151
|
+
including their permalinks, observations,
|
|
152
|
+
and any established relations.
|
|
153
|
+
|
|
154
|
+
Example Response:
|
|
155
|
+
{
|
|
156
|
+
"entities": [
|
|
157
|
+
{
|
|
158
|
+
"permalink": "component/search_service",
|
|
159
|
+
"title": "SearchService",
|
|
160
|
+
"entity_type": "component",
|
|
161
|
+
"description": "Knowledge graph search",
|
|
162
|
+
"observations": [
|
|
163
|
+
{
|
|
164
|
+
"content": "Implements full-text search"
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"relations": []
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"permalink": "document/api_docs",
|
|
171
|
+
"title": "API_Documentation",
|
|
172
|
+
"entity_type": "document",
|
|
173
|
+
"description": "API Reference",
|
|
174
|
+
"observations": [
|
|
175
|
+
{
|
|
176
|
+
"content": "Documents REST endpoints"
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
"relations": []
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
entities: List[EntityResponse]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class SearchNodesResponse(SQLAlchemyModel):
|
|
189
|
+
"""Response for search operation.
|
|
190
|
+
|
|
191
|
+
Returns matching entities with their complete information,
|
|
192
|
+
plus the original query for reference.
|
|
193
|
+
|
|
194
|
+
Example Response:
|
|
195
|
+
{
|
|
196
|
+
"matches": [
|
|
197
|
+
{
|
|
198
|
+
"permalink": "component/memory-service",
|
|
199
|
+
"title": "MemoryService",
|
|
200
|
+
"entity_type": "component",
|
|
201
|
+
"description": "Core service",
|
|
202
|
+
"observations": [...],
|
|
203
|
+
"relations": [...]
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
"query": "memory"
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Note: Each entity in matches includes full details
|
|
210
|
+
just like EntityResponse.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
matches: List[EntityResponse]
|
|
214
|
+
query: str
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class DeleteEntitiesResponse(SQLAlchemyModel):
|
|
218
|
+
"""Response indicating successful entity deletion.
|
|
219
|
+
|
|
220
|
+
A simple boolean response confirming the delete operation
|
|
221
|
+
completed successfully.
|
|
222
|
+
|
|
223
|
+
Example Response:
|
|
224
|
+
{
|
|
225
|
+
"deleted": true
|
|
226
|
+
}
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
deleted: bool
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Search schemas for Basic Memory.
|
|
2
|
+
|
|
3
|
+
The search system supports three primary modes:
|
|
4
|
+
1. Exact permalink lookup
|
|
5
|
+
2. Pattern matching with *
|
|
6
|
+
3. Full-text search across content
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Optional, List, Union
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from pydantic import BaseModel, field_validator
|
|
13
|
+
|
|
14
|
+
from basic_memory.schemas.base import Permalink
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SearchItemType(str, Enum):
|
|
18
|
+
"""Types of searchable items."""
|
|
19
|
+
|
|
20
|
+
ENTITY = "entity"
|
|
21
|
+
OBSERVATION = "observation"
|
|
22
|
+
RELATION = "relation"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SearchQuery(BaseModel):
|
|
26
|
+
"""Search query parameters.
|
|
27
|
+
|
|
28
|
+
Use ONE of these primary search modes:
|
|
29
|
+
- permalink: Exact permalink match
|
|
30
|
+
- permalink_match: Path pattern with *
|
|
31
|
+
- text: Full-text search of title/content (supports boolean operators: AND, OR, NOT)
|
|
32
|
+
|
|
33
|
+
Optionally filter results by:
|
|
34
|
+
- types: Limit to specific item types
|
|
35
|
+
- entity_types: Limit to specific entity types
|
|
36
|
+
- after_date: Only items after date
|
|
37
|
+
|
|
38
|
+
Boolean search examples:
|
|
39
|
+
- "python AND flask" - Find items with both terms
|
|
40
|
+
- "python OR django" - Find items with either term
|
|
41
|
+
- "python NOT django" - Find items with python but not django
|
|
42
|
+
- "(python OR flask) AND web" - Use parentheses for grouping
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Primary search modes (use ONE of these)
|
|
46
|
+
permalink: Optional[str] = None # Exact permalink match
|
|
47
|
+
permalink_match: Optional[str] = None # Glob permalink match
|
|
48
|
+
text: Optional[str] = None # Full-text search (now supports boolean operators)
|
|
49
|
+
title: Optional[str] = None # title only search
|
|
50
|
+
|
|
51
|
+
# Optional filters
|
|
52
|
+
types: Optional[List[str]] = None # Filter by type
|
|
53
|
+
entity_types: Optional[List[SearchItemType]] = None # Filter by entity type
|
|
54
|
+
after_date: Optional[Union[datetime, str]] = None # Time-based filter
|
|
55
|
+
|
|
56
|
+
@field_validator("after_date")
|
|
57
|
+
@classmethod
|
|
58
|
+
def validate_date(cls, v: Optional[Union[datetime, str]]) -> Optional[str]:
|
|
59
|
+
"""Convert datetime to ISO format if needed."""
|
|
60
|
+
if isinstance(v, datetime):
|
|
61
|
+
return v.isoformat()
|
|
62
|
+
return v
|
|
63
|
+
|
|
64
|
+
def no_criteria(self) -> bool:
|
|
65
|
+
return (
|
|
66
|
+
self.permalink is None
|
|
67
|
+
and self.permalink_match is None
|
|
68
|
+
and self.title is None
|
|
69
|
+
and self.text is None
|
|
70
|
+
and self.after_date is None
|
|
71
|
+
and self.types is None
|
|
72
|
+
and self.entity_types is None
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def has_boolean_operators(self) -> bool:
|
|
76
|
+
"""Check if the text query contains boolean operators (AND, OR, NOT)."""
|
|
77
|
+
if not self.text: # pragma: no cover
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
# Check for common boolean operators with correct word boundaries
|
|
81
|
+
# to avoid matching substrings like "GRAND" containing "AND"
|
|
82
|
+
boolean_patterns = [" AND ", " OR ", " NOT ", "(", ")"]
|
|
83
|
+
text = f" {self.text} " # Add spaces to ensure we match word boundaries
|
|
84
|
+
return any(pattern in text for pattern in boolean_patterns)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class SearchResult(BaseModel):
|
|
88
|
+
"""Search result with score and metadata."""
|
|
89
|
+
|
|
90
|
+
title: str
|
|
91
|
+
type: SearchItemType
|
|
92
|
+
score: float
|
|
93
|
+
entity: Optional[Permalink] = None
|
|
94
|
+
permalink: Optional[str]
|
|
95
|
+
content: Optional[str] = None
|
|
96
|
+
file_path: str
|
|
97
|
+
|
|
98
|
+
metadata: Optional[dict] = None
|
|
99
|
+
|
|
100
|
+
# IDs for v2 API consistency
|
|
101
|
+
entity_id: Optional[int] = None # Entity ID (always present for entities)
|
|
102
|
+
observation_id: Optional[int] = None # Observation ID (for observation results)
|
|
103
|
+
relation_id: Optional[int] = None # Relation ID (for relation results)
|
|
104
|
+
|
|
105
|
+
# Type-specific fields
|
|
106
|
+
category: Optional[str] = None # For observations
|
|
107
|
+
from_entity: Optional[Permalink] = None # For relations
|
|
108
|
+
to_entity: Optional[Permalink] = None # For relations
|
|
109
|
+
relation_type: Optional[str] = None # For relations
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class SearchResponse(BaseModel):
|
|
113
|
+
"""Wrapper for search results."""
|
|
114
|
+
|
|
115
|
+
results: List[SearchResult]
|
|
116
|
+
current_page: int
|
|
117
|
+
page_size: int
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Pydantic schemas for sync report responses."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, List, Set
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
# avoid cirular imports
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from basic_memory.sync.sync_service import SyncReport
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SkippedFileResponse(BaseModel):
|
|
14
|
+
"""Information about a file that was skipped due to repeated failures."""
|
|
15
|
+
|
|
16
|
+
path: str = Field(description="File path relative to project root")
|
|
17
|
+
reason: str = Field(description="Error message from last failure")
|
|
18
|
+
failure_count: int = Field(description="Number of consecutive failures")
|
|
19
|
+
first_failed: datetime = Field(description="Timestamp of first failure")
|
|
20
|
+
|
|
21
|
+
model_config = {"from_attributes": True}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SyncReportResponse(BaseModel):
|
|
25
|
+
"""Report of file changes found compared to database state.
|
|
26
|
+
|
|
27
|
+
Used for API responses when scanning or syncing files.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
new: Set[str] = Field(default_factory=set, description="Files on disk but not in database")
|
|
31
|
+
modified: Set[str] = Field(default_factory=set, description="Files with different checksums")
|
|
32
|
+
deleted: Set[str] = Field(default_factory=set, description="Files in database but not on disk")
|
|
33
|
+
moves: Dict[str, str] = Field(
|
|
34
|
+
default_factory=dict, description="Files moved (old_path -> new_path)"
|
|
35
|
+
)
|
|
36
|
+
checksums: Dict[str, str] = Field(
|
|
37
|
+
default_factory=dict, description="Current file checksums (path -> checksum)"
|
|
38
|
+
)
|
|
39
|
+
skipped_files: List[SkippedFileResponse] = Field(
|
|
40
|
+
default_factory=list, description="Files skipped due to repeated failures"
|
|
41
|
+
)
|
|
42
|
+
total: int = Field(description="Total number of changes")
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_sync_report(cls, report: "SyncReport") -> "SyncReportResponse":
|
|
46
|
+
"""Convert SyncReport dataclass to Pydantic model.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
report: SyncReport dataclass from sync service
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
SyncReportResponse with same data
|
|
53
|
+
"""
|
|
54
|
+
return cls(
|
|
55
|
+
new=report.new,
|
|
56
|
+
modified=report.modified,
|
|
57
|
+
deleted=report.deleted,
|
|
58
|
+
moves=report.moves,
|
|
59
|
+
checksums=report.checksums,
|
|
60
|
+
skipped_files=[
|
|
61
|
+
SkippedFileResponse(
|
|
62
|
+
path=skipped.path,
|
|
63
|
+
reason=skipped.reason,
|
|
64
|
+
failure_count=skipped.failure_count,
|
|
65
|
+
first_failed=skipped.first_failed,
|
|
66
|
+
)
|
|
67
|
+
for skipped in report.skipped_files
|
|
68
|
+
],
|
|
69
|
+
total=report.total,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
model_config = {"from_attributes": True}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""V2 API schemas - ID-based entity and project references."""
|
|
2
|
+
|
|
3
|
+
from basic_memory.schemas.v2.entity import (
|
|
4
|
+
EntityResolveRequest,
|
|
5
|
+
EntityResolveResponse,
|
|
6
|
+
EntityResponseV2,
|
|
7
|
+
MoveEntityRequestV2,
|
|
8
|
+
ProjectResolveRequest,
|
|
9
|
+
ProjectResolveResponse,
|
|
10
|
+
)
|
|
11
|
+
from basic_memory.schemas.v2.resource import (
|
|
12
|
+
CreateResourceRequest,
|
|
13
|
+
UpdateResourceRequest,
|
|
14
|
+
ResourceResponse,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"EntityResolveRequest",
|
|
19
|
+
"EntityResolveResponse",
|
|
20
|
+
"EntityResponseV2",
|
|
21
|
+
"MoveEntityRequestV2",
|
|
22
|
+
"ProjectResolveRequest",
|
|
23
|
+
"ProjectResolveResponse",
|
|
24
|
+
"CreateResourceRequest",
|
|
25
|
+
"UpdateResourceRequest",
|
|
26
|
+
"ResourceResponse",
|
|
27
|
+
]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""V2 entity and project schemas with ID-first design."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Dict, List, Literal, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
7
|
+
|
|
8
|
+
from basic_memory.schemas.response import ObservationResponse, RelationResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EntityResolveRequest(BaseModel):
|
|
12
|
+
"""Request to resolve a string identifier to an entity ID.
|
|
13
|
+
|
|
14
|
+
Supports resolution of:
|
|
15
|
+
- Permalinks (e.g., "specs/search")
|
|
16
|
+
- Titles (e.g., "Search Specification")
|
|
17
|
+
- File paths (e.g., "specs/search.md")
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
identifier: str = Field(
|
|
21
|
+
...,
|
|
22
|
+
description="Entity identifier to resolve (permalink, title, or file path)",
|
|
23
|
+
min_length=1,
|
|
24
|
+
max_length=500,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EntityResolveResponse(BaseModel):
|
|
29
|
+
"""Response from identifier resolution.
|
|
30
|
+
|
|
31
|
+
Returns the entity ID and associated metadata for the resolved entity.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
entity_id: int = Field(..., description="Numeric entity ID (primary identifier)")
|
|
35
|
+
permalink: Optional[str] = Field(None, description="Entity permalink")
|
|
36
|
+
file_path: str = Field(..., description="Relative file path")
|
|
37
|
+
title: str = Field(..., description="Entity title")
|
|
38
|
+
resolution_method: Literal["id", "permalink", "title", "path", "search"] = Field(
|
|
39
|
+
..., description="How the identifier was resolved"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MoveEntityRequestV2(BaseModel):
|
|
44
|
+
"""V2 request schema for moving an entity to a new file location.
|
|
45
|
+
|
|
46
|
+
In V2 API, the entity ID is provided in the URL path, so this request
|
|
47
|
+
only needs the destination path.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
destination_path: str = Field(
|
|
51
|
+
...,
|
|
52
|
+
description="New file path for the entity (relative to project root)",
|
|
53
|
+
min_length=1,
|
|
54
|
+
max_length=500,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class EntityResponseV2(BaseModel):
|
|
59
|
+
"""V2 entity response with ID as the primary field.
|
|
60
|
+
|
|
61
|
+
This response format emphasizes the entity ID as the primary identifier,
|
|
62
|
+
with all other fields (permalink, file_path) as secondary metadata.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
# ID first - this is the primary identifier in v2
|
|
66
|
+
id: int = Field(..., description="Numeric entity ID (primary identifier)")
|
|
67
|
+
|
|
68
|
+
# Core entity fields
|
|
69
|
+
title: str = Field(..., description="Entity title")
|
|
70
|
+
entity_type: str = Field(..., description="Entity type")
|
|
71
|
+
content_type: str = Field(default="text/markdown", description="Content MIME type")
|
|
72
|
+
|
|
73
|
+
# Secondary identifiers (for compatibility and convenience)
|
|
74
|
+
permalink: Optional[str] = Field(None, description="Entity permalink (may change)")
|
|
75
|
+
file_path: str = Field(..., description="Relative file path (may change)")
|
|
76
|
+
|
|
77
|
+
# Content and metadata
|
|
78
|
+
content: Optional[str] = Field(None, description="Entity content")
|
|
79
|
+
entity_metadata: Optional[Dict] = Field(None, description="Entity metadata")
|
|
80
|
+
|
|
81
|
+
# Relationships
|
|
82
|
+
observations: List[ObservationResponse] = Field(
|
|
83
|
+
default_factory=list, description="Entity observations"
|
|
84
|
+
)
|
|
85
|
+
relations: List[RelationResponse] = Field(default_factory=list, description="Entity relations")
|
|
86
|
+
|
|
87
|
+
# Timestamps
|
|
88
|
+
created_at: datetime = Field(..., description="Creation timestamp")
|
|
89
|
+
updated_at: datetime = Field(..., description="Last update timestamp")
|
|
90
|
+
|
|
91
|
+
# V2-specific metadata
|
|
92
|
+
api_version: Literal["v2"] = Field(
|
|
93
|
+
default="v2", description="API version (always 'v2' for this response)"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
model_config = ConfigDict(from_attributes=True)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ProjectResolveRequest(BaseModel):
|
|
100
|
+
"""Request to resolve a project identifier to a project ID.
|
|
101
|
+
|
|
102
|
+
Supports resolution of:
|
|
103
|
+
- Project names (e.g., "my-project")
|
|
104
|
+
- Permalinks (e.g., "my-project")
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
identifier: str = Field(
|
|
108
|
+
...,
|
|
109
|
+
description="Project identifier to resolve (name or permalink)",
|
|
110
|
+
min_length=1,
|
|
111
|
+
max_length=255,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ProjectResolveResponse(BaseModel):
|
|
116
|
+
"""Response from project identifier resolution.
|
|
117
|
+
|
|
118
|
+
Returns the project ID and associated metadata for the resolved project.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
project_id: int = Field(..., description="Numeric project ID (primary identifier)")
|
|
122
|
+
name: str = Field(..., description="Project name")
|
|
123
|
+
permalink: str = Field(..., description="Project permalink")
|
|
124
|
+
path: str = Field(..., description="Project file path")
|
|
125
|
+
is_active: bool = Field(..., description="Whether the project is active")
|
|
126
|
+
is_default: bool = Field(..., description="Whether the project is the default")
|
|
127
|
+
resolution_method: Literal["id", "name", "permalink"] = Field(
|
|
128
|
+
..., description="How the identifier was resolved"
|
|
129
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""V2 resource schemas for file content operations."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CreateResourceRequest(BaseModel):
|
|
7
|
+
"""Request to create a new resource file.
|
|
8
|
+
|
|
9
|
+
File path is required for new resources since we need to know where
|
|
10
|
+
to create the file.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
file_path: str = Field(
|
|
14
|
+
...,
|
|
15
|
+
description="Path to create the file, relative to project root",
|
|
16
|
+
min_length=1,
|
|
17
|
+
max_length=500,
|
|
18
|
+
)
|
|
19
|
+
content: str = Field(..., description="File content to write")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UpdateResourceRequest(BaseModel):
|
|
23
|
+
"""Request to update an existing resource by entity ID.
|
|
24
|
+
|
|
25
|
+
Only content is required - the file path is already known from the entity.
|
|
26
|
+
Optionally can update the file_path to move the file.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
content: str = Field(..., description="File content to write")
|
|
30
|
+
file_path: str | None = Field(
|
|
31
|
+
None,
|
|
32
|
+
description="Optional new file path to move the resource",
|
|
33
|
+
min_length=1,
|
|
34
|
+
max_length=500,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ResourceResponse(BaseModel):
|
|
39
|
+
"""Response from resource operations."""
|
|
40
|
+
|
|
41
|
+
entity_id: int = Field(..., description="Entity ID of the resource")
|
|
42
|
+
file_path: str = Field(..., description="File path of the resource")
|
|
43
|
+
checksum: str = Field(..., description="File content checksum")
|
|
44
|
+
size: int = Field(..., description="File size in bytes")
|
|
45
|
+
created_at: float = Field(..., description="Creation timestamp")
|
|
46
|
+
modified_at: float = Field(..., description="Modification timestamp")
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Services package."""
|
|
2
|
+
|
|
3
|
+
from .service import BaseService
|
|
4
|
+
from .file_service import FileService
|
|
5
|
+
from .entity_service import EntityService
|
|
6
|
+
from .project_service import ProjectService
|
|
7
|
+
|
|
8
|
+
__all__ = ["BaseService", "FileService", "EntityService", "ProjectService"]
|