向一对多关联表POST数据触发marshmallow ValidationError:未知字段
问题描述
使用flask-api、marshmallow、flask-sqlalchemy开发医院API,其中Subject为父表,RawData为子表,构成一对多关联关系。完成模型、Schema和资源实现后,通过Postman向/api/upload/subject端点提交以下JSON数据:
{ "first_name": "Johny", "last_name": "Silverhand", "raw_tomograms": [ { "rawdata_id": 1, "raw_name": "subject_1", "path": "/srv/John_s_rawdata.zip", "subject_id": 1 } ] }
触发错误:
marshmallow.exceptions.ValidationError: {'raw_tomograms': {0: {'rawdata_id': ['Unknown field.'], 'raw_name': ['Unknown field.'], 'path': ['Unknown field.']}}}
相关代码如下:
模型
./api/models/subject.py
from flask_sqlalchemy import SQLAlchemy from datetime import datetime from api.database import db class Subject(db.Model): __tablename__ = "subject" id = db.Column(db.Integer, primary_key=True) first_name = db.Column(db.String(50), nullable=True) last_name = db.Column(db.String(50), nullable=True) raw_tomograms = db.relationship("RawData", backref="subject", lazy=True) create_date = db.Column(db.DateTime(timezone=True), default=datetime.now()) update_date = db.Column(db.DateTime(timezone=True), onupdate=datetime.now()) def __repr__(self): return "<Subject {self.id} {self.first_name>"
./api/models/rawdata.py
from flask_sqlalchemy import SQLAlchemy from datetime import datetime from api.database import db class RawData(db.Model): __tablename__ = "raw_data" id = db.Column(db.Integer, primary_key=True) raw_name = db.Column(db.String(120), nullable=False) path = db.Column(db.String(250)) create_date = db.Column(db.DateTime(timezone=True), default=datetime.now) update_date = db.Column(db.DateTime(timezone=True), onupdate=datetime.now) subject_id = db.Column(db.Integer, db.ForeignKey("subject.id")) def __repr__(self): return f"<Raw_data {self.id}>"
Schema
./api/schemas/rawdata.py
from marshmallow import Schema, fields, post_load, EXCLUDE from api.models.rawdata import RawData from api.models.subject import Subject from api.database import db from api.database import ma class RawSchema(Schema): rawdata_id = fields.Integer(), raw_name = fields.String(), path = fields.String(), create_date = fields.DateTime(timezone=True) update_date = fields.DateTime(timezone=True) subject_id = fields.Integer() @post_load def make_rawdata(self, data, **kwargs): return RawData(**data)
./api/schemas/subjecta.py
from marshmallow import Schema, fields, post_load from epidetect_api.models.subject import Subject from epidetect_api.database import db #from epidetect_api.database import ma from epidetect_api.schemas.rawdata_schema import RawSchema class SubjectSchema(Schema): """ Subject Marshmallow Schema Marshmallow schema used for loading/dumping Subject """ #raw_tomograms = ma.Nested(RawSchema, many=True) class Meta: model = Subject #exclude = id = fields.Integer(load=True, partial=True) first_name = fields.String(allow_none=True) last_name = fields.String(allow_none=True) create_date = fields.DateTime(timezone=True) update_date = fields.DateTime(timezone=True) raw_tomograms = fields.Nested(RawSchema, many=True) @post_load def make_subject(self, data, **kwargs): return Subject(**data)
资源
./api/resources/subject_resource.py
import logging from flask import request, jsonify, render_template from flask_restful import Resource, abort from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.exc import NoResultFound import json from api.database import db from api.models.subject import Subject from api.schemas.subject_schema import SubjectSchema SUBJECTS_ENDPOINT = "/api/upload/subject" logger = logging.getLogger(__name__) class SubjectsResource(Resource): def post(self): """ SubjectsResource POST method. Adds a new Subject to the database. :return: Subject.id, 201 HTTP status code. """ subject = SubjectSchema().load(request.json) print(request.json, flush=True) try: db.session.add(subject) db.session.commit() except IntegrityError as e: logger.warning( f"Integrity Error, this subject is already in the database. Error: {e}" ) abort(500, message="Unexpected Error!") else: return subject.id, 201
完整错误日志:
Traceback (most recent call last): File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2091, in __call__ return self.wsgi_app(environ, start_response) File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2076, in wsgi_app response = self.handle_exception(e) File "/usr/local/lib/python3.7/site-packages/flask_restful/__init__.py", line 271, in error_router return original_handler(e) File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2073, in wsgi_app response = self.full_dispatch_request() File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1518, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/local/lib/python3.7/site-packages/flask_restful/__init__.py", line 271, in error_router return original_handler(e) File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1516, in full_dispatch_request rv = self.dispatch_request() File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1502, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "/usr/local/lib/python3.7/site-packages/flask_restful/__init__.py", line 467, in wrapper resp = resource(*args, **kwargs) File "/usr/local/lib/python3.7/site-packages/flask/views.py", line 84, in view return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs) File "/usr/local/lib/python3.7/site-packages/flask_restful/__init__.py", line 582, in dispatch_request resp = meth(*args, **kwargs) File "/srv/api/resources/subject_resource.py", line 48, in post subject = SubjectSchema().load(request.json) File "/usr/local/lib/python3.7/site-packages/marshmallow/schema.py", line 720, in load data, many=many, partial=partial, unknown=unknown, postprocess=True File "/usr/local/lib/python3.7/site-packages/marshmallow/schema.py", line 904, in _do_load raise exc marshmallow.exceptions.ValidationError: {'raw_tomograms': {0: {'rawdata_id': ['Unknown field.'], 'raw_name': ['Unknown field.'], 'path': ['Unknown field.']}}}
错误分析与修复方案
核心问题
- RawSchema字段语法错误:
rawdata_id、raw_name、path字段定义后多了逗号,导致这些字段被解析为元组而非Marshmallow字段对象,Schema无法识别。 - 字段与模型不匹配:请求中的
rawdata_id对应模型的id字段,Schema定义错误。 - 导入路径不匹配:SubjectSchema文件名为
subjecta.py,但资源文件导入的是subject_schema,且Schema内导入的包名epidetect_api与实际项目包api不符。 - Subject模型默认值错误:
create_date用了datetime.now(),会在类定义时就生成固定时间,应该用datetime.now(不带括号)让SQLAlchemy在插入时动态生成。
修复步骤
1. 修正RawSchema字段定义
去掉字段后的多余逗号,同步模型字段名:
# ./api/schemas/rawdata.py from marshmallow import Schema, fields, post_load from api.models.rawdata import RawData class RawSchema(Schema): id = fields.Integer() # 对应模型的id字段,替换rawdata_id raw_name = fields.String(required=True) # 模型中raw_name不可为空,添加required约束 path = fields.String() create_date = fields.DateTime(timezone=True, dump_only=True) update_date = fields.DateTime(timezone=True, dump_only=True) subject_id = fields.Integer(load_only=True) @post_load def make_rawdata(self, data, **kwargs): return RawData(**data)
2. 修正SubjectSchema的导入与定义
将Schema文件名改为subject_schema.py,修正导入路径:
# ./api/schemas/subject_schema.py from marshmallow import Schema, fields, post_load from api.models.subject import Subject from api.schemas.rawdata import RawSchema class SubjectSchema(Schema): id = fields.Integer(load_only=True, required=False) # 新增时无需传入id first_name = fields.String(allow_none=True) last_name = fields.String(allow_none=True) create_date = fields.DateTime(timezone=True, dump_only=True) # 数据库自动生成,仅用于返回 update_date = fields.DateTime(timezone=True, dump_only=True) raw_tomograms = fields.Nested(RawSchema, many=True) @post_load def make_subject(self, data, **kwargs): return Subject(**data)
3. 修正Subject模型的默认值
# ./api/models/subject.py # ... create_date = db.Column(db.DateTime(timezone=True), default=datetime.now) # 去掉括号 update_date = db.Column(db.DateTime(timezone=True), onupdate=datetime.now) # ...
4. 调整请求JSON数据
删除subject_id(关联关系会自动设置),将rawdata_id改为id:
{ "first_name": "Johny", "last_name": "Silverhand", "raw_tomograms": [ { "id": 1, "raw_name": "subject_1", "path": "/srv/John_s_rawdata.zip" } ] }
5. 确认资源文件的Schema导入路径
确保资源文件中导入的是正确的SubjectSchema:
# ./api/resources/subject_resource.py from api.schemas.subject_schema import SubjectSchema
内容的提问来源于stack exchange,提问作者PatrickHellman
相关产品推荐
相关产品推荐

