Flask marshmallow 在转储模型对象时无限递归

问题描述 投票:0回答:1

因此,我使用 Flask-Marshmallows 转储数据库模型并将它们转换为 Python 对象以适合我的 Web API。

但是,由于某种原因,程序总是会出现以下错误:

Traceback (most recent call at last):
...
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 624, in _serialize
    return schema.dump(nested_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 552, in dump
    result = self._serialize(processed_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 520, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 338, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 624, in _serialize
    return schema.dump(nested_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 552, in dump
    result = self._serialize(processed_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 520, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 338, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 624, in _serialize
    return schema.dump(nested_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 552, in dump
    result = self._serialize(processed_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 520, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 338, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 757, in _serialize
    return [self.inner._serialize(each, attr, obj, **kwargs) for each in value]
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 757, in <listcomp>
    return [self.inner._serialize(each, attr, obj, **kwargs) for each in value]
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 624, in _serialize
    return schema.dump(nested_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 552, in dump
    result = self._serialize(processed_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 520, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 338, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 624, in _serialize
    return schema.dump(nested_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 552, in dump
    result = self._serialize(processed_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 520, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 338, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 624, in _serialize
    return schema.dump(nested_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 552, in dump
    result = self._serialize(processed_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 520, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 338, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 757, in _serialize
    return [self.inner._serialize(each, attr, obj, **kwargs) for each in value]
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 757, in <listcomp>
    return [self.inner._serialize(each, attr, obj, **kwargs) for each in value]
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 624, in _serialize
    return schema.dump(nested_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 552, in dump
    result = self._serialize(processed_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 520, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 338, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 624, in _serialize
    return schema.dump(nested_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 552, in dump
    result = self._serialize(processed_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 520, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 338, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 624, in _serialize
    return schema.dump(nested_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 552, in dump
    result = self._serialize(processed_obj, many=many)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 520, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 330, in serialize
    value = self.get_value(obj, attr, accessor=accessor)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/fields.py", line 259, in get_value
    return accessor_func(obj, check_key, default)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/schema.py", line 477, in get_attribute
    return get_value(obj, attr, default)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/utils.py", line 239, in get_value
    return _get_value_for_key(obj, key, default)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/marshmallow/utils.py", line 253, in _get_value_for_key
    return getattr(obj, key, default)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/orm/attributes.py", line 481, in __get__
    return self.impl.get(state, dict_)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/orm/dynamic.py", line 101, in get
    return self.query_class(self, state)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/orm/dynamic.py", line 308, in __init__
    prop._with_parent(instance, alias_secondary=False),
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/orm/relationships.py", line 1674, in _with_parent
    return self._optimized_compare(
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/orm/relationships.py", line 1741, in _optimized_compare
    criterion = visitors.cloned_traverse(
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/sql/visitors.py", line 783, in cloned_traverse
    obj = clone(
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/sql/visitors.py", line 776, in clone
    newelem._copy_internals(clone=clone, **kw)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/sql/traversals.py", line 745, in _copy_internals
    result = meth(attrname, self, obj, **kw)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/sql/traversals.py", line 757, in visit_clauseelement
    return clone(element, **kw)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/sql/visitors.py", line 775, in clone
    cloned[id(elem)] = newelem = elem._clone(**kw)
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/sql/elements.py", line 1622, in _clone
    c.key = _anonymous_label.safe_construct(
  File "/Volumes/Data/Develop/Python/Orizont/venv/lib/python3.8/site-packages/sqlalchemy/sql/elements.py", line 5230, in safe_construct
    body = re.sub(r"[%\(\) \$]+", "_", body).strip("_")
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/re.py", line 210, in sub
    return _compile(pattern, flags).sub(repl, string, count)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/re.py", line 291, in _compile
    if isinstance(flags, RegexFlag):
RecursionError: maximum recursion depth exceeded while calling a Python object

我没有看到任何可能无限递归的字段...有什么想法吗?谢谢!

我的架构文件:

from flask_sqlalchemy import model
from .extensions import ma
from .models import User, Discussion, Tag, Comment, Reply

class UserSchema(ma.SQLAlchemySchema):
    class Meta:
        model = User
    
    id = ma.auto_field()
    username = ma.auto_field()
    email = ma.auto_field()
    bio = ma.auto_field()
    desc = ma.auto_field()
    avatar = ma.auto_field()
    discussions = ma.List(ma.Nested(lambda: DiscussionSchema(exclude=("author",))))
    comments = ma.List(ma.Nested(lambda: CommentSchema(exclude=("author",))))
    replies = ma.List(ma.Nested(lambda: ReplySchema(exclude=("author",))))

class DiscussionSchema(ma.SQLAlchemySchema):
    class Meta:
        model = Discussion
    
    id = ma.auto_field()
    title = ma.auto_field()
    content = ma.auto_field()
    author_id = ma.auto_field()
    author = ma.Nested(UserSchema, exclude=("discussions",))
    comments = ma.List(ma.Nested(lambda: CommentSchema(exclude=("discussion",))))
    replies = ma.List(ma.Nested(lambda: ReplySchema(exclude=("discussion",))))

class TagSchema(ma.SQLAlchemySchema):
    class Meta:
        model = Tag
    
    id = ma.auto_field()
    name = ma.auto_field()

class ReplySchema(ma.SQLAlchemySchema):
    class Meta:
        model = Reply
    
    id = ma.auto_field()
    content = ma.auto_field()
    author = ma.Nested(UserSchema, exclude=("replies",))
    author_id = ma.auto_field()
    discussion = ma.Nested(DiscussionSchema, exclude=("replies",))
    discussion_id = ma.auto_field()
    comments = ma.List(ma.Nested(lambda: CommentSchema(exclude=("reply",))))

class CommentSchema(ma.SQLAlchemySchema):
    class Meta:
        model = Comment
    
    id = ma.auto_field()
    content = ma.auto_field()
    to_discussion = ma.auto_field()
    author = ma.Nested(UserSchema, exclude=("comments",))
    author_id = ma.auto_field()
    discussion = ma.Nested(DiscussionSchema, exclude=("comments",))
    discussion_id = ma.auto_field()
    reply = ma.Nested(lambda: ReplySchema(exclude=("comments",)))
    reply_id = ma.auto_field()

还有我的模特:

from .extensions import db
from bcrypt import hashpw as hash_password, checkpw as check_password, gensalt as random_salt
from itsdangerous import (TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired)
from flask import current_app as app
from flask_login import UserMixin
from libgravatar import Gravatar

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String, unique=True, nullable=False)
    email = db.Column(db.String, unique=True, nullable=False)
    password_hash = db.Column(db.String, nullable=False)
    desc = db.Column(db.String)
    bio = db.Column(db.String)
    avatar = db.Column(db.String)
    discussions = db.relationship('Discussion', backref='author', lazy='dynamic')
    comments = db.relationship('Comment', backref='author', lazy='dynamic')
    replies = db.relationship('Reply', backref='author', lazy='dynamic')

    @property
    def password(self):
        return "Password not readable."
    
    @password.setter
    def password(self, value):
        self.password_hash = hash_password(value.encode('utf-8'), random_salt())
    
    def generate_avatar(self):
        gravatar = Gravatar(self.email)
        self.avatar = gravatar.get_image(default="retro")

    def verify_password(self, password):
        return check_password(password.encode("utf-8"), self.password_hash)
    
    def generate_auth_token(self, expiration=600):
        s = Serializer(app.config["SECRET_KEY"], expires_in=expiration)
        return s.dumps({"id": self.id})
    
    @staticmethod
    def verify_auth_token(token):
        s = Serializer(app.config["SECRET_KEY"])
        try:
            data = s.loads(token)
        except SignatureExpired:
            return False
        except BadSignature:
            return False
        user = User.query.get(data["id"])
        return user
    
    def is_active(self):
        return False

    def __repr__(self):
        return '<User %s>' % self.username

tags = db.Table("tags",
    db.Column("tag_id", db.Integer, db.ForeignKey("tag.id"), primary_key=True),
    db.Column("discussion_id", db.Integer, db.ForeignKey("discussion.id"), primary_key=True)
)

# discussion_comments = db.Table("discussion_comments",
#     db.Column("comment_id", db.Integer, db.ForeignKey("comment.id"), primary_key=True),
#     db.Column("discussion_id", db.Integer, db.ForeignKey("discussion.id"), primary_key=True)
# )

# replies = db.Table("replies",
#     db.Column("reply_id", db.Integer, db.ForeignKey("reply.id"), primary_key=True),
#     db.Column("discussion_id", db.Integer, db.ForeignKey("discussion.id"), primary_key=True)
# )

class Discussion(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String, nullable=False)
    content = db.Column(db.String)
    author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    tags = db.relationship("Tag", secondary=tags, lazy="dynamic", backref=db.backref("discussions", lazy=True))
    # comments = db.relationship("Comment", secondary=discussion_comments, lazy="subquery", backref=db.backref("discussions", lazy=True))
    # replies = db.relationship("Reply", secondary=replies, lazy="subquery", backref=db.backref("discussions", lazy=True))
    comments = db.relationship("Comment", backref="discussion", lazy="dynamic")
    replies = db.relationship("Reply", backref="discussion", lazy="dynamic")

    def __repr__(self) -> str:
        return '<Discussion %s>' % self.title


class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)


class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String)
    to_discussion = db.Column(db.Boolean, default=False)
    author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    discussion_id = db.Column(db.Integer, db.ForeignKey("discussion.id"))
    reply_id = db.Column(db.Integer, db.ForeignKey("reply.id"))

# reply_comments = db.Table("reply_comments",
#     db.Column("comment_id", db.Integer, db.ForeignKey("comment.id"), primary_key=True),
#     db.Column("reply_id", db.Integer, db.ForeignKey("reply.id"), primary_key=True)
# )

class Reply(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String)
    author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    discussion_id = db.Column(db.Integer, db.ForeignKey("discussion.id"))
    comments = db.relationship("Comment", lazy="dynamic", backref="reply")

谢谢!!

python database flask marshmallow flask-marshmallow
1个回答
1
投票

发生递归是因为,例如,如果您尝试转储用户信息,则每个用户将包含其所有相关讨论(因为这一行:

discussions = ma.List(ma.Nested(lambda: DiscussionSchema(exclude=("author",))))

然后每个讨论模式将再次转储讨论用户.. 评论和回复也会发生这种情况。

首先从用户模式中删除这三个嵌套模式列表,当您有关于用户的查询时,您不应该转储所有数据库信息。

同样,从其他模型中删除所有嵌套架构列表(例如,从 DiscussionSchema 中删除作者、评论和回复)。

© www.soinside.com 2019 - 2024. All rights reserved.