basic-memory 0.2.12__py3-none-any.whl → 0.16.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.
Potentially problematic release.
This version of basic-memory might be problematic. Click here for more details.
- basic_memory/__init__.py +5 -1
- basic_memory/alembic/alembic.ini +119 -0
- basic_memory/alembic/env.py +27 -3
- basic_memory/alembic/migrations.py +4 -9
- basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
- basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +108 -0
- basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +104 -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/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
- basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +100 -0
- basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
- basic_memory/api/app.py +63 -31
- basic_memory/api/routers/__init__.py +4 -1
- 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 +165 -28
- basic_memory/api/routers/management_router.py +80 -0
- basic_memory/api/routers/memory_router.py +28 -67
- basic_memory/api/routers/project_router.py +406 -0
- basic_memory/api/routers/prompt_router.py +260 -0
- basic_memory/api/routers/resource_router.py +219 -14
- basic_memory/api/routers/search_router.py +21 -13
- basic_memory/api/routers/utils.py +130 -0
- basic_memory/api/template_loader.py +292 -0
- basic_memory/cli/app.py +52 -1
- basic_memory/cli/auth.py +277 -0
- basic_memory/cli/commands/__init__.py +13 -2
- 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 +301 -0
- basic_memory/cli/commands/cloud/rclone_config.py +110 -0
- basic_memory/cli/commands/cloud/rclone_installer.py +249 -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 +51 -0
- basic_memory/cli/commands/db.py +26 -7
- basic_memory/cli/commands/import_chatgpt.py +83 -0
- basic_memory/cli/commands/import_claude_conversations.py +86 -0
- basic_memory/cli/commands/import_claude_projects.py +85 -0
- basic_memory/cli/commands/import_memory_json.py +35 -92
- basic_memory/cli/commands/mcp.py +84 -10
- basic_memory/cli/commands/project.py +876 -0
- basic_memory/cli/commands/status.py +47 -30
- basic_memory/cli/commands/tool.py +341 -0
- basic_memory/cli/main.py +13 -6
- basic_memory/config.py +481 -22
- basic_memory/db.py +192 -32
- basic_memory/deps.py +252 -22
- basic_memory/file_utils.py +113 -58
- 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 +177 -0
- basic_memory/importers/claude_projects_importer.py +148 -0
- basic_memory/importers/memory_json_importer.py +108 -0
- basic_memory/importers/utils.py +58 -0
- basic_memory/markdown/entity_parser.py +143 -23
- basic_memory/markdown/markdown_processor.py +3 -3
- basic_memory/markdown/plugins.py +39 -21
- basic_memory/markdown/schemas.py +1 -1
- basic_memory/markdown/utils.py +28 -13
- basic_memory/mcp/async_client.py +134 -4
- 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 +7 -13
- basic_memory/mcp/tools/__init__.py +33 -21
- basic_memory/mcp/tools/build_context.py +120 -0
- basic_memory/mcp/tools/canvas.py +130 -0
- basic_memory/mcp/tools/chatgpt_tools.py +187 -0
- basic_memory/mcp/tools/delete_note.py +225 -0
- basic_memory/mcp/tools/edit_note.py +320 -0
- basic_memory/mcp/tools/list_directory.py +167 -0
- basic_memory/mcp/tools/move_note.py +545 -0
- basic_memory/mcp/tools/project_management.py +200 -0
- basic_memory/mcp/tools/read_content.py +271 -0
- basic_memory/mcp/tools/read_note.py +255 -0
- basic_memory/mcp/tools/recent_activity.py +534 -0
- basic_memory/mcp/tools/search.py +369 -14
- basic_memory/mcp/tools/utils.py +374 -16
- basic_memory/mcp/tools/view_note.py +77 -0
- basic_memory/mcp/tools/write_note.py +207 -0
- basic_memory/models/__init__.py +3 -2
- basic_memory/models/knowledge.py +67 -15
- basic_memory/models/project.py +87 -0
- basic_memory/models/search.py +10 -6
- basic_memory/repository/__init__.py +2 -0
- basic_memory/repository/entity_repository.py +229 -7
- basic_memory/repository/observation_repository.py +35 -3
- basic_memory/repository/project_info_repository.py +10 -0
- basic_memory/repository/project_repository.py +103 -0
- basic_memory/repository/relation_repository.py +21 -2
- basic_memory/repository/repository.py +147 -29
- basic_memory/repository/search_repository.py +437 -59
- basic_memory/schemas/__init__.py +22 -9
- basic_memory/schemas/base.py +97 -8
- basic_memory/schemas/cloud.py +50 -0
- basic_memory/schemas/directory.py +30 -0
- basic_memory/schemas/importer.py +35 -0
- basic_memory/schemas/memory.py +188 -23
- basic_memory/schemas/project_info.py +211 -0
- basic_memory/schemas/prompt.py +90 -0
- basic_memory/schemas/request.py +57 -3
- basic_memory/schemas/response.py +9 -1
- basic_memory/schemas/search.py +33 -35
- basic_memory/schemas/sync_report.py +72 -0
- basic_memory/services/__init__.py +2 -1
- basic_memory/services/context_service.py +251 -106
- basic_memory/services/directory_service.py +295 -0
- basic_memory/services/entity_service.py +595 -60
- basic_memory/services/exceptions.py +21 -0
- basic_memory/services/file_service.py +284 -30
- basic_memory/services/initialization.py +191 -0
- basic_memory/services/link_resolver.py +50 -56
- basic_memory/services/project_service.py +863 -0
- basic_memory/services/search_service.py +172 -34
- basic_memory/sync/__init__.py +3 -2
- basic_memory/sync/background_sync.py +26 -0
- basic_memory/sync/sync_service.py +1176 -96
- basic_memory/sync/watch_service.py +412 -135
- basic_memory/templates/prompts/continue_conversation.hbs +110 -0
- basic_memory/templates/prompts/search.hbs +101 -0
- basic_memory/utils.py +388 -28
- basic_memory-0.16.1.dist-info/METADATA +493 -0
- basic_memory-0.16.1.dist-info/RECORD +148 -0
- {basic_memory-0.2.12.dist-info → basic_memory-0.16.1.dist-info}/entry_points.txt +1 -0
- basic_memory/alembic/README +0 -1
- basic_memory/cli/commands/sync.py +0 -203
- basic_memory/mcp/tools/knowledge.py +0 -56
- basic_memory/mcp/tools/memory.py +0 -151
- basic_memory/mcp/tools/notes.py +0 -122
- basic_memory/schemas/discovery.py +0 -28
- basic_memory/sync/file_change_scanner.py +0 -158
- basic_memory/sync/utils.py +0 -34
- basic_memory-0.2.12.dist-info/METADATA +0 -291
- basic_memory-0.2.12.dist-info/RECORD +0 -78
- {basic_memory-0.2.12.dist-info → basic_memory-0.16.1.dist-info}/WHEEL +0 -0
- {basic_memory-0.2.12.dist-info → basic_memory-0.16.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Base repository implementation."""
|
|
2
2
|
|
|
3
|
-
from typing import Type, Optional, Any, Sequence, TypeVar, List
|
|
3
|
+
from typing import Type, Optional, Any, Sequence, TypeVar, List, Dict
|
|
4
4
|
|
|
5
5
|
from loguru import logger
|
|
6
6
|
from sqlalchemy import (
|
|
@@ -10,13 +10,13 @@ from sqlalchemy import (
|
|
|
10
10
|
Executable,
|
|
11
11
|
inspect,
|
|
12
12
|
Result,
|
|
13
|
-
Column,
|
|
14
13
|
and_,
|
|
15
14
|
delete,
|
|
16
15
|
)
|
|
17
16
|
from sqlalchemy.exc import NoResultFound
|
|
18
17
|
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
|
|
19
18
|
from sqlalchemy.orm.interfaces import LoaderOption
|
|
19
|
+
from sqlalchemy.sql.elements import ColumnElement
|
|
20
20
|
|
|
21
21
|
from basic_memory import db
|
|
22
22
|
from basic_memory.models import Base
|
|
@@ -27,12 +27,30 @@ T = TypeVar("T", bound=Base)
|
|
|
27
27
|
class Repository[T: Base]:
|
|
28
28
|
"""Base repository implementation with generic CRUD operations."""
|
|
29
29
|
|
|
30
|
-
def __init__(
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
session_maker: async_sessionmaker[AsyncSession],
|
|
33
|
+
Model: Type[T],
|
|
34
|
+
project_id: Optional[int] = None,
|
|
35
|
+
):
|
|
31
36
|
self.session_maker = session_maker
|
|
32
|
-
self.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
self.project_id = project_id
|
|
38
|
+
if Model:
|
|
39
|
+
self.Model = Model
|
|
40
|
+
self.mapper = inspect(self.Model).mapper
|
|
41
|
+
self.primary_key: ColumnElement[Any] = self.mapper.primary_key[0]
|
|
42
|
+
self.valid_columns = [column.key for column in self.mapper.columns]
|
|
43
|
+
# Check if this model has a project_id column
|
|
44
|
+
self.has_project_id = "project_id" in self.valid_columns
|
|
45
|
+
|
|
46
|
+
def _set_project_id_if_needed(self, model: T) -> None:
|
|
47
|
+
"""Set project_id on model if needed and available."""
|
|
48
|
+
if (
|
|
49
|
+
self.has_project_id
|
|
50
|
+
and self.project_id is not None
|
|
51
|
+
and getattr(model, "project_id", None) is None
|
|
52
|
+
):
|
|
53
|
+
setattr(model, "project_id", self.project_id)
|
|
36
54
|
|
|
37
55
|
def get_model_data(self, entity_data):
|
|
38
56
|
model_data = {
|
|
@@ -40,6 +58,19 @@ class Repository[T: Base]:
|
|
|
40
58
|
}
|
|
41
59
|
return model_data
|
|
42
60
|
|
|
61
|
+
def _add_project_filter(self, query: Select) -> Select:
|
|
62
|
+
"""Add project_id filter to query if applicable.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
query: The SQLAlchemy query to modify
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Updated query with project filter if applicable
|
|
69
|
+
"""
|
|
70
|
+
if self.has_project_id and self.project_id is not None:
|
|
71
|
+
query = query.filter(getattr(self.Model, "project_id") == self.project_id)
|
|
72
|
+
return query
|
|
73
|
+
|
|
43
74
|
async def select_by_id(self, session: AsyncSession, entity_id: int) -> Optional[T]:
|
|
44
75
|
"""Select an entity by ID using an existing session."""
|
|
45
76
|
query = (
|
|
@@ -47,6 +78,9 @@ class Repository[T: Base]:
|
|
|
47
78
|
.filter(self.primary_key == entity_id)
|
|
48
79
|
.options(*self.get_load_options())
|
|
49
80
|
)
|
|
81
|
+
# Add project filter if applicable
|
|
82
|
+
query = self._add_project_filter(query)
|
|
83
|
+
|
|
50
84
|
result = await session.execute(query)
|
|
51
85
|
return result.scalars().one_or_none()
|
|
52
86
|
|
|
@@ -55,6 +89,9 @@ class Repository[T: Base]:
|
|
|
55
89
|
query = (
|
|
56
90
|
select(self.Model).where(self.primary_key.in_(ids)).options(*self.get_load_options())
|
|
57
91
|
)
|
|
92
|
+
# Add project filter if applicable
|
|
93
|
+
query = self._add_project_filter(query)
|
|
94
|
+
|
|
58
95
|
result = await session.execute(query)
|
|
59
96
|
return result.scalars().all()
|
|
60
97
|
|
|
@@ -65,12 +102,23 @@ class Repository[T: Base]:
|
|
|
65
102
|
:return: the added model instance
|
|
66
103
|
"""
|
|
67
104
|
async with db.scoped_session(self.session_maker) as session:
|
|
105
|
+
# Set project_id if applicable and not already set
|
|
106
|
+
self._set_project_id_if_needed(model)
|
|
107
|
+
|
|
68
108
|
session.add(model)
|
|
69
109
|
await session.flush()
|
|
70
110
|
|
|
71
111
|
# Query within same session
|
|
72
112
|
found = await self.select_by_id(session, model.id) # pyright: ignore [reportAttributeAccessIssue]
|
|
73
|
-
|
|
113
|
+
if found is None: # pragma: no cover
|
|
114
|
+
logger.error(
|
|
115
|
+
"Failed to retrieve model after add",
|
|
116
|
+
model_type=self.Model.__name__,
|
|
117
|
+
model_id=model.id, # pyright: ignore
|
|
118
|
+
)
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"Can't find {self.Model.__name__} with ID {model.id} after session.add" # pyright: ignore
|
|
121
|
+
)
|
|
74
122
|
return found
|
|
75
123
|
|
|
76
124
|
async def add_all(self, models: List[T]) -> Sequence[T]:
|
|
@@ -80,6 +128,10 @@ class Repository[T: Base]:
|
|
|
80
128
|
:return: the added models instances
|
|
81
129
|
"""
|
|
82
130
|
async with db.scoped_session(self.session_maker) as session:
|
|
131
|
+
# set the project id if not present in models
|
|
132
|
+
for model in models:
|
|
133
|
+
self._set_project_id_if_needed(model)
|
|
134
|
+
|
|
83
135
|
session.add_all(models)
|
|
84
136
|
await session.flush()
|
|
85
137
|
|
|
@@ -95,14 +147,33 @@ class Repository[T: Base]:
|
|
|
95
147
|
"""
|
|
96
148
|
if not entities:
|
|
97
149
|
entities = (self.Model,)
|
|
98
|
-
|
|
150
|
+
query = select(*entities)
|
|
151
|
+
|
|
152
|
+
# Add project filter if applicable
|
|
153
|
+
return self._add_project_filter(query)
|
|
154
|
+
|
|
155
|
+
async def find_all(
|
|
156
|
+
self, skip: int = 0, limit: Optional[int] = None, use_load_options: bool = True
|
|
157
|
+
) -> Sequence[T]:
|
|
158
|
+
"""Fetch records from the database with pagination.
|
|
99
159
|
|
|
100
|
-
|
|
101
|
-
|
|
160
|
+
Args:
|
|
161
|
+
skip: Number of records to skip
|
|
162
|
+
limit: Maximum number of records to return
|
|
163
|
+
use_load_options: Whether to apply eager loading options (default: True)
|
|
164
|
+
"""
|
|
102
165
|
logger.debug(f"Finding all {self.Model.__name__} (skip={skip}, limit={limit})")
|
|
103
166
|
|
|
104
167
|
async with db.scoped_session(self.session_maker) as session:
|
|
105
|
-
query = select(self.Model).offset(skip)
|
|
168
|
+
query = select(self.Model).offset(skip)
|
|
169
|
+
|
|
170
|
+
# Only apply load options if requested
|
|
171
|
+
if use_load_options:
|
|
172
|
+
query = query.options(*self.get_load_options())
|
|
173
|
+
|
|
174
|
+
# Add project filter if applicable
|
|
175
|
+
query = self._add_project_filter(query)
|
|
176
|
+
|
|
106
177
|
if limit:
|
|
107
178
|
query = query.limit(limit)
|
|
108
179
|
|
|
@@ -128,17 +199,15 @@ class Repository[T: Base]:
|
|
|
128
199
|
|
|
129
200
|
async def find_one(self, query: Select[tuple[T]]) -> Optional[T]:
|
|
130
201
|
"""Execute a query and retrieve a single record."""
|
|
131
|
-
logger.debug(f"Finding one {self.Model.__name__} with query: {query}")
|
|
132
|
-
|
|
133
202
|
# add in load options
|
|
134
203
|
query = query.options(*self.get_load_options())
|
|
135
204
|
result = await self.execute_query(query)
|
|
136
205
|
entity = result.scalars().one_or_none()
|
|
137
206
|
|
|
138
207
|
if entity:
|
|
139
|
-
logger.
|
|
208
|
+
logger.trace(f"Found {self.Model.__name__}: {getattr(entity, 'id', None)}")
|
|
140
209
|
else:
|
|
141
|
-
logger.
|
|
210
|
+
logger.trace(f"No {self.Model.__name__} found")
|
|
142
211
|
return entity
|
|
143
212
|
|
|
144
213
|
async def create(self, data: dict) -> T:
|
|
@@ -147,12 +216,29 @@ class Repository[T: Base]:
|
|
|
147
216
|
async with db.scoped_session(self.session_maker) as session:
|
|
148
217
|
# Only include valid columns that are provided in entity_data
|
|
149
218
|
model_data = self.get_model_data(data)
|
|
219
|
+
|
|
220
|
+
# Add project_id if applicable and not already provided
|
|
221
|
+
if (
|
|
222
|
+
self.has_project_id
|
|
223
|
+
and self.project_id is not None
|
|
224
|
+
and "project_id" not in model_data
|
|
225
|
+
):
|
|
226
|
+
model_data["project_id"] = self.project_id
|
|
227
|
+
|
|
150
228
|
model = self.Model(**model_data)
|
|
151
229
|
session.add(model)
|
|
152
230
|
await session.flush()
|
|
153
231
|
|
|
154
232
|
return_instance = await self.select_by_id(session, model.id) # pyright: ignore [reportAttributeAccessIssue]
|
|
155
|
-
|
|
233
|
+
if return_instance is None: # pragma: no cover
|
|
234
|
+
logger.error(
|
|
235
|
+
"Failed to retrieve model after create",
|
|
236
|
+
model_type=self.Model.__name__,
|
|
237
|
+
model_id=model.id, # pyright: ignore
|
|
238
|
+
)
|
|
239
|
+
raise ValueError(
|
|
240
|
+
f"Can't find {self.Model.__name__} with ID {model.id} after session.add" # pyright: ignore
|
|
241
|
+
)
|
|
156
242
|
return return_instance
|
|
157
243
|
|
|
158
244
|
async def create_all(self, data_list: List[dict]) -> Sequence[T]:
|
|
@@ -161,12 +247,20 @@ class Repository[T: Base]:
|
|
|
161
247
|
|
|
162
248
|
async with db.scoped_session(self.session_maker) as session:
|
|
163
249
|
# Only include valid columns that are provided in entity_data
|
|
164
|
-
model_list = [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
250
|
+
model_list = []
|
|
251
|
+
for d in data_list:
|
|
252
|
+
model_data = self.get_model_data(d)
|
|
253
|
+
|
|
254
|
+
# Add project_id if applicable and not already provided
|
|
255
|
+
if (
|
|
256
|
+
self.has_project_id
|
|
257
|
+
and self.project_id is not None
|
|
258
|
+
and "project_id" not in model_data
|
|
259
|
+
):
|
|
260
|
+
model_data["project_id"] = self.project_id # pragma: no cover
|
|
261
|
+
|
|
262
|
+
model_list.append(self.Model(**model_data))
|
|
263
|
+
|
|
170
264
|
session.add_all(model_list)
|
|
171
265
|
await session.flush()
|
|
172
266
|
|
|
@@ -222,7 +316,13 @@ class Repository[T: Base]:
|
|
|
222
316
|
"""Delete records matching given IDs."""
|
|
223
317
|
logger.debug(f"Deleting {self.Model.__name__} by ids: {ids}")
|
|
224
318
|
async with db.scoped_session(self.session_maker) as session:
|
|
225
|
-
|
|
319
|
+
conditions = [self.primary_key.in_(ids)]
|
|
320
|
+
|
|
321
|
+
# Add project_id filter if applicable
|
|
322
|
+
if self.has_project_id and self.project_id is not None: # pragma: no cover
|
|
323
|
+
conditions.append(getattr(self.Model, "project_id") == self.project_id)
|
|
324
|
+
|
|
325
|
+
query = delete(self.Model).where(and_(*conditions))
|
|
226
326
|
result = await session.execute(query)
|
|
227
327
|
logger.debug(f"Deleted {result.rowcount} records")
|
|
228
328
|
return result.rowcount
|
|
@@ -232,6 +332,11 @@ class Repository[T: Base]:
|
|
|
232
332
|
logger.debug(f"Deleting {self.Model.__name__} by fields: {filters}")
|
|
233
333
|
async with db.scoped_session(self.session_maker) as session:
|
|
234
334
|
conditions = [getattr(self.Model, field) == value for field, value in filters.items()]
|
|
335
|
+
|
|
336
|
+
# Add project_id filter if applicable
|
|
337
|
+
if self.has_project_id and self.project_id is not None:
|
|
338
|
+
conditions.append(getattr(self.Model, "project_id") == self.project_id)
|
|
339
|
+
|
|
235
340
|
query = delete(self.Model).where(and_(*conditions))
|
|
236
341
|
result = await session.execute(query)
|
|
237
342
|
deleted = result.rowcount > 0
|
|
@@ -243,21 +348,34 @@ class Repository[T: Base]:
|
|
|
243
348
|
async with db.scoped_session(self.session_maker) as session:
|
|
244
349
|
if query is None:
|
|
245
350
|
query = select(func.count()).select_from(self.Model)
|
|
351
|
+
# Add project filter if applicable
|
|
352
|
+
if (
|
|
353
|
+
isinstance(query, Select)
|
|
354
|
+
and self.has_project_id
|
|
355
|
+
and self.project_id is not None
|
|
356
|
+
):
|
|
357
|
+
query = query.where(
|
|
358
|
+
getattr(self.Model, "project_id") == self.project_id
|
|
359
|
+
) # pragma: no cover
|
|
360
|
+
|
|
246
361
|
result = await session.execute(query)
|
|
247
362
|
scalar = result.scalar()
|
|
248
363
|
count = scalar if scalar is not None else 0
|
|
249
364
|
logger.debug(f"Counted {count} {self.Model.__name__} records")
|
|
250
365
|
return count
|
|
251
366
|
|
|
252
|
-
async def execute_query(
|
|
367
|
+
async def execute_query(
|
|
368
|
+
self,
|
|
369
|
+
query: Executable,
|
|
370
|
+
params: Optional[Dict[str, Any]] = None,
|
|
371
|
+
use_query_options: bool = True,
|
|
372
|
+
) -> Result[Any]:
|
|
253
373
|
"""Execute a query asynchronously."""
|
|
254
374
|
|
|
255
375
|
query = query.options(*self.get_load_options()) if use_query_options else query
|
|
256
|
-
|
|
257
|
-
logger.debug(f"Executing query: {query}")
|
|
376
|
+
logger.trace(f"Executing query: {query}, params: {params}")
|
|
258
377
|
async with db.scoped_session(self.session_maker) as session:
|
|
259
|
-
result = await session.execute(query)
|
|
260
|
-
logger.debug("Query executed successfully")
|
|
378
|
+
result = await session.execute(query, params)
|
|
261
379
|
return result
|
|
262
380
|
|
|
263
381
|
def get_load_options(self) -> List[LoaderOption]:
|