mem-llm 1.1.0__py3-none-any.whl → 1.2.0__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 mem-llm might be problematic. Click here for more details.

@@ -0,0 +1,640 @@
1
+ """
2
+ Data Export/Import System
3
+ Supports multiple formats and databases: JSON, CSV, SQLite, PostgreSQL, MongoDB
4
+ """
5
+
6
+ import json
7
+ import csv
8
+ import sqlite3
9
+ from datetime import datetime
10
+ from typing import Dict, List, Optional, Any, Union
11
+ from pathlib import Path
12
+ import logging
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class DataExporter:
18
+ """Export memory data to various formats and databases"""
19
+
20
+ def __init__(self, memory_manager):
21
+ """
22
+ Args:
23
+ memory_manager: MemoryManager or SQLMemoryManager instance
24
+ """
25
+ self.memory = memory_manager
26
+
27
+ def export_to_json(self, user_id: str, output_file: str) -> Dict[str, Any]:
28
+ """
29
+ Export user data to JSON file
30
+
31
+ Args:
32
+ user_id: User ID to export
33
+ output_file: Output JSON file path
34
+
35
+ Returns:
36
+ Export statistics
37
+ """
38
+ try:
39
+ # Get all user data
40
+ conversations = self.memory.get_recent_conversations(user_id, limit=1000)
41
+ profile = getattr(self.memory, 'user_profiles', {}).get(user_id, {})
42
+
43
+ data = {
44
+ 'user_id': user_id,
45
+ 'export_date': datetime.now().isoformat(),
46
+ 'conversations': conversations,
47
+ 'profile': profile,
48
+ 'metadata': {
49
+ 'total_conversations': len(conversations),
50
+ 'format': 'json',
51
+ 'version': '1.0'
52
+ }
53
+ }
54
+
55
+ # Write to file
56
+ output_path = Path(output_file)
57
+ output_path.parent.mkdir(parents=True, exist_ok=True)
58
+
59
+ with open(output_path, 'w', encoding='utf-8') as f:
60
+ json.dump(data, f, ensure_ascii=False, indent=2)
61
+
62
+ logger.info(f"Exported {len(conversations)} conversations to {output_file}")
63
+
64
+ return {
65
+ 'success': True,
66
+ 'file': str(output_path),
67
+ 'conversations': len(conversations),
68
+ 'size_bytes': output_path.stat().st_size
69
+ }
70
+
71
+ except Exception as e:
72
+ logger.error(f"JSON export failed: {e}")
73
+ return {'success': False, 'error': str(e)}
74
+
75
+ def export_to_csv(self, user_id: str, output_file: str) -> Dict[str, Any]:
76
+ """
77
+ Export conversations to CSV file
78
+
79
+ Args:
80
+ user_id: User ID to export
81
+ output_file: Output CSV file path
82
+
83
+ Returns:
84
+ Export statistics
85
+ """
86
+ try:
87
+ conversations = self.memory.get_recent_conversations(user_id, limit=1000)
88
+
89
+ output_path = Path(output_file)
90
+ output_path.parent.mkdir(parents=True, exist_ok=True)
91
+
92
+ with open(output_path, 'w', newline='', encoding='utf-8') as f:
93
+ writer = csv.DictWriter(f, fieldnames=[
94
+ 'timestamp', 'user_message', 'bot_response', 'metadata'
95
+ ])
96
+ writer.writeheader()
97
+
98
+ for conv in conversations:
99
+ writer.writerow({
100
+ 'timestamp': conv.get('timestamp', ''),
101
+ 'user_message': conv.get('user_message', ''),
102
+ 'bot_response': conv.get('bot_response', ''),
103
+ 'metadata': json.dumps(conv.get('metadata', {}))
104
+ })
105
+
106
+ logger.info(f"Exported {len(conversations)} conversations to CSV: {output_file}")
107
+
108
+ return {
109
+ 'success': True,
110
+ 'file': str(output_path),
111
+ 'conversations': len(conversations),
112
+ 'size_bytes': output_path.stat().st_size
113
+ }
114
+
115
+ except Exception as e:
116
+ logger.error(f"CSV export failed: {e}")
117
+ return {'success': False, 'error': str(e)}
118
+
119
+ def export_to_sqlite(self, user_id: str, db_file: str) -> Dict[str, Any]:
120
+ """
121
+ Export to SQLite database
122
+
123
+ Args:
124
+ user_id: User ID to export
125
+ db_file: SQLite database file path
126
+
127
+ Returns:
128
+ Export statistics
129
+ """
130
+ try:
131
+ conversations = self.memory.get_recent_conversations(user_id, limit=1000)
132
+
133
+ db_path = Path(db_file)
134
+ db_path.parent.mkdir(parents=True, exist_ok=True)
135
+
136
+ conn = sqlite3.connect(str(db_path))
137
+ cursor = conn.cursor()
138
+
139
+ # Create table
140
+ cursor.execute('''
141
+ CREATE TABLE IF NOT EXISTS conversations (
142
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
143
+ user_id TEXT NOT NULL,
144
+ timestamp TEXT NOT NULL,
145
+ user_message TEXT NOT NULL,
146
+ bot_response TEXT NOT NULL,
147
+ metadata TEXT
148
+ )
149
+ ''')
150
+
151
+ # Insert conversations
152
+ for conv in conversations:
153
+ cursor.execute('''
154
+ INSERT INTO conversations
155
+ (user_id, timestamp, user_message, bot_response, metadata)
156
+ VALUES (?, ?, ?, ?, ?)
157
+ ''', (
158
+ user_id,
159
+ conv.get('timestamp', datetime.now().isoformat()),
160
+ conv.get('user_message', ''),
161
+ conv.get('bot_response', ''),
162
+ json.dumps(conv.get('metadata', {}))
163
+ ))
164
+
165
+ conn.commit()
166
+ conn.close()
167
+
168
+ logger.info(f"Exported {len(conversations)} conversations to SQLite: {db_file}")
169
+
170
+ return {
171
+ 'success': True,
172
+ 'file': str(db_path),
173
+ 'conversations': len(conversations),
174
+ 'size_bytes': db_path.stat().st_size
175
+ }
176
+
177
+ except Exception as e:
178
+ logger.error(f"SQLite export failed: {e}")
179
+ return {'success': False, 'error': str(e)}
180
+
181
+ def export_to_postgresql(self, user_id: str, connection_string: str) -> Dict[str, Any]:
182
+ """
183
+ Export to PostgreSQL database
184
+
185
+ Args:
186
+ user_id: User ID to export
187
+ connection_string: PostgreSQL connection string
188
+ (e.g., "postgresql://user:pass@localhost/dbname")
189
+
190
+ Returns:
191
+ Export statistics
192
+ """
193
+ try:
194
+ import psycopg2
195
+ from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
196
+ except ImportError:
197
+ return {
198
+ 'success': False,
199
+ 'error': 'psycopg2 not installed. Install: pip install psycopg2-binary'
200
+ }
201
+
202
+ try:
203
+ conversations = self.memory.get_recent_conversations(user_id, limit=1000)
204
+
205
+ # Parse connection string to get database name
206
+ import re
207
+ match = re.search(r'/([^/]+)(?:\?|$)', connection_string)
208
+ db_name = match.group(1) if match else None
209
+
210
+ # Try to connect, if database doesn't exist, create it
211
+ try:
212
+ conn = psycopg2.connect(connection_string)
213
+ except psycopg2.OperationalError as e:
214
+ if "does not exist" in str(e) and db_name:
215
+ # Connect to default 'postgres' database to create new one
216
+ base_conn_string = connection_string.rsplit('/', 1)[0] + '/postgres'
217
+ temp_conn = psycopg2.connect(base_conn_string)
218
+ temp_conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
219
+ temp_cursor = temp_conn.cursor()
220
+ temp_cursor.execute(f'CREATE DATABASE {db_name}')
221
+ temp_cursor.close()
222
+ temp_conn.close()
223
+ logger.info(f"Created PostgreSQL database: {db_name}")
224
+
225
+ # Now connect to the new database
226
+ conn = psycopg2.connect(connection_string)
227
+ else:
228
+ raise
229
+
230
+ cursor = conn.cursor()
231
+
232
+ # Create table if not exists
233
+ cursor.execute('''
234
+ CREATE TABLE IF NOT EXISTS conversations (
235
+ id SERIAL PRIMARY KEY,
236
+ user_id VARCHAR(255) NOT NULL,
237
+ timestamp TIMESTAMP NOT NULL,
238
+ user_message TEXT NOT NULL,
239
+ bot_response TEXT NOT NULL,
240
+ metadata JSONB
241
+ )
242
+ ''')
243
+
244
+ # Insert conversations
245
+ for conv in conversations:
246
+ cursor.execute('''
247
+ INSERT INTO conversations
248
+ (user_id, timestamp, user_message, bot_response, metadata)
249
+ VALUES (%s, %s, %s, %s, %s)
250
+ ''', (
251
+ user_id,
252
+ conv.get('timestamp', datetime.now().isoformat()),
253
+ conv.get('user_message', ''),
254
+ conv.get('bot_response', ''),
255
+ json.dumps(conv.get('metadata', {}))
256
+ ))
257
+
258
+ conn.commit()
259
+ cursor.close()
260
+ conn.close()
261
+
262
+ logger.info(f"Exported {len(conversations)} conversations to PostgreSQL")
263
+
264
+ return {
265
+ 'success': True,
266
+ 'database': 'postgresql',
267
+ 'conversations': len(conversations),
268
+ 'database_created': db_name is not None
269
+ }
270
+
271
+ except Exception as e:
272
+ logger.error(f"PostgreSQL export failed: {e}")
273
+ return {'success': False, 'error': str(e)}
274
+
275
+ def export_to_mongodb(self, user_id: str, connection_string: str,
276
+ database: str = 'mem_llm', collection: str = 'conversations') -> Dict[str, Any]:
277
+ """
278
+ Export to MongoDB database
279
+
280
+ Args:
281
+ user_id: User ID to export
282
+ connection_string: MongoDB connection string
283
+ (e.g., "mongodb://localhost:27017/")
284
+ database: Database name
285
+ collection: Collection name
286
+
287
+ Returns:
288
+ Export statistics
289
+ """
290
+ try:
291
+ from pymongo import MongoClient
292
+ except ImportError:
293
+ return {
294
+ 'success': False,
295
+ 'error': 'pymongo not installed. Install: pip install pymongo'
296
+ }
297
+
298
+ try:
299
+ conversations = self.memory.get_recent_conversations(user_id, limit=1000)
300
+
301
+ client = MongoClient(connection_string)
302
+
303
+ # MongoDB automatically creates database and collection if they don't exist
304
+ db = client[database]
305
+ coll = db[collection]
306
+
307
+ # Check if this is a new database/collection
308
+ is_new_db = database not in client.list_database_names()
309
+ is_new_collection = collection not in db.list_collection_names()
310
+
311
+ if is_new_db:
312
+ logger.info(f"Creating MongoDB database: {database}")
313
+ if is_new_collection:
314
+ logger.info(f"Creating MongoDB collection: {collection}")
315
+
316
+ # Prepare documents
317
+ documents = []
318
+ for conv in conversations:
319
+ doc = {
320
+ 'user_id': user_id,
321
+ 'timestamp': conv.get('timestamp', datetime.now().isoformat()),
322
+ 'user_message': conv.get('user_message', ''),
323
+ 'bot_response': conv.get('bot_response', ''),
324
+ 'metadata': conv.get('metadata', {}),
325
+ 'export_date': datetime.now()
326
+ }
327
+ documents.append(doc)
328
+
329
+ # Insert documents
330
+ result = coll.insert_many(documents)
331
+
332
+ client.close()
333
+
334
+ logger.info(f"Exported {len(conversations)} conversations to MongoDB")
335
+
336
+ return {
337
+ 'success': True,
338
+ 'database': 'mongodb',
339
+ 'conversations': len(conversations),
340
+ 'inserted_ids': len(result.inserted_ids),
341
+ 'database_created': is_new_db,
342
+ 'collection_created': is_new_collection
343
+ }
344
+
345
+ except Exception as e:
346
+ logger.error(f"MongoDB export failed: {e}")
347
+ return {'success': False, 'error': str(e)}
348
+
349
+
350
+ class DataImporter:
351
+ """Import memory data from various formats and databases"""
352
+
353
+ def __init__(self, memory_manager):
354
+ """
355
+ Args:
356
+ memory_manager: MemoryManager or SQLMemoryManager instance
357
+ """
358
+ self.memory = memory_manager
359
+
360
+ def import_from_json(self, input_file: str, user_id: Optional[str] = None) -> Dict[str, Any]:
361
+ """
362
+ Import user data from JSON file
363
+
364
+ Args:
365
+ input_file: Input JSON file path
366
+ user_id: Override user ID (use file's user_id if None)
367
+
368
+ Returns:
369
+ Import statistics
370
+ """
371
+ try:
372
+ input_path = Path(input_file)
373
+
374
+ if not input_path.exists():
375
+ return {'success': False, 'error': f'File not found: {input_file}'}
376
+
377
+ with open(input_path, 'r', encoding='utf-8') as f:
378
+ data = json.load(f)
379
+
380
+ # Get user ID
381
+ target_user_id = user_id or data.get('user_id')
382
+ if not target_user_id:
383
+ return {'success': False, 'error': 'No user_id specified'}
384
+
385
+ # Import conversations
386
+ conversations = data.get('conversations', [])
387
+ imported = 0
388
+
389
+ for conv in conversations:
390
+ self.memory.add_conversation(
391
+ target_user_id,
392
+ conv.get('user_message', ''),
393
+ conv.get('bot_response', ''),
394
+ conv.get('metadata', {})
395
+ )
396
+ imported += 1
397
+
398
+ # Import profile if available
399
+ if 'profile' in data and hasattr(self.memory, 'update_profile'):
400
+ self.memory.update_profile(target_user_id, data['profile'])
401
+
402
+ logger.info(f"Imported {imported} conversations from {input_file}")
403
+
404
+ return {
405
+ 'success': True,
406
+ 'file': str(input_path),
407
+ 'user_id': target_user_id,
408
+ 'conversations': imported
409
+ }
410
+
411
+ except Exception as e:
412
+ logger.error(f"JSON import failed: {e}")
413
+ return {'success': False, 'error': str(e)}
414
+
415
+ def import_from_csv(self, input_file: str, user_id: str) -> Dict[str, Any]:
416
+ """
417
+ Import conversations from CSV file
418
+
419
+ Args:
420
+ input_file: Input CSV file path
421
+ user_id: User ID for imported conversations
422
+
423
+ Returns:
424
+ Import statistics
425
+ """
426
+ try:
427
+ input_path = Path(input_file)
428
+
429
+ if not input_path.exists():
430
+ return {'success': False, 'error': f'File not found: {input_file}'}
431
+
432
+ imported = 0
433
+
434
+ with open(input_path, 'r', encoding='utf-8') as f:
435
+ reader = csv.DictReader(f)
436
+
437
+ for row in reader:
438
+ metadata = {}
439
+ if row.get('metadata'):
440
+ try:
441
+ metadata = json.loads(row['metadata'])
442
+ except:
443
+ pass
444
+
445
+ self.memory.add_conversation(
446
+ user_id,
447
+ row.get('user_message', ''),
448
+ row.get('bot_response', ''),
449
+ metadata
450
+ )
451
+ imported += 1
452
+
453
+ logger.info(f"Imported {imported} conversations from CSV: {input_file}")
454
+
455
+ return {
456
+ 'success': True,
457
+ 'file': str(input_path),
458
+ 'user_id': user_id,
459
+ 'conversations': imported
460
+ }
461
+
462
+ except Exception as e:
463
+ logger.error(f"CSV import failed: {e}")
464
+ return {'success': False, 'error': str(e)}
465
+
466
+ def import_from_sqlite(self, db_file: str, user_id: str) -> Dict[str, Any]:
467
+ """
468
+ Import from SQLite database
469
+
470
+ Args:
471
+ db_file: SQLite database file path
472
+ user_id: User ID to import data for
473
+
474
+ Returns:
475
+ Import statistics
476
+ """
477
+ try:
478
+ db_path = Path(db_file)
479
+
480
+ if not db_path.exists():
481
+ return {'success': False, 'error': f'Database not found: {db_file}'}
482
+
483
+ conn = sqlite3.connect(str(db_path))
484
+ conn.row_factory = sqlite3.Row
485
+ cursor = conn.cursor()
486
+
487
+ # Query conversations
488
+ cursor.execute('''
489
+ SELECT timestamp, user_message, bot_response, metadata
490
+ FROM conversations
491
+ WHERE user_id = ?
492
+ ORDER BY timestamp
493
+ ''', (user_id,))
494
+
495
+ imported = 0
496
+ for row in cursor.fetchall():
497
+ metadata = {}
498
+ if row['metadata']:
499
+ try:
500
+ metadata = json.loads(row['metadata'])
501
+ except:
502
+ pass
503
+
504
+ self.memory.add_conversation(
505
+ user_id,
506
+ row['user_message'],
507
+ row['bot_response'],
508
+ metadata
509
+ )
510
+ imported += 1
511
+
512
+ conn.close()
513
+
514
+ logger.info(f"Imported {imported} conversations from SQLite: {db_file}")
515
+
516
+ return {
517
+ 'success': True,
518
+ 'file': str(db_path),
519
+ 'user_id': user_id,
520
+ 'conversations': imported
521
+ }
522
+
523
+ except Exception as e:
524
+ logger.error(f"SQLite import failed: {e}")
525
+ return {'success': False, 'error': str(e)}
526
+
527
+ def import_from_postgresql(self, connection_string: str, user_id: str) -> Dict[str, Any]:
528
+ """
529
+ Import from PostgreSQL database
530
+
531
+ Args:
532
+ connection_string: PostgreSQL connection string
533
+ user_id: User ID to import data for
534
+
535
+ Returns:
536
+ Import statistics
537
+ """
538
+ try:
539
+ import psycopg2
540
+ from psycopg2.extras import RealDictCursor
541
+ except ImportError:
542
+ return {
543
+ 'success': False,
544
+ 'error': 'psycopg2 not installed. Install: pip install psycopg2-binary'
545
+ }
546
+
547
+ try:
548
+ conn = psycopg2.connect(connection_string)
549
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
550
+
551
+ # Query conversations
552
+ cursor.execute('''
553
+ SELECT timestamp, user_message, bot_response, metadata
554
+ FROM conversations
555
+ WHERE user_id = %s
556
+ ORDER BY timestamp
557
+ ''', (user_id,))
558
+
559
+ imported = 0
560
+ for row in cursor.fetchall():
561
+ metadata = row['metadata'] if isinstance(row['metadata'], dict) else {}
562
+
563
+ self.memory.add_conversation(
564
+ user_id,
565
+ row['user_message'],
566
+ row['bot_response'],
567
+ metadata
568
+ )
569
+ imported += 1
570
+
571
+ cursor.close()
572
+ conn.close()
573
+
574
+ logger.info(f"Imported {imported} conversations from PostgreSQL")
575
+
576
+ return {
577
+ 'success': True,
578
+ 'database': 'postgresql',
579
+ 'user_id': user_id,
580
+ 'conversations': imported
581
+ }
582
+
583
+ except Exception as e:
584
+ logger.error(f"PostgreSQL import failed: {e}")
585
+ return {'success': False, 'error': str(e)}
586
+
587
+ def import_from_mongodb(self, connection_string: str, user_id: str,
588
+ database: str = 'mem_llm', collection: str = 'conversations') -> Dict[str, Any]:
589
+ """
590
+ Import from MongoDB database
591
+
592
+ Args:
593
+ connection_string: MongoDB connection string
594
+ user_id: User ID to import data for
595
+ database: Database name
596
+ collection: Collection name
597
+
598
+ Returns:
599
+ Import statistics
600
+ """
601
+ try:
602
+ from pymongo import MongoClient
603
+ except ImportError:
604
+ return {
605
+ 'success': False,
606
+ 'error': 'pymongo not installed. Install: pip install pymongo'
607
+ }
608
+
609
+ try:
610
+ client = MongoClient(connection_string)
611
+ db = client[database]
612
+ coll = db[collection]
613
+
614
+ # Query conversations
615
+ documents = coll.find({'user_id': user_id}).sort('timestamp', 1)
616
+
617
+ imported = 0
618
+ for doc in documents:
619
+ self.memory.add_conversation(
620
+ user_id,
621
+ doc.get('user_message', ''),
622
+ doc.get('bot_response', ''),
623
+ doc.get('metadata', {})
624
+ )
625
+ imported += 1
626
+
627
+ client.close()
628
+
629
+ logger.info(f"Imported {imported} conversations from MongoDB")
630
+
631
+ return {
632
+ 'success': True,
633
+ 'database': 'mongodb',
634
+ 'user_id': user_id,
635
+ 'conversations': imported
636
+ }
637
+
638
+ except Exception as e:
639
+ logger.error(f"MongoDB import failed: {e}")
640
+ return {'success': False, 'error': str(e)}