Skip to content

Commit d534bf0

Browse files
committed
fix
1 parent 018d759 commit d534bf0

File tree

2 files changed

+200
-1
lines changed

2 files changed

+200
-1
lines changed

src/memos/api/config.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
import base64
2+
import hashlib
3+
import hmac
14
import json
5+
import logging
26
import os
7+
import re
8+
import time
39

410
from typing import Any
511

12+
import requests
13+
614
from dotenv import load_dotenv
715

816
from memos.configs.mem_cube import GeneralMemCubeConfig
@@ -13,6 +21,198 @@
1321
# Load environment variables
1422
load_dotenv()
1523

24+
logger = logging.getLogger(__name__)
25+
26+
27+
def _update_env_from_dict(data: dict[str, Any]) -> None:
28+
"""Apply a dict to environment variables, with change logging."""
29+
30+
def _is_sensitive(name: str) -> bool:
31+
n = name.upper()
32+
return any(s in n for s in ["PASSWORD", "SECRET", "AK", "SK", "TOKEN", "KEY"])
33+
34+
for k, v in data.items():
35+
if isinstance(v, dict):
36+
new_val = json.dumps(v, ensure_ascii=False)
37+
elif isinstance(v, bool):
38+
new_val = "true" if v else "false"
39+
elif v is None:
40+
new_val = ""
41+
else:
42+
new_val = str(v)
43+
44+
old_val = os.environ.get(k)
45+
os.environ[k] = new_val
46+
47+
try:
48+
log_old = "***" if _is_sensitive(k) else (old_val if old_val is not None else "<unset>")
49+
log_new = "***" if _is_sensitive(k) else new_val
50+
if old_val != new_val:
51+
logger.info(f"Nacos config update: {k}={log_new} (was {log_old})")
52+
except Exception as e:
53+
# Avoid logging failures blocking config updates
54+
logger.debug(f"Skip logging change for {k}: {e}")
55+
56+
57+
def get_config_json(name: str, default: Any | None = None) -> Any:
58+
"""Read JSON object/array from env and parse. Returns default on missing/invalid."""
59+
raw = os.getenv(name)
60+
if not raw:
61+
return default
62+
try:
63+
return json.loads(raw)
64+
except Exception:
65+
logger.warning(f"Invalid JSON in env '{name}', returning default.")
66+
return default
67+
68+
69+
def get_config_value(path: str, default: Any | None = None) -> Any:
70+
"""Read value from env with optional dot-path for structured configs.
71+
72+
Examples:
73+
- get_config_value("MONGODB_CONFIG.base_uri")
74+
- get_config_value("MONGODB_BASE_URI")
75+
"""
76+
if "." not in path:
77+
val = os.getenv(path)
78+
return val if val is not None else default
79+
root, *subkeys = path.split(".")
80+
data = get_config_json(root, default=None)
81+
if not isinstance(data, dict):
82+
return default
83+
cur: Any = data
84+
for key in subkeys:
85+
if isinstance(cur, dict) and key in cur:
86+
cur = cur[key]
87+
else:
88+
return default
89+
return cur
90+
91+
92+
class NacosConfigManager:
93+
_client = None
94+
_data_id = None
95+
_group = None
96+
_enabled = False
97+
98+
@classmethod
99+
def _sign(cls, secret_key: str, data: str) -> str:
100+
"""HMAC-SHA1 sgin"""
101+
signature = hmac.new(secret_key.encode("utf-8"), data.encode("utf-8"), hashlib.sha1)
102+
return base64.b64encode(signature.digest()).decode()
103+
104+
@staticmethod
105+
def parse_properties(content: str) -> dict[str, Any]:
106+
"""parse properties to dict"""
107+
data: dict[str, Any] = {}
108+
for line in content.splitlines():
109+
line = line.strip()
110+
if not line or line.startswith("#"):
111+
continue
112+
match = re.match(r"^([^=]+)=(.*)$", line)
113+
if match:
114+
key = match.group(1).strip()
115+
value = match.group(2).strip()
116+
val_lower = value.lower()
117+
if val_lower in ("true", "false"):
118+
value_parsed: Any = val_lower == "true"
119+
elif re.match(r"^[+-]?\d+$", value):
120+
try:
121+
value_parsed = int(value)
122+
except Exception:
123+
value_parsed = value
124+
else:
125+
value_parsed = value
126+
data[key] = value_parsed
127+
return data
128+
129+
@classmethod
130+
def start_config_watch(cls):
131+
while True:
132+
cls.init()
133+
time.sleep(60)
134+
135+
@classmethod
136+
def start_watch_if_enabled(cls) -> None:
137+
enable = os.getenv("NACOS_ENABLE_WATCH", "false").lower() == "true"
138+
print("enable:", enable)
139+
if not enable:
140+
return
141+
interval = int(os.getenv("NACOS_WATCH_INTERVAL", "60"))
142+
import threading
143+
144+
def _loop() -> None:
145+
while True:
146+
try:
147+
cls.init()
148+
except Exception as e:
149+
logger.error(f"❌ Nacos watch loop error: {e}")
150+
time.sleep(interval)
151+
152+
threading.Thread(target=_loop, daemon=True).start()
153+
logger.info(f"Nacos watch thread started (interval={interval}s).")
154+
155+
@classmethod
156+
def init(cls) -> None:
157+
server_addr = os.getenv("NACOS_SERVER_ADDR")
158+
data_id = os.getenv("NACOS_DATA_ID")
159+
group = os.getenv("NACOS_GROUP", "DEFAULT_GROUP")
160+
namespace = os.getenv("NACOS_NAMESPACE", "")
161+
ak = os.getenv("AK")
162+
sk = os.getenv("SK")
163+
164+
if not (server_addr and data_id and ak and sk):
165+
logger.warning("❌ missing NACOS_SERVER_ADDR / AK / SK / DATA_ID")
166+
return
167+
168+
base_url = f"http://{server_addr}/nacos/v1/cs/configs"
169+
170+
def _auth_headers():
171+
ts = str(int(time.time() * 1000))
172+
173+
sign_data = namespace + "+" + group + "+" + ts if namespace else group + "+" + ts
174+
signature = cls._sign(sk, sign_data)
175+
return {
176+
"Spas-AccessKey": ak,
177+
"Spas-Signature": signature,
178+
"timeStamp": ts,
179+
}
180+
181+
try:
182+
params = {
183+
"dataId": data_id,
184+
"group": group,
185+
"tenant": namespace,
186+
}
187+
188+
headers = _auth_headers()
189+
resp = requests.get(base_url, headers=headers, params=params, timeout=10)
190+
191+
if resp.status_code != 200:
192+
logger.error(f"Nacos AK/SK fail: {resp.status_code} {resp.text}")
193+
return
194+
195+
content = resp.text.strip()
196+
if not content:
197+
logger.warning("⚠️ Nacos is empty")
198+
return
199+
try:
200+
data_props = cls.parse_properties(content)
201+
logger.info("nacos config:", data_props)
202+
_update_env_from_dict(data_props)
203+
logger.info("✅ parse Nacos setting is Properties ")
204+
except Exception as e:
205+
logger.error(f"⚠️ Nacos parse fail(not JSON/YAML/Properties): {e}")
206+
return
207+
208+
except Exception as e:
209+
logger.error(f"❌ Nacos AK/SK init fail: {e}")
210+
211+
212+
# init Nacos
213+
NacosConfigManager.init()
214+
NacosConfigManager.start_watch_if_enabled()
215+
16216

17217
class APIConfig:
18218
"""Centralized configuration management for MemOS APIs."""

src/memos/graph_dbs/polardb.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,6 @@ def get_nodes(
835835
# Parse embedding from JSONB if it exists
836836
if embedding_json is not None:
837837
try:
838-
print("embedding_json:", embedding_json)
839838
# remove embedding
840839
"""
841840
embedding = json.loads(embedding_json) if isinstance(embedding_json, str) else embedding_json

0 commit comments

Comments
 (0)