MemoryOS 2.0.3__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 (315) hide show
  1. memoryos-2.0.3.dist-info/METADATA +418 -0
  2. memoryos-2.0.3.dist-info/RECORD +315 -0
  3. memoryos-2.0.3.dist-info/WHEEL +4 -0
  4. memoryos-2.0.3.dist-info/entry_points.txt +3 -0
  5. memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
  6. memos/__init__.py +20 -0
  7. memos/api/client.py +571 -0
  8. memos/api/config.py +1018 -0
  9. memos/api/context/dependencies.py +50 -0
  10. memos/api/exceptions.py +53 -0
  11. memos/api/handlers/__init__.py +62 -0
  12. memos/api/handlers/add_handler.py +158 -0
  13. memos/api/handlers/base_handler.py +194 -0
  14. memos/api/handlers/chat_handler.py +1401 -0
  15. memos/api/handlers/component_init.py +388 -0
  16. memos/api/handlers/config_builders.py +190 -0
  17. memos/api/handlers/feedback_handler.py +93 -0
  18. memos/api/handlers/formatters_handler.py +237 -0
  19. memos/api/handlers/memory_handler.py +316 -0
  20. memos/api/handlers/scheduler_handler.py +497 -0
  21. memos/api/handlers/search_handler.py +222 -0
  22. memos/api/handlers/suggestion_handler.py +117 -0
  23. memos/api/mcp_serve.py +614 -0
  24. memos/api/middleware/request_context.py +101 -0
  25. memos/api/product_api.py +38 -0
  26. memos/api/product_models.py +1206 -0
  27. memos/api/routers/__init__.py +1 -0
  28. memos/api/routers/product_router.py +477 -0
  29. memos/api/routers/server_router.py +394 -0
  30. memos/api/server_api.py +44 -0
  31. memos/api/start_api.py +433 -0
  32. memos/chunkers/__init__.py +4 -0
  33. memos/chunkers/base.py +24 -0
  34. memos/chunkers/charactertext_chunker.py +41 -0
  35. memos/chunkers/factory.py +24 -0
  36. memos/chunkers/markdown_chunker.py +62 -0
  37. memos/chunkers/sentence_chunker.py +54 -0
  38. memos/chunkers/simple_chunker.py +50 -0
  39. memos/cli.py +113 -0
  40. memos/configs/__init__.py +0 -0
  41. memos/configs/base.py +82 -0
  42. memos/configs/chunker.py +59 -0
  43. memos/configs/embedder.py +88 -0
  44. memos/configs/graph_db.py +236 -0
  45. memos/configs/internet_retriever.py +100 -0
  46. memos/configs/llm.py +151 -0
  47. memos/configs/mem_agent.py +54 -0
  48. memos/configs/mem_chat.py +81 -0
  49. memos/configs/mem_cube.py +105 -0
  50. memos/configs/mem_os.py +83 -0
  51. memos/configs/mem_reader.py +91 -0
  52. memos/configs/mem_scheduler.py +385 -0
  53. memos/configs/mem_user.py +70 -0
  54. memos/configs/memory.py +324 -0
  55. memos/configs/parser.py +38 -0
  56. memos/configs/reranker.py +18 -0
  57. memos/configs/utils.py +8 -0
  58. memos/configs/vec_db.py +80 -0
  59. memos/context/context.py +355 -0
  60. memos/dependency.py +52 -0
  61. memos/deprecation.py +262 -0
  62. memos/embedders/__init__.py +0 -0
  63. memos/embedders/ark.py +95 -0
  64. memos/embedders/base.py +106 -0
  65. memos/embedders/factory.py +29 -0
  66. memos/embedders/ollama.py +77 -0
  67. memos/embedders/sentence_transformer.py +49 -0
  68. memos/embedders/universal_api.py +51 -0
  69. memos/exceptions.py +30 -0
  70. memos/graph_dbs/__init__.py +0 -0
  71. memos/graph_dbs/base.py +274 -0
  72. memos/graph_dbs/factory.py +27 -0
  73. memos/graph_dbs/item.py +46 -0
  74. memos/graph_dbs/nebular.py +1794 -0
  75. memos/graph_dbs/neo4j.py +1942 -0
  76. memos/graph_dbs/neo4j_community.py +1058 -0
  77. memos/graph_dbs/polardb.py +5446 -0
  78. memos/hello_world.py +97 -0
  79. memos/llms/__init__.py +0 -0
  80. memos/llms/base.py +25 -0
  81. memos/llms/deepseek.py +13 -0
  82. memos/llms/factory.py +38 -0
  83. memos/llms/hf.py +443 -0
  84. memos/llms/hf_singleton.py +114 -0
  85. memos/llms/ollama.py +135 -0
  86. memos/llms/openai.py +222 -0
  87. memos/llms/openai_new.py +198 -0
  88. memos/llms/qwen.py +13 -0
  89. memos/llms/utils.py +14 -0
  90. memos/llms/vllm.py +218 -0
  91. memos/log.py +237 -0
  92. memos/mem_agent/base.py +19 -0
  93. memos/mem_agent/deepsearch_agent.py +391 -0
  94. memos/mem_agent/factory.py +36 -0
  95. memos/mem_chat/__init__.py +0 -0
  96. memos/mem_chat/base.py +30 -0
  97. memos/mem_chat/factory.py +21 -0
  98. memos/mem_chat/simple.py +200 -0
  99. memos/mem_cube/__init__.py +0 -0
  100. memos/mem_cube/base.py +30 -0
  101. memos/mem_cube/general.py +240 -0
  102. memos/mem_cube/navie.py +172 -0
  103. memos/mem_cube/utils.py +169 -0
  104. memos/mem_feedback/base.py +15 -0
  105. memos/mem_feedback/feedback.py +1192 -0
  106. memos/mem_feedback/simple_feedback.py +40 -0
  107. memos/mem_feedback/utils.py +230 -0
  108. memos/mem_os/client.py +5 -0
  109. memos/mem_os/core.py +1203 -0
  110. memos/mem_os/main.py +582 -0
  111. memos/mem_os/product.py +1608 -0
  112. memos/mem_os/product_server.py +455 -0
  113. memos/mem_os/utils/default_config.py +359 -0
  114. memos/mem_os/utils/format_utils.py +1403 -0
  115. memos/mem_os/utils/reference_utils.py +162 -0
  116. memos/mem_reader/__init__.py +0 -0
  117. memos/mem_reader/base.py +47 -0
  118. memos/mem_reader/factory.py +53 -0
  119. memos/mem_reader/memory.py +298 -0
  120. memos/mem_reader/multi_modal_struct.py +965 -0
  121. memos/mem_reader/read_multi_modal/__init__.py +43 -0
  122. memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
  123. memos/mem_reader/read_multi_modal/base.py +273 -0
  124. memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
  125. memos/mem_reader/read_multi_modal/image_parser.py +359 -0
  126. memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
  127. memos/mem_reader/read_multi_modal/string_parser.py +139 -0
  128. memos/mem_reader/read_multi_modal/system_parser.py +327 -0
  129. memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
  130. memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
  131. memos/mem_reader/read_multi_modal/user_parser.py +218 -0
  132. memos/mem_reader/read_multi_modal/utils.py +358 -0
  133. memos/mem_reader/simple_struct.py +912 -0
  134. memos/mem_reader/strategy_struct.py +163 -0
  135. memos/mem_reader/utils.py +157 -0
  136. memos/mem_scheduler/__init__.py +0 -0
  137. memos/mem_scheduler/analyzer/__init__.py +0 -0
  138. memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
  139. memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
  140. memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
  141. memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
  142. memos/mem_scheduler/base_scheduler.py +1319 -0
  143. memos/mem_scheduler/general_modules/__init__.py +0 -0
  144. memos/mem_scheduler/general_modules/api_misc.py +137 -0
  145. memos/mem_scheduler/general_modules/base.py +80 -0
  146. memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
  147. memos/mem_scheduler/general_modules/misc.py +313 -0
  148. memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
  149. memos/mem_scheduler/general_modules/task_threads.py +315 -0
  150. memos/mem_scheduler/general_scheduler.py +1495 -0
  151. memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
  152. memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
  153. memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
  154. memos/mem_scheduler/monitors/__init__.py +0 -0
  155. memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
  156. memos/mem_scheduler/monitors/general_monitor.py +394 -0
  157. memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
  158. memos/mem_scheduler/optimized_scheduler.py +410 -0
  159. memos/mem_scheduler/orm_modules/__init__.py +0 -0
  160. memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
  161. memos/mem_scheduler/orm_modules/base_model.py +729 -0
  162. memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
  163. memos/mem_scheduler/orm_modules/redis_model.py +699 -0
  164. memos/mem_scheduler/scheduler_factory.py +23 -0
  165. memos/mem_scheduler/schemas/__init__.py +0 -0
  166. memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
  167. memos/mem_scheduler/schemas/api_schemas.py +233 -0
  168. memos/mem_scheduler/schemas/general_schemas.py +55 -0
  169. memos/mem_scheduler/schemas/message_schemas.py +173 -0
  170. memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
  171. memos/mem_scheduler/schemas/task_schemas.py +132 -0
  172. memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
  173. memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
  174. memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
  175. memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
  176. memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
  177. memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
  178. memos/mem_scheduler/utils/__init__.py +0 -0
  179. memos/mem_scheduler/utils/api_utils.py +77 -0
  180. memos/mem_scheduler/utils/config_utils.py +100 -0
  181. memos/mem_scheduler/utils/db_utils.py +50 -0
  182. memos/mem_scheduler/utils/filter_utils.py +176 -0
  183. memos/mem_scheduler/utils/metrics.py +125 -0
  184. memos/mem_scheduler/utils/misc_utils.py +290 -0
  185. memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
  186. memos/mem_scheduler/utils/status_tracker.py +229 -0
  187. memos/mem_scheduler/webservice_modules/__init__.py +0 -0
  188. memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
  189. memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
  190. memos/mem_user/factory.py +94 -0
  191. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  192. memos/mem_user/mysql_user_manager.py +502 -0
  193. memos/mem_user/persistent_factory.py +98 -0
  194. memos/mem_user/persistent_user_manager.py +260 -0
  195. memos/mem_user/redis_persistent_user_manager.py +225 -0
  196. memos/mem_user/user_manager.py +488 -0
  197. memos/memories/__init__.py +0 -0
  198. memos/memories/activation/__init__.py +0 -0
  199. memos/memories/activation/base.py +42 -0
  200. memos/memories/activation/item.py +56 -0
  201. memos/memories/activation/kv.py +292 -0
  202. memos/memories/activation/vllmkv.py +219 -0
  203. memos/memories/base.py +19 -0
  204. memos/memories/factory.py +42 -0
  205. memos/memories/parametric/__init__.py +0 -0
  206. memos/memories/parametric/base.py +19 -0
  207. memos/memories/parametric/item.py +11 -0
  208. memos/memories/parametric/lora.py +41 -0
  209. memos/memories/textual/__init__.py +0 -0
  210. memos/memories/textual/base.py +92 -0
  211. memos/memories/textual/general.py +236 -0
  212. memos/memories/textual/item.py +304 -0
  213. memos/memories/textual/naive.py +187 -0
  214. memos/memories/textual/prefer_text_memory/__init__.py +0 -0
  215. memos/memories/textual/prefer_text_memory/adder.py +504 -0
  216. memos/memories/textual/prefer_text_memory/config.py +106 -0
  217. memos/memories/textual/prefer_text_memory/extractor.py +221 -0
  218. memos/memories/textual/prefer_text_memory/factory.py +85 -0
  219. memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
  220. memos/memories/textual/prefer_text_memory/spliter.py +132 -0
  221. memos/memories/textual/prefer_text_memory/utils.py +93 -0
  222. memos/memories/textual/preference.py +344 -0
  223. memos/memories/textual/simple_preference.py +161 -0
  224. memos/memories/textual/simple_tree.py +69 -0
  225. memos/memories/textual/tree.py +459 -0
  226. memos/memories/textual/tree_text_memory/__init__.py +0 -0
  227. memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
  228. memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
  229. memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
  230. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
  231. memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
  232. memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
  233. memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
  234. memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
  235. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
  236. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
  237. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
  238. memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
  239. memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
  240. memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
  241. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
  242. memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
  243. memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
  244. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
  245. memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
  246. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
  247. memos/memos_tools/dinding_report_bot.py +453 -0
  248. memos/memos_tools/lockfree_dict.py +120 -0
  249. memos/memos_tools/notification_service.py +44 -0
  250. memos/memos_tools/notification_utils.py +142 -0
  251. memos/memos_tools/singleton.py +174 -0
  252. memos/memos_tools/thread_safe_dict.py +310 -0
  253. memos/memos_tools/thread_safe_dict_segment.py +382 -0
  254. memos/multi_mem_cube/__init__.py +0 -0
  255. memos/multi_mem_cube/composite_cube.py +86 -0
  256. memos/multi_mem_cube/single_cube.py +874 -0
  257. memos/multi_mem_cube/views.py +54 -0
  258. memos/parsers/__init__.py +0 -0
  259. memos/parsers/base.py +15 -0
  260. memos/parsers/factory.py +21 -0
  261. memos/parsers/markitdown.py +28 -0
  262. memos/reranker/__init__.py +4 -0
  263. memos/reranker/base.py +25 -0
  264. memos/reranker/concat.py +103 -0
  265. memos/reranker/cosine_local.py +102 -0
  266. memos/reranker/factory.py +72 -0
  267. memos/reranker/http_bge.py +324 -0
  268. memos/reranker/http_bge_strategy.py +327 -0
  269. memos/reranker/noop.py +19 -0
  270. memos/reranker/strategies/__init__.py +4 -0
  271. memos/reranker/strategies/base.py +61 -0
  272. memos/reranker/strategies/concat_background.py +94 -0
  273. memos/reranker/strategies/concat_docsource.py +110 -0
  274. memos/reranker/strategies/dialogue_common.py +109 -0
  275. memos/reranker/strategies/factory.py +31 -0
  276. memos/reranker/strategies/single_turn.py +107 -0
  277. memos/reranker/strategies/singleturn_outmem.py +98 -0
  278. memos/settings.py +10 -0
  279. memos/templates/__init__.py +0 -0
  280. memos/templates/advanced_search_prompts.py +211 -0
  281. memos/templates/cloud_service_prompt.py +107 -0
  282. memos/templates/instruction_completion.py +66 -0
  283. memos/templates/mem_agent_prompts.py +85 -0
  284. memos/templates/mem_feedback_prompts.py +822 -0
  285. memos/templates/mem_reader_prompts.py +1096 -0
  286. memos/templates/mem_reader_strategy_prompts.py +238 -0
  287. memos/templates/mem_scheduler_prompts.py +626 -0
  288. memos/templates/mem_search_prompts.py +93 -0
  289. memos/templates/mos_prompts.py +403 -0
  290. memos/templates/prefer_complete_prompt.py +735 -0
  291. memos/templates/tool_mem_prompts.py +139 -0
  292. memos/templates/tree_reorganize_prompts.py +230 -0
  293. memos/types/__init__.py +34 -0
  294. memos/types/general_types.py +151 -0
  295. memos/types/openai_chat_completion_types/__init__.py +15 -0
  296. memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
  297. memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
  298. memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
  299. memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
  300. memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
  301. memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
  302. memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
  303. memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
  304. memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
  305. memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
  306. memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
  307. memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
  308. memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
  309. memos/utils.py +123 -0
  310. memos/vec_dbs/__init__.py +0 -0
  311. memos/vec_dbs/base.py +117 -0
  312. memos/vec_dbs/factory.py +23 -0
  313. memos/vec_dbs/item.py +50 -0
  314. memos/vec_dbs/milvus.py +654 -0
  315. memos/vec_dbs/qdrant.py +355 -0
@@ -0,0 +1,502 @@
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(
59
+ String(20), default=UserRole.USER.value, nullable=False
60
+ ) # for sqlite backend this is SQLEnum
61
+ created_at = Column(DateTime, default=datetime.now, nullable=False)
62
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
63
+ is_active = Column(Boolean, default=True, nullable=False)
64
+
65
+ # Relationship with cubes
66
+ cubes = relationship("Cube", secondary=user_cube_association, back_populates="users")
67
+ owned_cubes = relationship("Cube", back_populates="owner", cascade="all, delete-orphan")
68
+
69
+ def __repr__(self):
70
+ return f"<User(user_id='{self.user_id}', user_name='{self.user_name}', role='{self.role}')>"
71
+
72
+
73
+ class Cube(Base):
74
+ """Cube model for the database."""
75
+
76
+ __tablename__ = "cubes"
77
+
78
+ cube_id = Column(String(255), primary_key=True, default=lambda: str(uuid.uuid4()))
79
+ cube_name = Column(String(255), nullable=False)
80
+ cube_path = Column(String(500), nullable=True) # Local path or remote repo
81
+ owner_id = Column(String(255), ForeignKey("users.user_id"), nullable=False)
82
+ created_at = Column(DateTime, default=datetime.now, nullable=False)
83
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
84
+ is_active = Column(Boolean, default=True, nullable=False)
85
+
86
+ # Relationships
87
+ owner = relationship("User", back_populates="owned_cubes")
88
+ users = relationship("User", secondary=user_cube_association, back_populates="cubes")
89
+
90
+ def __repr__(self):
91
+ return f"<Cube(cube_id='{self.cube_id}', cube_name='{self.cube_name}', owner_id='{self.owner_id}')>"
92
+
93
+
94
+ class MySQLUserManager:
95
+ """User management system for MemOS using MySQL."""
96
+
97
+ def __init__(
98
+ self,
99
+ user_id: str = "root",
100
+ host: str = "localhost",
101
+ port: int = 3306,
102
+ username: str = "root",
103
+ password: str = "",
104
+ database: str = "memos_users",
105
+ charset: str = "utf8mb4",
106
+ ):
107
+ """Initialize the user manager with MySQL database connection.
108
+
109
+ Args:
110
+ user_id (str, optional): User ID. If None, uses default user ID.
111
+ host (str): MySQL server host. Defaults to "localhost".
112
+ port (int): MySQL server port. Defaults to 3306.
113
+ username (str): MySQL username. Defaults to "root".
114
+ password (str): MySQL password. Defaults to "".
115
+ database (str): MySQL database name. Defaults to "memos_users".
116
+ charset (str): MySQL charset. Defaults to "utf8mb4".
117
+ """
118
+ # Build MySQL connection URL
119
+ if password:
120
+ connection_url = (
121
+ f"mysql+pymysql://{username}:{password}@{host}:{port}/{database}?charset={charset}"
122
+ )
123
+ else:
124
+ connection_url = (
125
+ f"mysql+pymysql://{username}@{host}:{port}/{database}?charset={charset}"
126
+ )
127
+
128
+ self.connection_url = connection_url
129
+ self.engine = create_engine(connection_url, echo=False, pool_pre_ping=True)
130
+ self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
131
+
132
+ # Create tables
133
+ Base.metadata.create_all(bind=self.engine)
134
+
135
+ # Initialize with root user if no users exist
136
+ self._init_root_user(user_id)
137
+
138
+ logger.info(f"MySQLUserManager initialized with database at {host}:{port}/{database}")
139
+
140
+ def _get_session(self) -> Session:
141
+ """Get a database session."""
142
+ return self.SessionLocal()
143
+
144
+ def _init_root_user(self, user_id: str) -> None:
145
+ """Initialize the root user if no users exist."""
146
+ session = self._get_session()
147
+ try:
148
+ # Check if any users exist
149
+ user_count = session.query(User).count()
150
+ if user_count == 0:
151
+ root_user = User(user_id=user_id, user_name=user_id, role=UserRole.ROOT)
152
+ session.add(root_user)
153
+ session.commit()
154
+ logger.info("Root user created successfully")
155
+ else:
156
+ self.create_user(user_name=user_id, user_id=user_id, role=UserRole.ROOT)
157
+ except Exception as e:
158
+ session.rollback()
159
+ logger.error(f"Failed to create {user_id} user: {e}")
160
+ finally:
161
+ session.close()
162
+
163
+ def create_user(
164
+ self, user_name: str, role: UserRole = UserRole.USER, user_id: str | None = None
165
+ ) -> str:
166
+ """Create a new user.
167
+
168
+ Args:
169
+ user_name (str): Name of the user.
170
+ role (UserRole): Role of the user.
171
+ user_id (str, optional): Custom user ID. If None, generates UUID.
172
+
173
+ Returns:
174
+ str: The created user ID.
175
+
176
+ Raises:
177
+ ValueError: If user_name already exists.
178
+ """
179
+ session = self._get_session()
180
+ try:
181
+ # Check if user_name already exists
182
+ existing_user = session.query(User).filter(User.user_name == user_name).first()
183
+ if existing_user:
184
+ logger.info(f"User with name '{user_name}' already exists")
185
+ return existing_user.user_id
186
+ user = User(user_name=user_name, role=role.value, user_id=user_id or str(uuid.uuid4()))
187
+ session.add(user)
188
+ session.commit()
189
+ logger.info(f"User '{user_name}' created with ID: {user.user_id}")
190
+ return user.user_id
191
+ except IntegrityError:
192
+ session.rollback()
193
+ logger.info(f"Failed to create user with name '{user_name}' already exists")
194
+ except Exception as e:
195
+ session.rollback()
196
+ logger.error(f"Error creating user: {e}")
197
+ raise
198
+ finally:
199
+ session.close()
200
+
201
+ def get_user(self, user_id: str) -> User | None:
202
+ """Get user by ID.
203
+
204
+ Args:
205
+ user_id (str): The user ID.
206
+
207
+ Returns:
208
+ User: The user object or None if not found.
209
+ """
210
+ session = self._get_session()
211
+ try:
212
+ return session.query(User).filter(User.user_id == user_id).first()
213
+ finally:
214
+ session.close()
215
+
216
+ def get_user_by_name(self, user_name: str) -> User | None:
217
+ """Get user by name.
218
+
219
+ Args:
220
+ user_name (str): The user name.
221
+
222
+ Returns:
223
+ User: The user object or None if not found.
224
+ """
225
+ session = self._get_session()
226
+ try:
227
+ return session.query(User).filter(User.user_name == user_name).first()
228
+ finally:
229
+ session.close()
230
+
231
+ def validate_user(self, user_id: str) -> bool:
232
+ """Validate if a user exists and is active.
233
+
234
+ Args:
235
+ user_id (str): The user ID to validate.
236
+
237
+ Returns:
238
+ bool: True if user exists and is active, False otherwise.
239
+ """
240
+ user = self.get_user(user_id)
241
+ return user is not None and user.is_active
242
+
243
+ def list_users(self) -> list[User]:
244
+ """List all active users.
245
+
246
+ Returns:
247
+ list[User]: List of all active users.
248
+ """
249
+ session = self._get_session()
250
+ try:
251
+ return session.query(User).filter(User.is_active).all()
252
+ finally:
253
+ session.close()
254
+
255
+ def create_cube(
256
+ self,
257
+ cube_name: str,
258
+ owner_id: str,
259
+ cube_path: str | None = None,
260
+ cube_id: str | None = None,
261
+ ) -> str:
262
+ """Create a new cube.
263
+
264
+ Args:
265
+ cube_name (str): Name of the cube.
266
+ owner_id (str): ID of the cube owner.
267
+ cube_path (str, optional): Path to the cube.
268
+ cube_id (str, optional): Custom cube ID. If None, generates UUID.
269
+
270
+ Returns:
271
+ str: The created cube ID.
272
+
273
+ Raises:
274
+ ValueError: If owner doesn't exist.
275
+ """
276
+ session = self._get_session()
277
+ try:
278
+ # Validate owner exists
279
+ owner = session.query(User).filter(User.user_id == owner_id).first()
280
+ if not owner:
281
+ raise ValueError(f"User with ID '{owner_id}' does not exist")
282
+
283
+ cube = Cube(
284
+ cube_name=cube_name,
285
+ owner_id=owner_id,
286
+ cube_path=cube_path,
287
+ cube_id=cube_id or str(uuid.uuid4()),
288
+ )
289
+ session.add(cube)
290
+
291
+ # Add owner to cube users
292
+ cube.users.append(owner)
293
+
294
+ session.commit()
295
+ logger.info(f"Cube '{cube_name}' created with ID: {cube.cube_id}")
296
+ return cube.cube_id
297
+ except Exception as e:
298
+ session.rollback()
299
+ logger.error(f"Error creating cube: {e}")
300
+ raise
301
+ finally:
302
+ session.close()
303
+
304
+ def get_cube(self, cube_id: str) -> Cube | None:
305
+ """Get cube by ID.
306
+
307
+ Args:
308
+ cube_id (str): The cube ID.
309
+
310
+ Returns:
311
+ Cube: The cube object or None if not found.
312
+ """
313
+ session = self._get_session()
314
+ try:
315
+ return session.query(Cube).filter(Cube.cube_id == cube_id).first()
316
+ finally:
317
+ session.close()
318
+
319
+ def validate_user_cube_access(self, user_id: str, cube_id: str) -> bool:
320
+ """Validate if a user has access to a cube.
321
+
322
+ Args:
323
+ user_id (str): The user ID.
324
+ cube_id (str): The cube ID.
325
+
326
+ Returns:
327
+ bool: True if user has access to cube, False otherwise.
328
+ """
329
+ session = self._get_session()
330
+ try:
331
+ # Check if user exists and is active
332
+ user = session.query(User).filter(User.user_id == user_id, User.is_active).first()
333
+ if not user:
334
+ return False
335
+
336
+ # Check if cube exists and is active
337
+ cube = session.query(Cube).filter(Cube.cube_id == cube_id, Cube.is_active).first()
338
+ if not cube:
339
+ return False
340
+
341
+ # Check if user has access to cube (owner or in users list)
342
+ if cube.owner_id == user_id:
343
+ return True
344
+
345
+ # Check many-to-many relationship
346
+ return user in cube.users
347
+ finally:
348
+ session.close()
349
+
350
+ def get_user_cubes(self, user_id: str) -> list[Cube]:
351
+ """Get all cubes accessible by a user.
352
+
353
+ Args:
354
+ user_id (str): The user ID.
355
+
356
+ Returns:
357
+ list[Cube]: List of cubes accessible by the user.
358
+ """
359
+ session = self._get_session()
360
+ try:
361
+ user = session.query(User).filter(User.user_id == user_id).first()
362
+ if not user:
363
+ return []
364
+
365
+ active_cubes = [cube for cube in user.cubes if cube.is_active]
366
+ return sorted(active_cubes, key=lambda cube: cube.created_at, reverse=True)
367
+ finally:
368
+ session.close()
369
+
370
+ def add_user_to_cube(self, user_id: str, cube_id: str) -> bool:
371
+ """Add a user to a cube's access list.
372
+
373
+ Args:
374
+ user_id (str): The user ID.
375
+ cube_id (str): The cube ID.
376
+
377
+ Returns:
378
+ bool: True if successful, False otherwise.
379
+ """
380
+ session = self._get_session()
381
+ try:
382
+ user = session.query(User).filter(User.user_id == user_id).first()
383
+ cube = session.query(Cube).filter(Cube.cube_id == cube_id).first()
384
+
385
+ if not user or not cube:
386
+ return False
387
+
388
+ if user not in cube.users:
389
+ cube.users.append(user)
390
+ session.commit()
391
+ logger.info(f"User '{user_id}' added to cube '{cube_id}'")
392
+
393
+ return True
394
+ except Exception as e:
395
+ session.rollback()
396
+ logger.error(f"Error adding user to cube: {e}")
397
+ return False
398
+ finally:
399
+ session.close()
400
+
401
+ def remove_user_from_cube(self, user_id: str, cube_id: str) -> bool:
402
+ """Remove a user from a cube's access list.
403
+
404
+ Args:
405
+ user_id (str): The user ID.
406
+ cube_id (str): The cube ID.
407
+
408
+ Returns:
409
+ bool: True if successful, False otherwise.
410
+ """
411
+ session = self._get_session()
412
+ try:
413
+ user = session.query(User).filter(User.user_id == user_id).first()
414
+ cube = session.query(Cube).filter(Cube.cube_id == cube_id).first()
415
+
416
+ if not user or not cube:
417
+ return False
418
+
419
+ # Don't remove owner
420
+ if cube.owner_id == user_id:
421
+ logger.warning(f"Cannot remove owner '{user_id}' from cube '{cube_id}'")
422
+ return False
423
+
424
+ if user in cube.users:
425
+ cube.users.remove(user)
426
+ session.commit()
427
+ logger.info(f"User '{user_id}' removed from cube '{cube_id}'")
428
+
429
+ return True
430
+ except Exception as e:
431
+ session.rollback()
432
+ logger.error(f"Error removing user from cube: {e}")
433
+ return False
434
+ finally:
435
+ session.close()
436
+
437
+ def delete_user(self, user_id: str) -> bool:
438
+ """Soft delete a user (set is_active to False).
439
+
440
+ Args:
441
+ user_id (str): The user ID.
442
+
443
+ Returns:
444
+ bool: True if successful, False otherwise.
445
+ """
446
+ session = self._get_session()
447
+ try:
448
+ user = session.query(User).filter(User.user_id == user_id).first()
449
+ if not user:
450
+ return False
451
+
452
+ # Don't delete root user
453
+ if user.role == UserRole.ROOT:
454
+ logger.warning("Cannot delete root user")
455
+ return False
456
+
457
+ user.is_active = False
458
+ session.commit()
459
+ logger.info(f"User '{user_id}' deactivated")
460
+ return True
461
+ except Exception as e:
462
+ session.rollback()
463
+ logger.error(f"Error deleting user: {e}")
464
+ return False
465
+ finally:
466
+ session.close()
467
+
468
+ def delete_cube(self, cube_id: str) -> bool:
469
+ """Soft delete a cube (set is_active to False).
470
+
471
+ Args:
472
+ cube_id (str): The cube ID.
473
+
474
+ Returns:
475
+ bool: True if successful, False otherwise.
476
+ """
477
+ session = self._get_session()
478
+ try:
479
+ cube = session.query(Cube).filter(Cube.cube_id == cube_id).first()
480
+ if not cube:
481
+ return False
482
+
483
+ cube.is_active = False
484
+ session.commit()
485
+ logger.info(f"Cube '{cube_id}' deactivated")
486
+ return True
487
+ except Exception as e:
488
+ session.rollback()
489
+ logger.error(f"Error deleting cube: {e}")
490
+ return False
491
+ finally:
492
+ session.close()
493
+
494
+ def close(self) -> None:
495
+ """Close the database engine and dispose of all connections.
496
+
497
+ This method should be called when the MySQLUserManager is no longer needed
498
+ to ensure proper cleanup of database connections.
499
+ """
500
+ if hasattr(self, "engine"):
501
+ self.engine.dispose()
502
+ logger.info("MySQLUserManager database connections closed")
@@ -0,0 +1,98 @@
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
+ from memos.mem_user.redis_persistent_user_manager import RedisPersistentUserManager
7
+
8
+
9
+ class PersistentUserManagerFactory:
10
+ """Factory class for creating persistent user manager instances."""
11
+
12
+ backend_to_class: ClassVar[dict[str, Any]] = {
13
+ "sqlite": PersistentUserManager,
14
+ "mysql": MySQLPersistentUserManager,
15
+ "redis": RedisPersistentUserManager,
16
+ }
17
+
18
+ @classmethod
19
+ def from_config(
20
+ cls, config_factory: UserManagerConfigFactory
21
+ ) -> PersistentUserManager | MySQLPersistentUserManager:
22
+ """Create a persistent user manager instance from configuration.
23
+
24
+ Args:
25
+ config_factory: Configuration factory containing backend and config
26
+
27
+ Returns:
28
+ Persistent user manager instance
29
+
30
+ Raises:
31
+ ValueError: If backend is not supported
32
+ """
33
+ backend = config_factory.backend
34
+ if backend not in cls.backend_to_class:
35
+ raise ValueError(f"Invalid persistent user manager backend: {backend}")
36
+
37
+ user_manager_class = cls.backend_to_class[backend]
38
+ config = config_factory.config
39
+
40
+ # Use model_dump() to convert Pydantic model to dict and unpack as kwargs
41
+ return user_manager_class(**config.model_dump())
42
+
43
+ @classmethod
44
+ def create_sqlite(
45
+ cls, db_path: str | None = None, user_id: str = "root"
46
+ ) -> PersistentUserManager:
47
+ """Create SQLite persistent user manager with default configuration.
48
+
49
+ Args:
50
+ db_path: Path to SQLite database file
51
+ user_id: Default user ID for initialization
52
+
53
+ Returns:
54
+ SQLite persistent user manager instance
55
+ """
56
+ config_factory = UserManagerConfigFactory(
57
+ backend="sqlite", config={"db_path": db_path, "user_id": user_id}
58
+ )
59
+ return cls.from_config(config_factory)
60
+
61
+ @classmethod
62
+ def create_mysql(
63
+ cls,
64
+ user_id: str = "root",
65
+ host: str = "localhost",
66
+ port: int = 3306,
67
+ username: str = "root",
68
+ password: str = "",
69
+ database: str = "memos_users",
70
+ charset: str = "utf8mb4",
71
+ ) -> MySQLPersistentUserManager:
72
+ """Create MySQL persistent user manager with specified configuration.
73
+
74
+ Args:
75
+ user_id: Default user ID for initialization
76
+ host: MySQL server host
77
+ port: MySQL server port
78
+ username: MySQL username
79
+ password: MySQL password
80
+ database: MySQL database name
81
+ charset: MySQL charset
82
+
83
+ Returns:
84
+ MySQL persistent user manager instance
85
+ """
86
+ config_factory = UserManagerConfigFactory(
87
+ backend="mysql",
88
+ config={
89
+ "user_id": user_id,
90
+ "host": host,
91
+ "port": port,
92
+ "username": username,
93
+ "password": password,
94
+ "database": database,
95
+ "charset": charset,
96
+ },
97
+ )
98
+ return cls.from_config(config_factory)