调用 /api/metric/update 接口时遇到两个错误:
{
"code": {
"error": "'Node' object does not support item assignment"
}
}
{
"code": {
"error": "{code: Neo.ClientError.Statement.TypeError} {message: Property values can only be of primitive types or arrays thereof. Encountered: Map{metaData -> NO_VALUE, id -> Long(300), type -> String(\"metric\")}.}"
}
}
在 app/core/data_metric/metric_interface.py 的 data_metric_edit 函数中存在多个问题:
# 错误的代码(第678行)
for key, value in data.items():
if value is not None and key != "model_selected":
node_a[key] = value # ❌ Node对象不支持这种赋值方式
Neo4j 只支持以下属性类型:
string, integer, float, booleanstring[], integer[], 等不支持:
但代码没有过滤复杂类型,导致尝试存储 {metaData: ..., id: 300, type: "metric"} 这样的 Map 对象。
session.push(node_a) - 已废弃connect_graph.create(connection) - 旧API调用方式connect_graph.merge(relationship_label) - 旧API调用方式实现 is_valid_neo4j_property 函数,只保留 Neo4j 支持的基本类型:
def is_valid_neo4j_property(value):
"""检查值是否为 Neo4j 支持的属性类型"""
if value is None:
return False
# 基本类型:str, int, float, bool
if isinstance(value, (str, int, float, bool)):
return True
# 列表类型:但列表中的元素必须是基本类型
if isinstance(value, list):
if not value: # 空列表是允许的
return True
# 检查列表中所有元素是否为基本类型
return all(isinstance(item, (str, int, float, bool)) for item in value)
# 其他类型(dict, object等)不支持
return False
对于复杂类型(如字典、包含对象的列表),转换为 JSON 字符串存储:
# 准备更新属性,只保留有效类型
update_props = {}
for key, value in data.items():
if key in excluded_keys:
continue
if not is_valid_neo4j_property(value):
# 如果是复杂类型,尝试转换为 JSON 字符串
if isinstance(value, (dict, list)):
try:
import json
update_props[key] = json.dumps(value, ensure_ascii=False)
logger.info(f"属性 {key} 从复杂类型转换为JSON字符串")
except Exception as e:
logger.warning(f"跳过无法序列化的属性 {key}: {type(value)}")
else:
logger.warning(f"跳过不支持的属性类型 {key}: {type(value)}")
else:
update_props[key] = value
# 使用 Cypher 更新节点属性
if update_props:
set_clauses = []
for key in update_props.keys():
set_clauses.append(f"n.{key} = ${key}")
set_clause = ", ".join(set_clauses)
update_query = f"""
MATCH (n:DataMetric)
WHERE id(n) = $metric_id
SET {set_clause}
RETURN n
"""
session.run(update_query, metric_id=metric_id, **update_props)
logger.info(f"成功更新数据指标节点属性: ID={metric_id}, 更新字段: {list(update_props.keys())}")
替换旧的 API 调用,使用 Cypher MERGE 子句:
# 创建子节点关系
child_query = """
MATCH (parent:DataMetric), (child)
WHERE id(parent) = $parent_id AND id(child) = $child_id
MERGE (parent)-[:child]->(child)
"""
session.run(child_query, parent_id=metric_id, child_id=child_id_int)
# 创建标签关系
tag_query = """
MATCH (metric:DataMetric), (tag:DataLabel)
WHERE id(metric) = $metric_id AND id(tag) = $tag_id
MERGE (metric)-[:LABEL]->(tag)
"""
session.run(tag_query, metric_id=metric_id, tag_id=tag_id_int)
# 创建连接关系
connection_query = """
MATCH (metric:DataMetric), (meta)
WHERE id(metric) = $metric_id AND id(meta) = $meta_id
MERGE (metric)-[:connection]->(meta)
"""
session.run(connection_query, metric_id=metric_id, meta_id=meta_id_int)
# 验证必需参数
metric_id = data.get("id")
if not metric_id:
logger.error("数据指标ID不能为空")
raise ValueError("数据指标ID不能为空")
# 验证节点存在性
node_a = get_node_by_id('DataMetric', metric_id)
if not node_a:
logger.error(f"数据指标节点不存在: ID={metric_id}")
raise ValueError(f"数据指标节点不存在: ID={metric_id}")
# ID类型转换和验证
try:
child_id_int = int(child_id)
# ... 使用 child_id_int
except (ValueError, TypeError) as e:
logger.warning(f"无效的子节点ID: {child_id}, 错误: {str(e)}")
continue
logger.info(f"属性 {key} 从复杂类型转换为JSON字符串")
logger.info(f"成功更新数据指标节点属性: ID={metric_id}, 更新字段: {list(update_props.keys())}")
logger.info(f"成功创建child关系: {metric_id} -> {child_id_int}")
logger.info(f"成功创建LABEL关系: {metric_id} -> {tag_id_int}")
logger.info(f"成功创建connection关系: {metric_id} -> {meta_id_int}")
logger.info(f"数据指标编辑完成: ID={metric_id}")
logger.warning(f"跳过无法序列化的属性 {key}: {type(value)}")
logger.warning(f"跳过不支持的属性类型 {key}: {type(value)}")
def data_metric_edit(data):
node_a = get_node_by_id('DataMetric', data["id"])
if node_a:
delete_relationships(data["id"])
# ❌ 错误1:直接给Node对象赋值
for key, value in data.items():
if value is not None and key != "model_selected":
node_a[key] = value # Node对象不支持字典赋值
# ❌ 错误2:没有过滤复杂类型
# 可能尝试存储 {id: 300, type: "metric"} 这样的Map对象
# ❌ 错误3:使用过时的API
with connect_graph().session() as session:
session.push(node_a) # 已废弃
# ❌ 错误4:使用过时的关系创建方式
connection = Relationship(node_a, 'child', child)
connect_graph.create(connection) # 旧API
def data_metric_edit(data):
metric_id = data.get("id")
if not metric_id:
raise ValueError("数据指标ID不能为空")
node_a = get_node_by_id('DataMetric', metric_id)
if not node_a:
raise ValueError(f"数据指标节点不存在: ID={metric_id}")
delete_relationships(metric_id)
# ✅ 正确:定义属性类型验证函数
def is_valid_neo4j_property(value):
if value is None:
return False
if isinstance(value, (str, int, float, bool)):
return True
if isinstance(value, list):
return all(isinstance(item, (str, int, float, bool)) for item in value)
return False
# ✅ 正确:过滤和转换属性
excluded_keys = {'id', 'model_selected', 'childrenId', 'tag'}
update_props = {}
for key, value in data.items():
if key in excluded_keys:
continue
if not is_valid_neo4j_property(value):
# 复杂类型转为JSON字符串
if isinstance(value, (dict, list)):
update_props[key] = json.dumps(value, ensure_ascii=False)
else:
update_props[key] = value
driver = connect_graph()
with driver.session() as session:
# ✅ 正确:使用Cypher SET更新属性
if update_props:
set_clauses = [f"n.{key} = ${key}" for key in update_props.keys()]
set_clause = ", ".join(set_clauses)
update_query = f"""
MATCH (n:DataMetric)
WHERE id(n) = $metric_id
SET {set_clause}
RETURN n
"""
session.run(update_query, metric_id=metric_id, **update_props)
# ✅ 正确:使用Cypher MERGE创建关系
child_query = """
MATCH (parent:DataMetric), (child)
WHERE id(parent) = $parent_id AND id(child) = $child_id
MERGE (parent)-[:child]->(child)
"""
session.run(child_query, parent_id=metric_id, child_id=child_id_int)
| 方面 | 修复前 | 修复后 |
|---|---|---|
| API 兼容性 | ❌ 使用过时API | ✅ 使用标准Cypher |
| Node 对象处理 | ❌ 字典式赋值(不支持) | ✅ Cypher SET 语句 |
| 属性类型验证 | ❌ 无验证,直接存储 | ✅ 严格类型验证和过滤 |
| 复杂类型处理 | ❌ 尝试直接存储Map对象 | ✅ 转换为JSON字符串 |
| 错误处理 | ⚠️ 基础异常捕获 | ✅ 详细验证和日志 |
| 参数验证 | ❌ 缺少验证 | ✅ 完整类型验证 |
| 日志记录 | ⚠️ 基础日志 | ✅ 详细操作和警告日志 |
| 代码可维护性 | ⚠️ 一般 | ✅ 良好 |
POST /api/metric/update
Content-Type: application/json
{
"id": 12345,
"name_zh": "更新后的指标名称",
"name_en": "updated_metric_name",
"description": "这是更新后的描述",
"category": "财务指标",
"childrenId": [123, 456],
"tag": 789,
"model_selected": [
{
"meta": [
{"id": 111},
{"id": 222}
]
}
]
}
{
"code": 200,
"data": {},
"msg": "success"
}
{
"code": 500,
"data": {},
"msg": {
"error": "数据指标ID不能为空"
}
}
运行测试脚本验证修复效果:
python test_metric_update.py
测试将会:
app/core/data_metric/metric_interface.py - data_metric_edit 函数test_metric_update.py - 更新功能测试脚本METRIC_UPDATE_FIX.md - 本文档id 字段是必需的,不能为空通过将直接对 Node 对象的属性赋值改为使用 Cypher 查询更新,以及使用 Cypher MERGE 语句创建关系,成功修复了 'Node' object does not support item assignment 错误。新的实现使用标准的 Neo4j Python Driver API,具有更好的兼容性、可维护性和错误处理能力。