memorygraphMCP 0.11.7__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.
- memorygraph/__init__.py +50 -0
- memorygraph/__main__.py +12 -0
- memorygraph/advanced_tools.py +509 -0
- memorygraph/analytics/__init__.py +46 -0
- memorygraph/analytics/advanced_queries.py +727 -0
- memorygraph/backends/__init__.py +21 -0
- memorygraph/backends/base.py +179 -0
- memorygraph/backends/cloud.py +75 -0
- memorygraph/backends/cloud_backend.py +858 -0
- memorygraph/backends/factory.py +577 -0
- memorygraph/backends/falkordb_backend.py +749 -0
- memorygraph/backends/falkordblite_backend.py +746 -0
- memorygraph/backends/ladybugdb_backend.py +242 -0
- memorygraph/backends/memgraph_backend.py +327 -0
- memorygraph/backends/neo4j_backend.py +298 -0
- memorygraph/backends/sqlite_fallback.py +463 -0
- memorygraph/backends/turso.py +448 -0
- memorygraph/cli.py +743 -0
- memorygraph/cloud_database.py +297 -0
- memorygraph/config.py +295 -0
- memorygraph/database.py +933 -0
- memorygraph/graph_analytics.py +631 -0
- memorygraph/integration/__init__.py +69 -0
- memorygraph/integration/context_capture.py +426 -0
- memorygraph/integration/project_analysis.py +583 -0
- memorygraph/integration/workflow_tracking.py +492 -0
- memorygraph/intelligence/__init__.py +59 -0
- memorygraph/intelligence/context_retrieval.py +447 -0
- memorygraph/intelligence/entity_extraction.py +386 -0
- memorygraph/intelligence/pattern_recognition.py +420 -0
- memorygraph/intelligence/temporal.py +374 -0
- memorygraph/migration/__init__.py +27 -0
- memorygraph/migration/manager.py +579 -0
- memorygraph/migration/models.py +142 -0
- memorygraph/migration/scripts/__init__.py +17 -0
- memorygraph/migration/scripts/bitemporal_migration.py +595 -0
- memorygraph/migration/scripts/multitenancy_migration.py +452 -0
- memorygraph/migration_tools_module.py +146 -0
- memorygraph/models.py +684 -0
- memorygraph/proactive/__init__.py +46 -0
- memorygraph/proactive/outcome_learning.py +444 -0
- memorygraph/proactive/predictive.py +410 -0
- memorygraph/proactive/session_briefing.py +399 -0
- memorygraph/relationships.py +668 -0
- memorygraph/server.py +883 -0
- memorygraph/sqlite_database.py +1876 -0
- memorygraph/tools/__init__.py +59 -0
- memorygraph/tools/activity_tools.py +262 -0
- memorygraph/tools/memory_tools.py +315 -0
- memorygraph/tools/migration_tools.py +181 -0
- memorygraph/tools/relationship_tools.py +147 -0
- memorygraph/tools/search_tools.py +406 -0
- memorygraph/tools/temporal_tools.py +339 -0
- memorygraph/utils/__init__.py +10 -0
- memorygraph/utils/context_extractor.py +429 -0
- memorygraph/utils/error_handling.py +151 -0
- memorygraph/utils/export_import.py +425 -0
- memorygraph/utils/graph_algorithms.py +200 -0
- memorygraph/utils/pagination.py +149 -0
- memorygraph/utils/project_detection.py +133 -0
- memorygraphmcp-0.11.7.dist-info/METADATA +970 -0
- memorygraphmcp-0.11.7.dist-info/RECORD +65 -0
- memorygraphmcp-0.11.7.dist-info/WHEEL +4 -0
- memorygraphmcp-0.11.7.dist-info/entry_points.txt +2 -0
- memorygraphmcp-0.11.7.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backend factory for automatic backend selection.
|
|
3
|
+
|
|
4
|
+
This module provides a factory class that automatically selects the best available
|
|
5
|
+
graph database backend based on environment configuration and availability.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from .base import GraphBackend
|
|
13
|
+
from ..models import DatabaseConnectionError
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BackendFactory:
|
|
19
|
+
"""
|
|
20
|
+
Factory class for creating and selecting graph database backends.
|
|
21
|
+
|
|
22
|
+
Default: SQLite (zero-config)
|
|
23
|
+
|
|
24
|
+
Selection priority:
|
|
25
|
+
1. If MEMORY_BACKEND env var is set, use that specific backend
|
|
26
|
+
2. Default to SQLite for frictionless installation
|
|
27
|
+
3. "auto" mode tries: Neo4j → Memgraph → SQLite
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
async def create_backend() -> GraphBackend:
|
|
32
|
+
"""
|
|
33
|
+
Create and connect to the best available backend.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Connected GraphBackend instance
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
DatabaseConnectionError: If no backend can be connected
|
|
40
|
+
|
|
41
|
+
Selection logic:
|
|
42
|
+
- Default: SQLite (zero-config, no external dependencies)
|
|
43
|
+
- Explicit: Use MEMORY_BACKEND env var if set (neo4j, memgraph, falkordb, falkordblite, sqlite, ladybugdb, auto)
|
|
44
|
+
- Auto: Try backends in order until one connects successfully
|
|
45
|
+
"""
|
|
46
|
+
backend_type = os.getenv("MEMORY_BACKEND", "sqlite").lower()
|
|
47
|
+
|
|
48
|
+
if backend_type == "neo4j":
|
|
49
|
+
logger.info("Explicit backend selection: Neo4j")
|
|
50
|
+
return await BackendFactory._create_neo4j()
|
|
51
|
+
|
|
52
|
+
elif backend_type == "memgraph":
|
|
53
|
+
logger.info("Explicit backend selection: Memgraph")
|
|
54
|
+
return await BackendFactory._create_memgraph()
|
|
55
|
+
|
|
56
|
+
elif backend_type == "falkordb":
|
|
57
|
+
logger.info("Explicit backend selection: FalkorDB")
|
|
58
|
+
return await BackendFactory._create_falkordb()
|
|
59
|
+
|
|
60
|
+
elif backend_type == "falkordblite":
|
|
61
|
+
logger.info("Explicit backend selection: FalkorDBLite")
|
|
62
|
+
return await BackendFactory._create_falkordblite()
|
|
63
|
+
|
|
64
|
+
elif backend_type == "sqlite":
|
|
65
|
+
logger.info("Explicit backend selection: SQLite")
|
|
66
|
+
return await BackendFactory._create_sqlite()
|
|
67
|
+
|
|
68
|
+
elif backend_type == "turso":
|
|
69
|
+
logger.info("Explicit backend selection: Turso")
|
|
70
|
+
return await BackendFactory._create_turso()
|
|
71
|
+
|
|
72
|
+
elif backend_type == "cloud":
|
|
73
|
+
logger.info("Explicit backend selection: Cloud (MemoryGraph Cloud)")
|
|
74
|
+
return await BackendFactory._create_cloud()
|
|
75
|
+
|
|
76
|
+
elif backend_type == "ladybugdb":
|
|
77
|
+
logger.info("Explicit backend selection: LadybugDB")
|
|
78
|
+
return await BackendFactory._create_ladybugdb()
|
|
79
|
+
|
|
80
|
+
elif backend_type == "auto":
|
|
81
|
+
logger.info("Auto-selecting backend...")
|
|
82
|
+
return await BackendFactory._auto_select_backend()
|
|
83
|
+
|
|
84
|
+
else:
|
|
85
|
+
raise DatabaseConnectionError(
|
|
86
|
+
f"Unknown backend type: {backend_type}. "
|
|
87
|
+
f"Valid options: neo4j, memgraph, falkordb, falkordblite, sqlite, turso, ladybugdb, cloud, auto"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
async def _auto_select_backend() -> GraphBackend:
|
|
92
|
+
"""
|
|
93
|
+
Automatically select the best available backend.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Connected GraphBackend instance
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
DatabaseConnectionError: If no backend can be connected
|
|
100
|
+
"""
|
|
101
|
+
# Try Neo4j first (if password is configured)
|
|
102
|
+
neo4j_password = os.getenv("MEMORY_NEO4J_PASSWORD") or os.getenv("NEO4J_PASSWORD")
|
|
103
|
+
if neo4j_password:
|
|
104
|
+
try:
|
|
105
|
+
logger.info("Attempting to connect to Neo4j...")
|
|
106
|
+
backend = await BackendFactory._create_neo4j()
|
|
107
|
+
logger.info("✓ Successfully connected to Neo4j backend")
|
|
108
|
+
return backend
|
|
109
|
+
except DatabaseConnectionError as e:
|
|
110
|
+
logger.warning(f"Neo4j connection failed: {e}")
|
|
111
|
+
|
|
112
|
+
# Try Memgraph (Community Edition typically has no auth)
|
|
113
|
+
memgraph_uri = os.getenv("MEMORY_MEMGRAPH_URI")
|
|
114
|
+
if memgraph_uri:
|
|
115
|
+
try:
|
|
116
|
+
logger.info("Attempting to connect to Memgraph...")
|
|
117
|
+
backend = await BackendFactory._create_memgraph()
|
|
118
|
+
logger.info("✓ Successfully connected to Memgraph backend")
|
|
119
|
+
return backend
|
|
120
|
+
except DatabaseConnectionError as e:
|
|
121
|
+
logger.warning(f"Memgraph connection failed: {e}")
|
|
122
|
+
|
|
123
|
+
# Fall back to SQLite
|
|
124
|
+
try:
|
|
125
|
+
logger.info("Falling back to SQLite backend...")
|
|
126
|
+
backend = await BackendFactory._create_sqlite()
|
|
127
|
+
logger.info("✓ Successfully connected to SQLite backend")
|
|
128
|
+
return backend
|
|
129
|
+
except DatabaseConnectionError as e:
|
|
130
|
+
logger.error(f"SQLite backend failed: {e}")
|
|
131
|
+
raise DatabaseConnectionError(
|
|
132
|
+
"Could not connect to any backend. "
|
|
133
|
+
"Please configure Neo4j, Memgraph, or ensure NetworkX is installed for SQLite fallback."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
async def _create_neo4j() -> GraphBackend:
|
|
138
|
+
"""
|
|
139
|
+
Create and connect to Neo4j backend.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Connected Neo4jBackend instance
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
DatabaseConnectionError: If connection fails
|
|
146
|
+
"""
|
|
147
|
+
# Lazy import - only load neo4j backend when needed
|
|
148
|
+
from .neo4j_backend import Neo4jBackend
|
|
149
|
+
|
|
150
|
+
uri = os.getenv("MEMORY_NEO4J_URI") or os.getenv("NEO4J_URI")
|
|
151
|
+
user = os.getenv("MEMORY_NEO4J_USER") or os.getenv("NEO4J_USER")
|
|
152
|
+
password = os.getenv("MEMORY_NEO4J_PASSWORD") or os.getenv("NEO4J_PASSWORD")
|
|
153
|
+
|
|
154
|
+
if not password:
|
|
155
|
+
raise DatabaseConnectionError(
|
|
156
|
+
"Neo4j password not configured. "
|
|
157
|
+
"Set MEMORY_NEO4J_PASSWORD or NEO4J_PASSWORD environment variable."
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
backend = Neo4jBackend(uri=uri, user=user, password=password)
|
|
161
|
+
await backend.connect()
|
|
162
|
+
return backend
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
async def _create_memgraph() -> GraphBackend:
|
|
166
|
+
"""
|
|
167
|
+
Create and connect to Memgraph backend.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Connected MemgraphBackend instance
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
DatabaseConnectionError: If connection fails
|
|
174
|
+
"""
|
|
175
|
+
# Lazy import - only load memgraph backend when needed
|
|
176
|
+
from .memgraph_backend import MemgraphBackend
|
|
177
|
+
|
|
178
|
+
uri = os.getenv("MEMORY_MEMGRAPH_URI")
|
|
179
|
+
user = os.getenv("MEMORY_MEMGRAPH_USER", "")
|
|
180
|
+
password = os.getenv("MEMORY_MEMGRAPH_PASSWORD", "")
|
|
181
|
+
|
|
182
|
+
backend = MemgraphBackend(uri=uri, user=user, password=password)
|
|
183
|
+
await backend.connect()
|
|
184
|
+
return backend
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
async def _create_falkordb() -> GraphBackend:
|
|
188
|
+
"""
|
|
189
|
+
Create and connect to FalkorDB backend.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Connected FalkorDBBackend instance
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
DatabaseConnectionError: If connection fails
|
|
196
|
+
"""
|
|
197
|
+
# Lazy import - only load falkordb backend when needed
|
|
198
|
+
from .falkordb_backend import FalkorDBBackend
|
|
199
|
+
|
|
200
|
+
host = os.getenv("MEMORY_FALKORDB_HOST") or os.getenv("FALKORDB_HOST")
|
|
201
|
+
port_str = os.getenv("MEMORY_FALKORDB_PORT") or os.getenv("FALKORDB_PORT")
|
|
202
|
+
port = int(port_str) if port_str else None
|
|
203
|
+
password = os.getenv("MEMORY_FALKORDB_PASSWORD") or os.getenv("FALKORDB_PASSWORD")
|
|
204
|
+
|
|
205
|
+
backend = FalkorDBBackend(host=host, port=port, password=password)
|
|
206
|
+
await backend.connect()
|
|
207
|
+
return backend
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
async def _create_falkordblite() -> GraphBackend:
|
|
211
|
+
"""
|
|
212
|
+
Create and connect to FalkorDBLite backend.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Connected FalkorDBLiteBackend instance
|
|
216
|
+
|
|
217
|
+
Raises:
|
|
218
|
+
DatabaseConnectionError: If connection fails
|
|
219
|
+
"""
|
|
220
|
+
# Lazy import - only load falkordblite backend when needed
|
|
221
|
+
from .falkordblite_backend import FalkorDBLiteBackend
|
|
222
|
+
|
|
223
|
+
db_path = os.getenv("MEMORY_FALKORDBLITE_PATH") or os.getenv("FALKORDBLITE_PATH")
|
|
224
|
+
|
|
225
|
+
backend = FalkorDBLiteBackend(db_path=db_path)
|
|
226
|
+
await backend.connect()
|
|
227
|
+
return backend
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
async def _create_ladybugdb() -> GraphBackend:
|
|
231
|
+
"""
|
|
232
|
+
Create and connect to LadybugDB backend.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Connected LadybugDBBackend instance
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
DatabaseConnectionError: If connection fails
|
|
239
|
+
"""
|
|
240
|
+
# Lazy import - only load ladybugdb backend when needed
|
|
241
|
+
from .ladybugdb_backend import LadybugDBBackend
|
|
242
|
+
|
|
243
|
+
db_path = os.getenv("MEMORY_LADYBUGDB_PATH") or os.getenv("LADYBUGDB_PATH")
|
|
244
|
+
|
|
245
|
+
backend = LadybugDBBackend(db_path=db_path)
|
|
246
|
+
await backend.connect()
|
|
247
|
+
return backend
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
async def _create_sqlite() -> GraphBackend:
|
|
251
|
+
"""
|
|
252
|
+
Create and connect to SQLite fallback backend.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Connected SQLiteFallbackBackend instance
|
|
256
|
+
|
|
257
|
+
Raises:
|
|
258
|
+
DatabaseConnectionError: If connection fails
|
|
259
|
+
"""
|
|
260
|
+
# Lazy import - only load sqlite backend when needed
|
|
261
|
+
from .sqlite_fallback import SQLiteFallbackBackend
|
|
262
|
+
|
|
263
|
+
db_path = os.getenv("MEMORY_SQLITE_PATH")
|
|
264
|
+
backend = SQLiteFallbackBackend(db_path=db_path)
|
|
265
|
+
await backend.connect()
|
|
266
|
+
await backend.initialize_schema()
|
|
267
|
+
return backend
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
async def _create_turso() -> GraphBackend:
|
|
271
|
+
"""
|
|
272
|
+
Create and connect to Turso backend.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Connected TursoBackend instance
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
DatabaseConnectionError: If connection fails
|
|
279
|
+
"""
|
|
280
|
+
# Lazy import - only load turso backend when needed
|
|
281
|
+
from .turso import TursoBackend
|
|
282
|
+
|
|
283
|
+
db_path = os.getenv("MEMORY_TURSO_PATH")
|
|
284
|
+
sync_url = os.getenv("TURSO_DATABASE_URL") or os.getenv("MEMORYGRAPH_TURSO_URL")
|
|
285
|
+
auth_token = os.getenv("TURSO_AUTH_TOKEN") or os.getenv("MEMORYGRAPH_TURSO_TOKEN")
|
|
286
|
+
|
|
287
|
+
backend = TursoBackend(
|
|
288
|
+
db_path=db_path,
|
|
289
|
+
sync_url=sync_url,
|
|
290
|
+
auth_token=auth_token
|
|
291
|
+
)
|
|
292
|
+
await backend.connect()
|
|
293
|
+
await backend.initialize_schema()
|
|
294
|
+
return backend
|
|
295
|
+
|
|
296
|
+
@staticmethod
|
|
297
|
+
async def _create_cloud() -> GraphBackend:
|
|
298
|
+
"""
|
|
299
|
+
Create and connect to MemoryGraph Cloud backend.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Connected CloudBackend instance
|
|
303
|
+
|
|
304
|
+
Raises:
|
|
305
|
+
DatabaseConnectionError: If connection fails or API key not configured
|
|
306
|
+
"""
|
|
307
|
+
# Lazy import - only load cloud backend when needed
|
|
308
|
+
from .cloud_backend import CloudBackend
|
|
309
|
+
|
|
310
|
+
api_key = os.getenv("MEMORYGRAPH_API_KEY")
|
|
311
|
+
api_url = os.getenv("MEMORYGRAPH_API_URL")
|
|
312
|
+
timeout_str = os.getenv("MEMORYGRAPH_TIMEOUT")
|
|
313
|
+
timeout = int(timeout_str) if timeout_str else None
|
|
314
|
+
|
|
315
|
+
if not api_key:
|
|
316
|
+
raise DatabaseConnectionError(
|
|
317
|
+
"MEMORYGRAPH_API_KEY is required for cloud backend. "
|
|
318
|
+
"Get your API key at https://app.memorygraph.dev"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
backend = CloudBackend(
|
|
322
|
+
api_key=api_key,
|
|
323
|
+
api_url=api_url,
|
|
324
|
+
timeout=timeout
|
|
325
|
+
)
|
|
326
|
+
await backend.connect()
|
|
327
|
+
return backend
|
|
328
|
+
|
|
329
|
+
@staticmethod
|
|
330
|
+
async def create_from_config(config: 'BackendConfig') -> GraphBackend:
|
|
331
|
+
"""
|
|
332
|
+
Create backend from explicit configuration without using environment variables.
|
|
333
|
+
|
|
334
|
+
This is a thread-safe alternative to create_backend() that doesn't mutate
|
|
335
|
+
global environment variables.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
config: BackendConfig with backend type and connection details
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Connected GraphBackend instance
|
|
342
|
+
|
|
343
|
+
Raises:
|
|
344
|
+
DatabaseConnectionError: If backend creation or connection fails
|
|
345
|
+
|
|
346
|
+
Example:
|
|
347
|
+
config = BackendConfig(
|
|
348
|
+
backend_type=BackendType.SQLITE,
|
|
349
|
+
path="/path/to/db.sqlite"
|
|
350
|
+
)
|
|
351
|
+
backend = await BackendFactory.create_from_config(config)
|
|
352
|
+
"""
|
|
353
|
+
from ..migration.models import BackendConfig # Import here to avoid circular dependency
|
|
354
|
+
|
|
355
|
+
backend_type = config.backend_type.value
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
if backend_type == "sqlite":
|
|
359
|
+
return await BackendFactory._create_sqlite_with_path(config.path)
|
|
360
|
+
|
|
361
|
+
elif backend_type == "falkordblite":
|
|
362
|
+
return await BackendFactory._create_falkordblite_with_path(config.path)
|
|
363
|
+
|
|
364
|
+
elif backend_type == "ladybugdb":
|
|
365
|
+
return await BackendFactory._create_ladybugdb_with_path(config.path)
|
|
366
|
+
|
|
367
|
+
elif backend_type == "neo4j":
|
|
368
|
+
return await BackendFactory._create_neo4j_with_config(
|
|
369
|
+
uri=config.uri,
|
|
370
|
+
user=config.username,
|
|
371
|
+
password=config.password
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
elif backend_type == "memgraph":
|
|
375
|
+
return await BackendFactory._create_memgraph_with_config(
|
|
376
|
+
uri=config.uri,
|
|
377
|
+
user=config.username or "",
|
|
378
|
+
password=config.password or ""
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
elif backend_type == "falkordb":
|
|
382
|
+
# Parse host and port from URI (format: redis://host:port)
|
|
383
|
+
import re
|
|
384
|
+
if config.uri:
|
|
385
|
+
match = re.match(r'redis://([^:]+):(\d+)', config.uri)
|
|
386
|
+
if match:
|
|
387
|
+
host, port_str = match.groups()
|
|
388
|
+
port = int(port_str)
|
|
389
|
+
else:
|
|
390
|
+
raise DatabaseConnectionError(f"Invalid FalkorDB URI format: {config.uri}")
|
|
391
|
+
else:
|
|
392
|
+
raise DatabaseConnectionError("FalkorDB requires URI")
|
|
393
|
+
|
|
394
|
+
return await BackendFactory._create_falkordb_with_config(
|
|
395
|
+
host=host,
|
|
396
|
+
port=port,
|
|
397
|
+
password=config.password
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
elif backend_type == "turso":
|
|
401
|
+
return await BackendFactory._create_turso_with_config(
|
|
402
|
+
db_path=config.path,
|
|
403
|
+
sync_url=config.uri,
|
|
404
|
+
auth_token=config.password
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
elif backend_type == "cloud":
|
|
408
|
+
return await BackendFactory._create_cloud_with_config(
|
|
409
|
+
api_key=config.password, # Use password field for API key
|
|
410
|
+
api_url=config.uri
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
else:
|
|
414
|
+
raise DatabaseConnectionError(
|
|
415
|
+
f"Unknown backend type: {backend_type}. "
|
|
416
|
+
f"Valid options: neo4j, memgraph, falkordb, falkordblite, sqlite, turso, cloud"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
except Exception as e:
|
|
420
|
+
logger.error(f"Failed to create backend from config: {e}")
|
|
421
|
+
raise DatabaseConnectionError(f"Failed to create backend: {e}")
|
|
422
|
+
|
|
423
|
+
@staticmethod
|
|
424
|
+
async def _create_sqlite_with_path(db_path: Optional[str] = None) -> GraphBackend:
|
|
425
|
+
"""Create SQLite backend with explicit path (thread-safe)."""
|
|
426
|
+
from .sqlite_fallback import SQLiteFallbackBackend
|
|
427
|
+
|
|
428
|
+
backend = SQLiteFallbackBackend(db_path=db_path)
|
|
429
|
+
await backend.connect()
|
|
430
|
+
await backend.initialize_schema()
|
|
431
|
+
return backend
|
|
432
|
+
|
|
433
|
+
@staticmethod
|
|
434
|
+
async def _create_falkordblite_with_path(db_path: Optional[str] = None) -> GraphBackend:
|
|
435
|
+
"""Create FalkorDBLite backend with explicit path (thread-safe)."""
|
|
436
|
+
from .falkordblite_backend import FalkorDBLiteBackend
|
|
437
|
+
|
|
438
|
+
backend = FalkorDBLiteBackend(db_path=db_path)
|
|
439
|
+
await backend.connect()
|
|
440
|
+
return backend
|
|
441
|
+
|
|
442
|
+
@staticmethod
|
|
443
|
+
async def _create_ladybugdb_with_path(db_path: Optional[str] = None) -> GraphBackend:
|
|
444
|
+
"""Create LadybugDB backend with explicit path (thread-safe)."""
|
|
445
|
+
from .ladybugdb_backend import LadybugDBBackend
|
|
446
|
+
|
|
447
|
+
backend = LadybugDBBackend(db_path=db_path)
|
|
448
|
+
await backend.connect()
|
|
449
|
+
return backend
|
|
450
|
+
|
|
451
|
+
@staticmethod
|
|
452
|
+
async def _create_neo4j_with_config(
|
|
453
|
+
uri: Optional[str] = None,
|
|
454
|
+
user: Optional[str] = None,
|
|
455
|
+
password: Optional[str] = None
|
|
456
|
+
) -> GraphBackend:
|
|
457
|
+
"""Create Neo4j backend with explicit config (thread-safe)."""
|
|
458
|
+
from .neo4j_backend import Neo4jBackend
|
|
459
|
+
|
|
460
|
+
if not password:
|
|
461
|
+
raise DatabaseConnectionError("Neo4j password is required")
|
|
462
|
+
|
|
463
|
+
backend = Neo4jBackend(uri=uri, user=user, password=password)
|
|
464
|
+
await backend.connect()
|
|
465
|
+
return backend
|
|
466
|
+
|
|
467
|
+
@staticmethod
|
|
468
|
+
async def _create_memgraph_with_config(
|
|
469
|
+
uri: Optional[str] = None,
|
|
470
|
+
user: str = "",
|
|
471
|
+
password: str = ""
|
|
472
|
+
) -> GraphBackend:
|
|
473
|
+
"""Create Memgraph backend with explicit config (thread-safe)."""
|
|
474
|
+
from .memgraph_backend import MemgraphBackend
|
|
475
|
+
|
|
476
|
+
backend = MemgraphBackend(uri=uri, user=user, password=password)
|
|
477
|
+
await backend.connect()
|
|
478
|
+
return backend
|
|
479
|
+
|
|
480
|
+
@staticmethod
|
|
481
|
+
async def _create_falkordb_with_config(
|
|
482
|
+
host: Optional[str] = None,
|
|
483
|
+
port: Optional[int] = None,
|
|
484
|
+
password: Optional[str] = None
|
|
485
|
+
) -> GraphBackend:
|
|
486
|
+
"""Create FalkorDB backend with explicit config (thread-safe)."""
|
|
487
|
+
from .falkordb_backend import FalkorDBBackend
|
|
488
|
+
|
|
489
|
+
backend = FalkorDBBackend(host=host, port=port, password=password)
|
|
490
|
+
await backend.connect()
|
|
491
|
+
return backend
|
|
492
|
+
|
|
493
|
+
@staticmethod
|
|
494
|
+
async def _create_turso_with_config(
|
|
495
|
+
db_path: Optional[str] = None,
|
|
496
|
+
sync_url: Optional[str] = None,
|
|
497
|
+
auth_token: Optional[str] = None
|
|
498
|
+
) -> GraphBackend:
|
|
499
|
+
"""Create Turso backend with explicit config (thread-safe)."""
|
|
500
|
+
from .turso import TursoBackend
|
|
501
|
+
|
|
502
|
+
backend = TursoBackend(
|
|
503
|
+
db_path=db_path,
|
|
504
|
+
sync_url=sync_url,
|
|
505
|
+
auth_token=auth_token
|
|
506
|
+
)
|
|
507
|
+
await backend.connect()
|
|
508
|
+
await backend.initialize_schema()
|
|
509
|
+
return backend
|
|
510
|
+
|
|
511
|
+
@staticmethod
|
|
512
|
+
async def _create_cloud_with_config(
|
|
513
|
+
api_key: Optional[str] = None,
|
|
514
|
+
api_url: Optional[str] = None,
|
|
515
|
+
timeout: Optional[int] = None
|
|
516
|
+
) -> GraphBackend:
|
|
517
|
+
"""Create Cloud backend with explicit config (thread-safe)."""
|
|
518
|
+
from .cloud_backend import CloudBackend
|
|
519
|
+
|
|
520
|
+
if not api_key:
|
|
521
|
+
raise DatabaseConnectionError("MEMORYGRAPH_API_KEY is required for cloud backend")
|
|
522
|
+
|
|
523
|
+
backend = CloudBackend(
|
|
524
|
+
api_key=api_key,
|
|
525
|
+
api_url=api_url,
|
|
526
|
+
timeout=timeout
|
|
527
|
+
)
|
|
528
|
+
await backend.connect()
|
|
529
|
+
return backend
|
|
530
|
+
|
|
531
|
+
@staticmethod
|
|
532
|
+
def get_configured_backend_type() -> str:
|
|
533
|
+
"""
|
|
534
|
+
Get the configured backend type without connecting.
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Backend type string: "neo4j", "memgraph", "sqlite", or "auto"
|
|
538
|
+
"""
|
|
539
|
+
return os.getenv("MEMORY_BACKEND", "auto").lower()
|
|
540
|
+
|
|
541
|
+
@staticmethod
|
|
542
|
+
def is_backend_configured(backend_type: str) -> bool:
|
|
543
|
+
"""
|
|
544
|
+
Check if a specific backend is configured via environment variables.
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
backend_type: Backend type to check ("neo4j", "memgraph", "falkordb", "falkordblite", "sqlite")
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
True if backend appears to be configured
|
|
551
|
+
"""
|
|
552
|
+
if backend_type == "neo4j":
|
|
553
|
+
return bool(
|
|
554
|
+
os.getenv("MEMORY_NEO4J_PASSWORD") or
|
|
555
|
+
os.getenv("NEO4J_PASSWORD")
|
|
556
|
+
)
|
|
557
|
+
elif backend_type == "memgraph":
|
|
558
|
+
return bool(os.getenv("MEMORY_MEMGRAPH_URI"))
|
|
559
|
+
elif backend_type == "falkordb":
|
|
560
|
+
return bool(
|
|
561
|
+
os.getenv("MEMORY_FALKORDB_HOST") or
|
|
562
|
+
os.getenv("FALKORDB_HOST")
|
|
563
|
+
)
|
|
564
|
+
elif backend_type == "falkordblite":
|
|
565
|
+
return True # FalkorDBLite is always available (embedded, like SQLite)
|
|
566
|
+
elif backend_type == "sqlite":
|
|
567
|
+
return True # SQLite is always available if NetworkX is installed
|
|
568
|
+
elif backend_type == "turso":
|
|
569
|
+
return bool(
|
|
570
|
+
os.getenv("TURSO_DATABASE_URL") or
|
|
571
|
+
os.getenv("MEMORYGRAPH_TURSO_URL") or
|
|
572
|
+
os.getenv("MEMORY_TURSO_PATH")
|
|
573
|
+
)
|
|
574
|
+
elif backend_type == "cloud":
|
|
575
|
+
return bool(os.getenv("MEMORYGRAPH_API_KEY"))
|
|
576
|
+
else:
|
|
577
|
+
return False
|