flaskflask-sqlalchemymarshmallow-sqlalchemy

SQLAlchemyAutoSchema Nested Deserialization


I have the following:

Models.py

class Policy(db.Model):
    policy_id = db.Column(db.Integer, primary_key=True)
    client_reference = db.Column(db.String(50), nullable=False)
    submission_id = db.Column(db.ForeignKey('submission.submission_id'), nullable=False)

    submission = relationship("Submission", back_populates="policies", lazy='joined')


class Submission(db.Model):
    submission_id = db.Column(db.Integer, primary_key=True)
    client_reference = db.Column(db.String(50), nullable=False)

    policies = db.relationship('Policy', back_populates='submission', lazy='joined')

Schema.py

class PolicySchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Policy
        include_relationships = True
        load_instance = True
        unknown = INCLUDE
        exclude = ("submission",)

    submission_id = auto_field("submission")


class SubmissionSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Submission
        include_relationships = False
        load_instance = True

    policies = fields.Nested(PolicySchema, many=True, allow_none=True)

routes.py

@submission_api.route('/submission/', methods=['POST'])
def add_new():
    body = request.get_json()
    validated_request = SubmissionSchema().load(body, session=db.session)
    db.session.add(validated_request)
    db.session.commit()
    return jsonify({"submission_id": validated_request.submission_id}), 200

If I try to add a new record into Submission using:

{
    "client_reference": "POL1",
    "description": "hello"
}

It works correctly.

If I call with:

{
    "client_reference": "POL1","description": "hello",
    "policies": [{"client_reference": "A1"}]
}

I receieve this error:

File "/usr/local/lib/python3.8/site-packages/marshmallow_sqlalchemy/schema/load_instance_mixin.py", line 89, in load
    raise ValueError("Deserialization requires a session")
ValueError: Deserialization requires a session

I can't figure out why. I pass the session in the same way for both (its the same function). I've made a change to cause this because it was working (reminder to commit code more often).


Solution

  • I am afraid there is no real solution only, just a workaround discussed in this github issue. To quote TMiguelIT:

    Would it not be reasonable for us to create our own Nested field that inherits from the original Marshmallow Nested, allowing us to pass the session object down? Would this diminish usability in any way?

    As I understand it, the solution should look something like this:

    from marshmallow import fields
    
    class Nested(fields.Nested):
        """Nested field that inherits the session from its parent."""
    
        def _deserialize(self, *args, **kwargs):
            if hasattr(self.schema, "session"):
                self.schema.session = db.session  # overwrite session here
                self.schema.transient = self.root.transient
            return super()._deserialize(*args, **kwargs)
    

    So you create your own Nested field (I just copied the code from marshmallow-sqlalchemy) at the top of schemy.py and overwrite the session.