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.
Files changed (171) hide show
  1. basic_memory/__init__.py +7 -0
  2. basic_memory/alembic/alembic.ini +119 -0
  3. basic_memory/alembic/env.py +185 -0
  4. basic_memory/alembic/migrations.py +24 -0
  5. basic_memory/alembic/script.py.mako +26 -0
  6. basic_memory/alembic/versions/314f1ea54dc4_add_postgres_full_text_search_support_.py +131 -0
  7. basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +93 -0
  8. basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
  9. basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +120 -0
  10. basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +112 -0
  11. basic_memory/alembic/versions/9d9c1cb7d8f5_add_mtime_and_size_columns_to_entity_.py +49 -0
  12. basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +49 -0
  13. basic_memory/alembic/versions/a2b3c4d5e6f7_add_search_index_entity_cascade.py +56 -0
  14. basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
  15. basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +113 -0
  16. basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
  17. basic_memory/alembic/versions/f8a9b2c3d4e5_add_pg_trgm_for_fuzzy_link_resolution.py +239 -0
  18. basic_memory/api/__init__.py +5 -0
  19. basic_memory/api/app.py +131 -0
  20. basic_memory/api/routers/__init__.py +11 -0
  21. basic_memory/api/routers/directory_router.py +84 -0
  22. basic_memory/api/routers/importer_router.py +152 -0
  23. basic_memory/api/routers/knowledge_router.py +318 -0
  24. basic_memory/api/routers/management_router.py +80 -0
  25. basic_memory/api/routers/memory_router.py +90 -0
  26. basic_memory/api/routers/project_router.py +448 -0
  27. basic_memory/api/routers/prompt_router.py +260 -0
  28. basic_memory/api/routers/resource_router.py +249 -0
  29. basic_memory/api/routers/search_router.py +36 -0
  30. basic_memory/api/routers/utils.py +169 -0
  31. basic_memory/api/template_loader.py +292 -0
  32. basic_memory/api/v2/__init__.py +35 -0
  33. basic_memory/api/v2/routers/__init__.py +21 -0
  34. basic_memory/api/v2/routers/directory_router.py +93 -0
  35. basic_memory/api/v2/routers/importer_router.py +182 -0
  36. basic_memory/api/v2/routers/knowledge_router.py +413 -0
  37. basic_memory/api/v2/routers/memory_router.py +130 -0
  38. basic_memory/api/v2/routers/project_router.py +342 -0
  39. basic_memory/api/v2/routers/prompt_router.py +270 -0
  40. basic_memory/api/v2/routers/resource_router.py +286 -0
  41. basic_memory/api/v2/routers/search_router.py +73 -0
  42. basic_memory/cli/__init__.py +1 -0
  43. basic_memory/cli/app.py +84 -0
  44. basic_memory/cli/auth.py +277 -0
  45. basic_memory/cli/commands/__init__.py +18 -0
  46. basic_memory/cli/commands/cloud/__init__.py +6 -0
  47. basic_memory/cli/commands/cloud/api_client.py +112 -0
  48. basic_memory/cli/commands/cloud/bisync_commands.py +110 -0
  49. basic_memory/cli/commands/cloud/cloud_utils.py +101 -0
  50. basic_memory/cli/commands/cloud/core_commands.py +195 -0
  51. basic_memory/cli/commands/cloud/rclone_commands.py +371 -0
  52. basic_memory/cli/commands/cloud/rclone_config.py +110 -0
  53. basic_memory/cli/commands/cloud/rclone_installer.py +263 -0
  54. basic_memory/cli/commands/cloud/upload.py +233 -0
  55. basic_memory/cli/commands/cloud/upload_command.py +124 -0
  56. basic_memory/cli/commands/command_utils.py +77 -0
  57. basic_memory/cli/commands/db.py +44 -0
  58. basic_memory/cli/commands/format.py +198 -0
  59. basic_memory/cli/commands/import_chatgpt.py +84 -0
  60. basic_memory/cli/commands/import_claude_conversations.py +87 -0
  61. basic_memory/cli/commands/import_claude_projects.py +86 -0
  62. basic_memory/cli/commands/import_memory_json.py +87 -0
  63. basic_memory/cli/commands/mcp.py +76 -0
  64. basic_memory/cli/commands/project.py +889 -0
  65. basic_memory/cli/commands/status.py +174 -0
  66. basic_memory/cli/commands/telemetry.py +81 -0
  67. basic_memory/cli/commands/tool.py +341 -0
  68. basic_memory/cli/main.py +28 -0
  69. basic_memory/config.py +616 -0
  70. basic_memory/db.py +394 -0
  71. basic_memory/deps.py +705 -0
  72. basic_memory/file_utils.py +478 -0
  73. basic_memory/ignore_utils.py +297 -0
  74. basic_memory/importers/__init__.py +27 -0
  75. basic_memory/importers/base.py +79 -0
  76. basic_memory/importers/chatgpt_importer.py +232 -0
  77. basic_memory/importers/claude_conversations_importer.py +180 -0
  78. basic_memory/importers/claude_projects_importer.py +148 -0
  79. basic_memory/importers/memory_json_importer.py +108 -0
  80. basic_memory/importers/utils.py +61 -0
  81. basic_memory/markdown/__init__.py +21 -0
  82. basic_memory/markdown/entity_parser.py +279 -0
  83. basic_memory/markdown/markdown_processor.py +160 -0
  84. basic_memory/markdown/plugins.py +242 -0
  85. basic_memory/markdown/schemas.py +70 -0
  86. basic_memory/markdown/utils.py +117 -0
  87. basic_memory/mcp/__init__.py +1 -0
  88. basic_memory/mcp/async_client.py +139 -0
  89. basic_memory/mcp/project_context.py +141 -0
  90. basic_memory/mcp/prompts/__init__.py +19 -0
  91. basic_memory/mcp/prompts/ai_assistant_guide.py +70 -0
  92. basic_memory/mcp/prompts/continue_conversation.py +62 -0
  93. basic_memory/mcp/prompts/recent_activity.py +188 -0
  94. basic_memory/mcp/prompts/search.py +57 -0
  95. basic_memory/mcp/prompts/utils.py +162 -0
  96. basic_memory/mcp/resources/ai_assistant_guide.md +283 -0
  97. basic_memory/mcp/resources/project_info.py +71 -0
  98. basic_memory/mcp/server.py +81 -0
  99. basic_memory/mcp/tools/__init__.py +48 -0
  100. basic_memory/mcp/tools/build_context.py +120 -0
  101. basic_memory/mcp/tools/canvas.py +152 -0
  102. basic_memory/mcp/tools/chatgpt_tools.py +190 -0
  103. basic_memory/mcp/tools/delete_note.py +242 -0
  104. basic_memory/mcp/tools/edit_note.py +324 -0
  105. basic_memory/mcp/tools/list_directory.py +168 -0
  106. basic_memory/mcp/tools/move_note.py +551 -0
  107. basic_memory/mcp/tools/project_management.py +201 -0
  108. basic_memory/mcp/tools/read_content.py +281 -0
  109. basic_memory/mcp/tools/read_note.py +267 -0
  110. basic_memory/mcp/tools/recent_activity.py +534 -0
  111. basic_memory/mcp/tools/search.py +385 -0
  112. basic_memory/mcp/tools/utils.py +540 -0
  113. basic_memory/mcp/tools/view_note.py +78 -0
  114. basic_memory/mcp/tools/write_note.py +230 -0
  115. basic_memory/models/__init__.py +15 -0
  116. basic_memory/models/base.py +10 -0
  117. basic_memory/models/knowledge.py +226 -0
  118. basic_memory/models/project.py +87 -0
  119. basic_memory/models/search.py +85 -0
  120. basic_memory/repository/__init__.py +11 -0
  121. basic_memory/repository/entity_repository.py +503 -0
  122. basic_memory/repository/observation_repository.py +73 -0
  123. basic_memory/repository/postgres_search_repository.py +379 -0
  124. basic_memory/repository/project_info_repository.py +10 -0
  125. basic_memory/repository/project_repository.py +128 -0
  126. basic_memory/repository/relation_repository.py +146 -0
  127. basic_memory/repository/repository.py +385 -0
  128. basic_memory/repository/search_index_row.py +95 -0
  129. basic_memory/repository/search_repository.py +94 -0
  130. basic_memory/repository/search_repository_base.py +241 -0
  131. basic_memory/repository/sqlite_search_repository.py +439 -0
  132. basic_memory/schemas/__init__.py +86 -0
  133. basic_memory/schemas/base.py +297 -0
  134. basic_memory/schemas/cloud.py +50 -0
  135. basic_memory/schemas/delete.py +37 -0
  136. basic_memory/schemas/directory.py +30 -0
  137. basic_memory/schemas/importer.py +35 -0
  138. basic_memory/schemas/memory.py +285 -0
  139. basic_memory/schemas/project_info.py +212 -0
  140. basic_memory/schemas/prompt.py +90 -0
  141. basic_memory/schemas/request.py +112 -0
  142. basic_memory/schemas/response.py +229 -0
  143. basic_memory/schemas/search.py +117 -0
  144. basic_memory/schemas/sync_report.py +72 -0
  145. basic_memory/schemas/v2/__init__.py +27 -0
  146. basic_memory/schemas/v2/entity.py +129 -0
  147. basic_memory/schemas/v2/resource.py +46 -0
  148. basic_memory/services/__init__.py +8 -0
  149. basic_memory/services/context_service.py +601 -0
  150. basic_memory/services/directory_service.py +308 -0
  151. basic_memory/services/entity_service.py +864 -0
  152. basic_memory/services/exceptions.py +37 -0
  153. basic_memory/services/file_service.py +541 -0
  154. basic_memory/services/initialization.py +216 -0
  155. basic_memory/services/link_resolver.py +121 -0
  156. basic_memory/services/project_service.py +880 -0
  157. basic_memory/services/search_service.py +404 -0
  158. basic_memory/services/service.py +15 -0
  159. basic_memory/sync/__init__.py +6 -0
  160. basic_memory/sync/background_sync.py +26 -0
  161. basic_memory/sync/sync_service.py +1259 -0
  162. basic_memory/sync/watch_service.py +510 -0
  163. basic_memory/telemetry.py +249 -0
  164. basic_memory/templates/prompts/continue_conversation.hbs +110 -0
  165. basic_memory/templates/prompts/search.hbs +101 -0
  166. basic_memory/utils.py +468 -0
  167. basic_memory-0.17.1.dist-info/METADATA +617 -0
  168. basic_memory-0.17.1.dist-info/RECORD +171 -0
  169. basic_memory-0.17.1.dist-info/WHEEL +4 -0
  170. basic_memory-0.17.1.dist-info/entry_points.txt +3 -0
  171. 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"]