MemoryOS 0.2.1__py3-none-any.whl → 0.2.2__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 MemoryOS might be problematic. Click here for more details.

Files changed (74) hide show
  1. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/METADATA +2 -1
  2. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/RECORD +72 -55
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +156 -65
  5. memos/api/context/context.py +147 -0
  6. memos/api/context/dependencies.py +90 -0
  7. memos/api/product_models.py +5 -1
  8. memos/api/routers/product_router.py +54 -26
  9. memos/configs/graph_db.py +49 -1
  10. memos/configs/internet_retriever.py +6 -0
  11. memos/configs/mem_os.py +5 -0
  12. memos/configs/mem_reader.py +9 -0
  13. memos/configs/mem_scheduler.py +18 -4
  14. memos/configs/mem_user.py +58 -0
  15. memos/graph_dbs/base.py +9 -1
  16. memos/graph_dbs/factory.py +2 -0
  17. memos/graph_dbs/nebular.py +1364 -0
  18. memos/graph_dbs/neo4j.py +4 -4
  19. memos/log.py +1 -1
  20. memos/mem_cube/utils.py +13 -6
  21. memos/mem_os/core.py +140 -30
  22. memos/mem_os/main.py +1 -1
  23. memos/mem_os/product.py +266 -152
  24. memos/mem_os/utils/format_utils.py +314 -67
  25. memos/mem_reader/simple_struct.py +13 -5
  26. memos/mem_scheduler/base_scheduler.py +220 -250
  27. memos/mem_scheduler/general_scheduler.py +193 -73
  28. memos/mem_scheduler/modules/base.py +5 -5
  29. memos/mem_scheduler/modules/dispatcher.py +6 -9
  30. memos/mem_scheduler/modules/misc.py +81 -16
  31. memos/mem_scheduler/modules/monitor.py +52 -41
  32. memos/mem_scheduler/modules/rabbitmq_service.py +9 -7
  33. memos/mem_scheduler/modules/retriever.py +108 -191
  34. memos/mem_scheduler/modules/scheduler_logger.py +255 -0
  35. memos/mem_scheduler/mos_for_test_scheduler.py +16 -19
  36. memos/mem_scheduler/schemas/__init__.py +0 -0
  37. memos/mem_scheduler/schemas/general_schemas.py +43 -0
  38. memos/mem_scheduler/schemas/message_schemas.py +148 -0
  39. memos/mem_scheduler/schemas/monitor_schemas.py +329 -0
  40. memos/mem_scheduler/utils/__init__.py +0 -0
  41. memos/mem_scheduler/utils/filter_utils.py +176 -0
  42. memos/mem_scheduler/utils/misc_utils.py +61 -0
  43. memos/mem_user/factory.py +94 -0
  44. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  45. memos/mem_user/mysql_user_manager.py +500 -0
  46. memos/mem_user/persistent_factory.py +96 -0
  47. memos/mem_user/user_manager.py +4 -4
  48. memos/memories/activation/item.py +4 -0
  49. memos/memories/textual/base.py +1 -1
  50. memos/memories/textual/general.py +35 -91
  51. memos/memories/textual/item.py +5 -33
  52. memos/memories/textual/tree.py +13 -7
  53. memos/memories/textual/tree_text_memory/organize/conflict.py +4 -2
  54. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +47 -43
  55. memos/memories/textual/tree_text_memory/organize/reorganizer.py +8 -5
  56. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
  57. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
  58. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
  59. memos/memories/textual/tree_text_memory/retrieve/searcher.py +46 -23
  60. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +42 -15
  61. memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
  62. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
  63. memos/memos_tools/dinding_report_bot.py +422 -0
  64. memos/memos_tools/notification_service.py +44 -0
  65. memos/memos_tools/notification_utils.py +96 -0
  66. memos/settings.py +3 -1
  67. memos/templates/mem_reader_prompts.py +2 -1
  68. memos/templates/mem_scheduler_prompts.py +41 -7
  69. memos/templates/mos_prompts.py +87 -0
  70. memos/mem_scheduler/modules/schemas.py +0 -328
  71. memos/mem_scheduler/utils.py +0 -75
  72. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/LICENSE +0 -0
  73. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/WHEEL +0 -0
  74. {memoryos-0.2.1.dist-info → memoryos-0.2.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,500 @@
1
+ """User management system for MemOS.
2
+
3
+ This module provides user authentication, authorization, and cube management
4
+ functionality using SQLAlchemy and MySQL.
5
+ """
6
+
7
+ import uuid
8
+
9
+ from datetime import datetime
10
+ from enum import Enum
11
+
12
+ from sqlalchemy import (
13
+ Boolean,
14
+ Column,
15
+ DateTime,
16
+ ForeignKey,
17
+ String,
18
+ Table,
19
+ create_engine,
20
+ )
21
+ from sqlalchemy.exc import IntegrityError
22
+ from sqlalchemy.orm import Session, declarative_base, relationship, sessionmaker
23
+
24
+ from memos.log import get_logger
25
+
26
+
27
+ logger = get_logger(__name__)
28
+
29
+ Base = declarative_base()
30
+
31
+
32
+ class UserRole(Enum):
33
+ """User roles enumeration."""
34
+
35
+ ROOT = "ROOT"
36
+ ADMIN = "ADMIN"
37
+ USER = "USER"
38
+ GUEST = "GUEST"
39
+
40
+
41
+ # Association table for many-to-many relationship between users and cubes
42
+ user_cube_association = Table(
43
+ "user_cube_association",
44
+ Base.metadata,
45
+ Column("user_id", String(255), ForeignKey("users.user_id"), primary_key=True),
46
+ Column("cube_id", String(255), ForeignKey("cubes.cube_id"), primary_key=True),
47
+ Column("created_at", DateTime, default=datetime.now),
48
+ )
49
+
50
+
51
+ class User(Base):
52
+ """User model for the database."""
53
+
54
+ __tablename__ = "users"
55
+
56
+ user_id = Column(String(255), primary_key=True, default=lambda: str(uuid.uuid4()))
57
+ user_name = Column(String(255), unique=True, nullable=False)
58
+ role = Column(String(20), default=UserRole.USER.value, nullable=False)
59
+ created_at = Column(DateTime, default=datetime.now, nullable=False)
60
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
61
+ is_active = Column(Boolean, default=True, nullable=False)
62
+
63
+ # Relationship with cubes
64
+ cubes = relationship("Cube", secondary=user_cube_association, back_populates="users")
65
+ owned_cubes = relationship("Cube", back_populates="owner", cascade="all, delete-orphan")
66
+
67
+ def __repr__(self):
68
+ return f"<User(user_id='{self.user_id}', user_name='{self.user_name}', role='{self.role.value}')>"
69
+
70
+
71
+ class Cube(Base):
72
+ """Cube model for the database."""
73
+
74
+ __tablename__ = "cubes"
75
+
76
+ cube_id = Column(String(255), primary_key=True, default=lambda: str(uuid.uuid4()))
77
+ cube_name = Column(String(255), nullable=False)
78
+ cube_path = Column(String(500), nullable=True) # Local path or remote repo
79
+ owner_id = Column(String(255), ForeignKey("users.user_id"), nullable=False)
80
+ created_at = Column(DateTime, default=datetime.now, nullable=False)
81
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
82
+ is_active = Column(Boolean, default=True, nullable=False)
83
+
84
+ # Relationships
85
+ owner = relationship("User", back_populates="owned_cubes")
86
+ users = relationship("User", secondary=user_cube_association, back_populates="cubes")
87
+
88
+ def __repr__(self):
89
+ return f"<Cube(cube_id='{self.cube_id}', cube_name='{self.cube_name}', owner_id='{self.owner_id}')>"
90
+
91
+
92
+ class MySQLUserManager:
93
+ """User management system for MemOS using MySQL."""
94
+
95
+ def __init__(
96
+ self,
97
+ user_id: str = "root",
98
+ host: str = "localhost",
99
+ port: int = 3306,
100
+ username: str = "root",
101
+ password: str = "",
102
+ database: str = "memos_users",
103
+ charset: str = "utf8mb4",
104
+ ):
105
+ """Initialize the user manager with MySQL database connection.
106
+
107
+ Args:
108
+ user_id (str, optional): User ID. If None, uses default user ID.
109
+ host (str): MySQL server host. Defaults to "localhost".
110
+ port (int): MySQL server port. Defaults to 3306.
111
+ username (str): MySQL username. Defaults to "root".
112
+ password (str): MySQL password. Defaults to "".
113
+ database (str): MySQL database name. Defaults to "memos_users".
114
+ charset (str): MySQL charset. Defaults to "utf8mb4".
115
+ """
116
+ # Build MySQL connection URL
117
+ if password:
118
+ connection_url = (
119
+ f"mysql+pymysql://{username}:{password}@{host}:{port}/{database}?charset={charset}"
120
+ )
121
+ else:
122
+ connection_url = (
123
+ f"mysql+pymysql://{username}@{host}:{port}/{database}?charset={charset}"
124
+ )
125
+
126
+ self.connection_url = connection_url
127
+ self.engine = create_engine(connection_url, echo=False, pool_pre_ping=True)
128
+ self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
129
+
130
+ # Create tables
131
+ Base.metadata.create_all(bind=self.engine)
132
+
133
+ # Initialize with root user if no users exist
134
+ self._init_root_user(user_id)
135
+
136
+ logger.info(f"MySQLUserManager initialized with database at {host}:{port}/{database}")
137
+
138
+ def _get_session(self) -> Session:
139
+ """Get a database session."""
140
+ return self.SessionLocal()
141
+
142
+ def _init_root_user(self, user_id: str) -> None:
143
+ """Initialize the root user if no users exist."""
144
+ session = self._get_session()
145
+ try:
146
+ # Check if any users exist
147
+ user_count = session.query(User).count()
148
+ if user_count == 0:
149
+ root_user = User(user_id=user_id, user_name=user_id, role=UserRole.ROOT)
150
+ session.add(root_user)
151
+ session.commit()
152
+ logger.info("Root user created successfully")
153
+ else:
154
+ self.create_user(user_name=user_id, user_id=user_id, role=UserRole.ROOT)
155
+ except Exception as e:
156
+ session.rollback()
157
+ logger.error(f"Failed to create {user_id} user: {e}")
158
+ finally:
159
+ session.close()
160
+
161
+ def create_user(
162
+ self, user_name: str, role: UserRole = UserRole.USER, user_id: str | None = None
163
+ ) -> str:
164
+ """Create a new user.
165
+
166
+ Args:
167
+ user_name (str): Name of the user.
168
+ role (UserRole): Role of the user.
169
+ user_id (str, optional): Custom user ID. If None, generates UUID.
170
+
171
+ Returns:
172
+ str: The created user ID.
173
+
174
+ Raises:
175
+ ValueError: If user_name already exists.
176
+ """
177
+ session = self._get_session()
178
+ try:
179
+ # Check if user_name already exists
180
+ existing_user = session.query(User).filter(User.user_name == user_name).first()
181
+ if existing_user:
182
+ logger.info(f"User with name '{user_name}' already exists")
183
+ return existing_user.user_id
184
+ user = User(user_name=user_name, role=role.value, user_id=user_id or str(uuid.uuid4()))
185
+ session.add(user)
186
+ session.commit()
187
+ logger.info(f"User '{user_name}' created with ID: {user.user_id}")
188
+ return user.user_id
189
+ except IntegrityError:
190
+ session.rollback()
191
+ logger.info(f"Failed to create user with name '{user_name}' already exists")
192
+ except Exception as e:
193
+ session.rollback()
194
+ logger.error(f"Error creating user: {e}")
195
+ raise
196
+ finally:
197
+ session.close()
198
+
199
+ def get_user(self, user_id: str) -> User | None:
200
+ """Get user by ID.
201
+
202
+ Args:
203
+ user_id (str): The user ID.
204
+
205
+ Returns:
206
+ User: The user object or None if not found.
207
+ """
208
+ session = self._get_session()
209
+ try:
210
+ return session.query(User).filter(User.user_id == user_id).first()
211
+ finally:
212
+ session.close()
213
+
214
+ def get_user_by_name(self, user_name: str) -> User | None:
215
+ """Get user by name.
216
+
217
+ Args:
218
+ user_name (str): The user name.
219
+
220
+ Returns:
221
+ User: The user object or None if not found.
222
+ """
223
+ session = self._get_session()
224
+ try:
225
+ return session.query(User).filter(User.user_name == user_name).first()
226
+ finally:
227
+ session.close()
228
+
229
+ def validate_user(self, user_id: str) -> bool:
230
+ """Validate if a user exists and is active.
231
+
232
+ Args:
233
+ user_id (str): The user ID to validate.
234
+
235
+ Returns:
236
+ bool: True if user exists and is active, False otherwise.
237
+ """
238
+ user = self.get_user(user_id)
239
+ return user is not None and user.is_active
240
+
241
+ def list_users(self) -> list[User]:
242
+ """List all active users.
243
+
244
+ Returns:
245
+ list[User]: List of all active users.
246
+ """
247
+ session = self._get_session()
248
+ try:
249
+ return session.query(User).filter(User.is_active).all()
250
+ finally:
251
+ session.close()
252
+
253
+ def create_cube(
254
+ self,
255
+ cube_name: str,
256
+ owner_id: str,
257
+ cube_path: str | None = None,
258
+ cube_id: str | None = None,
259
+ ) -> str:
260
+ """Create a new cube.
261
+
262
+ Args:
263
+ cube_name (str): Name of the cube.
264
+ owner_id (str): ID of the cube owner.
265
+ cube_path (str, optional): Path to the cube.
266
+ cube_id (str, optional): Custom cube ID. If None, generates UUID.
267
+
268
+ Returns:
269
+ str: The created cube ID.
270
+
271
+ Raises:
272
+ ValueError: If owner doesn't exist.
273
+ """
274
+ session = self._get_session()
275
+ try:
276
+ # Validate owner exists
277
+ owner = session.query(User).filter(User.user_id == owner_id).first()
278
+ if not owner:
279
+ raise ValueError(f"User with ID '{owner_id}' does not exist")
280
+
281
+ cube = Cube(
282
+ cube_name=cube_name,
283
+ owner_id=owner_id,
284
+ cube_path=cube_path,
285
+ cube_id=cube_id or str(uuid.uuid4()),
286
+ )
287
+ session.add(cube)
288
+
289
+ # Add owner to cube users
290
+ cube.users.append(owner)
291
+
292
+ session.commit()
293
+ logger.info(f"Cube '{cube_name}' created with ID: {cube.cube_id}")
294
+ return cube.cube_id
295
+ except Exception as e:
296
+ session.rollback()
297
+ logger.error(f"Error creating cube: {e}")
298
+ raise
299
+ finally:
300
+ session.close()
301
+
302
+ def get_cube(self, cube_id: str) -> Cube | None:
303
+ """Get cube by ID.
304
+
305
+ Args:
306
+ cube_id (str): The cube ID.
307
+
308
+ Returns:
309
+ Cube: The cube object or None if not found.
310
+ """
311
+ session = self._get_session()
312
+ try:
313
+ return session.query(Cube).filter(Cube.cube_id == cube_id).first()
314
+ finally:
315
+ session.close()
316
+
317
+ def validate_user_cube_access(self, user_id: str, cube_id: str) -> bool:
318
+ """Validate if a user has access to a cube.
319
+
320
+ Args:
321
+ user_id (str): The user ID.
322
+ cube_id (str): The cube ID.
323
+
324
+ Returns:
325
+ bool: True if user has access to cube, False otherwise.
326
+ """
327
+ session = self._get_session()
328
+ try:
329
+ # Check if user exists and is active
330
+ user = session.query(User).filter(User.user_id == user_id, User.is_active).first()
331
+ if not user:
332
+ return False
333
+
334
+ # Check if cube exists and is active
335
+ cube = session.query(Cube).filter(Cube.cube_id == cube_id, Cube.is_active).first()
336
+ if not cube:
337
+ return False
338
+
339
+ # Check if user has access to cube (owner or in users list)
340
+ if cube.owner_id == user_id:
341
+ return True
342
+
343
+ # Check many-to-many relationship
344
+ return user in cube.users
345
+ finally:
346
+ session.close()
347
+
348
+ def get_user_cubes(self, user_id: str) -> list[Cube]:
349
+ """Get all cubes accessible by a user.
350
+
351
+ Args:
352
+ user_id (str): The user ID.
353
+
354
+ Returns:
355
+ list[Cube]: List of cubes accessible by the user.
356
+ """
357
+ session = self._get_session()
358
+ try:
359
+ user = session.query(User).filter(User.user_id == user_id).first()
360
+ if not user:
361
+ return []
362
+
363
+ active_cubes = [cube for cube in user.cubes if cube.is_active]
364
+ return sorted(active_cubes, key=lambda cube: cube.created_at, reverse=True)
365
+ finally:
366
+ session.close()
367
+
368
+ def add_user_to_cube(self, user_id: str, cube_id: str) -> bool:
369
+ """Add a user to a cube's access list.
370
+
371
+ Args:
372
+ user_id (str): The user ID.
373
+ cube_id (str): The cube ID.
374
+
375
+ Returns:
376
+ bool: True if successful, False otherwise.
377
+ """
378
+ session = self._get_session()
379
+ try:
380
+ user = session.query(User).filter(User.user_id == user_id).first()
381
+ cube = session.query(Cube).filter(Cube.cube_id == cube_id).first()
382
+
383
+ if not user or not cube:
384
+ return False
385
+
386
+ if user not in cube.users:
387
+ cube.users.append(user)
388
+ session.commit()
389
+ logger.info(f"User '{user_id}' added to cube '{cube_id}'")
390
+
391
+ return True
392
+ except Exception as e:
393
+ session.rollback()
394
+ logger.error(f"Error adding user to cube: {e}")
395
+ return False
396
+ finally:
397
+ session.close()
398
+
399
+ def remove_user_from_cube(self, user_id: str, cube_id: str) -> bool:
400
+ """Remove a user from a cube's access list.
401
+
402
+ Args:
403
+ user_id (str): The user ID.
404
+ cube_id (str): The cube ID.
405
+
406
+ Returns:
407
+ bool: True if successful, False otherwise.
408
+ """
409
+ session = self._get_session()
410
+ try:
411
+ user = session.query(User).filter(User.user_id == user_id).first()
412
+ cube = session.query(Cube).filter(Cube.cube_id == cube_id).first()
413
+
414
+ if not user or not cube:
415
+ return False
416
+
417
+ # Don't remove owner
418
+ if cube.owner_id == user_id:
419
+ logger.warning(f"Cannot remove owner '{user_id}' from cube '{cube_id}'")
420
+ return False
421
+
422
+ if user in cube.users:
423
+ cube.users.remove(user)
424
+ session.commit()
425
+ logger.info(f"User '{user_id}' removed from cube '{cube_id}'")
426
+
427
+ return True
428
+ except Exception as e:
429
+ session.rollback()
430
+ logger.error(f"Error removing user from cube: {e}")
431
+ return False
432
+ finally:
433
+ session.close()
434
+
435
+ def delete_user(self, user_id: str) -> bool:
436
+ """Soft delete a user (set is_active to False).
437
+
438
+ Args:
439
+ user_id (str): The user ID.
440
+
441
+ Returns:
442
+ bool: True if successful, False otherwise.
443
+ """
444
+ session = self._get_session()
445
+ try:
446
+ user = session.query(User).filter(User.user_id == user_id).first()
447
+ if not user:
448
+ return False
449
+
450
+ # Don't delete root user
451
+ if user.role == UserRole.ROOT:
452
+ logger.warning("Cannot delete root user")
453
+ return False
454
+
455
+ user.is_active = False
456
+ session.commit()
457
+ logger.info(f"User '{user_id}' deactivated")
458
+ return True
459
+ except Exception as e:
460
+ session.rollback()
461
+ logger.error(f"Error deleting user: {e}")
462
+ return False
463
+ finally:
464
+ session.close()
465
+
466
+ def delete_cube(self, cube_id: str) -> bool:
467
+ """Soft delete a cube (set is_active to False).
468
+
469
+ Args:
470
+ cube_id (str): The cube ID.
471
+
472
+ Returns:
473
+ bool: True if successful, False otherwise.
474
+ """
475
+ session = self._get_session()
476
+ try:
477
+ cube = session.query(Cube).filter(Cube.cube_id == cube_id).first()
478
+ if not cube:
479
+ return False
480
+
481
+ cube.is_active = False
482
+ session.commit()
483
+ logger.info(f"Cube '{cube_id}' deactivated")
484
+ return True
485
+ except Exception as e:
486
+ session.rollback()
487
+ logger.error(f"Error deleting cube: {e}")
488
+ return False
489
+ finally:
490
+ session.close()
491
+
492
+ def close(self) -> None:
493
+ """Close the database engine and dispose of all connections.
494
+
495
+ This method should be called when the MySQLUserManager is no longer needed
496
+ to ensure proper cleanup of database connections.
497
+ """
498
+ if hasattr(self, "engine"):
499
+ self.engine.dispose()
500
+ logger.info("MySQLUserManager database connections closed")
@@ -0,0 +1,96 @@
1
+ from typing import Any, ClassVar
2
+
3
+ from memos.configs.mem_user import UserManagerConfigFactory
4
+ from memos.mem_user.mysql_persistent_user_manager import MySQLPersistentUserManager
5
+ from memos.mem_user.persistent_user_manager import PersistentUserManager
6
+
7
+
8
+ class PersistentUserManagerFactory:
9
+ """Factory class for creating persistent user manager instances."""
10
+
11
+ backend_to_class: ClassVar[dict[str, Any]] = {
12
+ "sqlite": PersistentUserManager,
13
+ "mysql": MySQLPersistentUserManager,
14
+ }
15
+
16
+ @classmethod
17
+ def from_config(
18
+ cls, config_factory: UserManagerConfigFactory
19
+ ) -> PersistentUserManager | MySQLPersistentUserManager:
20
+ """Create a persistent user manager instance from configuration.
21
+
22
+ Args:
23
+ config_factory: Configuration factory containing backend and config
24
+
25
+ Returns:
26
+ Persistent user manager instance
27
+
28
+ Raises:
29
+ ValueError: If backend is not supported
30
+ """
31
+ backend = config_factory.backend
32
+ if backend not in cls.backend_to_class:
33
+ raise ValueError(f"Invalid persistent user manager backend: {backend}")
34
+
35
+ user_manager_class = cls.backend_to_class[backend]
36
+ config = config_factory.config
37
+
38
+ # Use model_dump() to convert Pydantic model to dict and unpack as kwargs
39
+ return user_manager_class(**config.model_dump())
40
+
41
+ @classmethod
42
+ def create_sqlite(
43
+ cls, db_path: str | None = None, user_id: str = "root"
44
+ ) -> PersistentUserManager:
45
+ """Create SQLite persistent user manager with default configuration.
46
+
47
+ Args:
48
+ db_path: Path to SQLite database file
49
+ user_id: Default user ID for initialization
50
+
51
+ Returns:
52
+ SQLite persistent user manager instance
53
+ """
54
+ config_factory = UserManagerConfigFactory(
55
+ backend="sqlite", config={"db_path": db_path, "user_id": user_id}
56
+ )
57
+ return cls.from_config(config_factory)
58
+
59
+ @classmethod
60
+ def create_mysql(
61
+ cls,
62
+ user_id: str = "root",
63
+ host: str = "localhost",
64
+ port: int = 3306,
65
+ username: str = "root",
66
+ password: str = "",
67
+ database: str = "memos_users",
68
+ charset: str = "utf8mb4",
69
+ ) -> MySQLPersistentUserManager:
70
+ """Create MySQL persistent user manager with specified configuration.
71
+
72
+ Args:
73
+ user_id: Default user ID for initialization
74
+ host: MySQL server host
75
+ port: MySQL server port
76
+ username: MySQL username
77
+ password: MySQL password
78
+ database: MySQL database name
79
+ charset: MySQL charset
80
+
81
+ Returns:
82
+ MySQL persistent user manager instance
83
+ """
84
+ config_factory = UserManagerConfigFactory(
85
+ backend="mysql",
86
+ config={
87
+ "user_id": user_id,
88
+ "host": host,
89
+ "port": port,
90
+ "username": username,
91
+ "password": password,
92
+ "database": database,
93
+ "charset": charset,
94
+ },
95
+ )
96
+ return cls.from_config(config_factory)
@@ -37,10 +37,10 @@ Base = declarative_base()
37
37
  class UserRole(Enum):
38
38
  """User roles enumeration."""
39
39
 
40
- ROOT = "root"
41
- ADMIN = "admin"
42
- USER = "user"
43
- GUEST = "guest"
40
+ ROOT = "ROOT"
41
+ ADMIN = "ADMIN"
42
+ USER = "USER"
43
+ GUEST = "GUEST"
44
44
 
45
45
 
46
46
  # Association table for many-to-many relationship between users and cubes
@@ -18,6 +18,10 @@ class KVCacheRecords(BaseModel):
18
18
  default=[],
19
19
  description="The list of text memories transformed to the activation memory.",
20
20
  )
21
+ composed_text_memory: str = Field(
22
+ default="",
23
+ description="Single string combining all text_memories using assembly template",
24
+ )
21
25
  timestamp: datetime = Field(
22
26
  default_factory=datetime.now, description="submit time for schedule_messages"
23
27
  )
@@ -24,7 +24,7 @@ class BaseTextMemory(BaseMemory):
24
24
  """
25
25
 
26
26
  @abstractmethod
27
- def add(self, memories: list[TextualMemoryItem | dict[str, Any]]) -> None:
27
+ def add(self, memories: list[TextualMemoryItem | dict[str, Any]]) -> list[str]:
28
28
  """Add memories.
29
29
 
30
30
  Args: