You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

向一对多关联表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.']}}}

错误分析与修复方案

核心问题

  1. RawSchema字段语法错误:rawdata_id、raw_name、path字段定义后多了逗号,导致这些字段被解析为元组而非Marshmallow字段对象,Schema无法识别。
  2. 字段与模型不匹配:请求中的rawdata_id对应模型的id字段,Schema定义错误。
  3. 导入路径不匹配:SubjectSchema文件名为subjecta.py,但资源文件导入的是subject_schema,且Schema内导入的包名epidetect_api与实际项目包api不符。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 17:50:35