pythonsqlalchemymarshmallow

Elegant Way to Deal with Marshmallow Altering a SQLAlchemy Object


I have found myself in a situation that does not seem to have an elegant solution. Consider the following (pseudo-ish) REST API code


bp = Blueprint('Something', __name__, url_prefix='/api')

class SomeOutputSchema(ma.SQLAlchemyAutoSchema)
    class Meta:
        model = MyModel

    @pre_dump(pass_many=False)
    def resolveFilePath(self, ormObject, many):
        # ormObject has a parent via a relationship
        ormObject.filePath = os.path.join(ormObject.parent.rootDir, ormObject.filePath)


@bp.route("/someRoute")
class SomeClass(MethodView):

    def put(self):
        ormObject = MyModel(filePath = "/some/relative/path")
        db.session.add(ormObject)
        db.session.flush()

        outputDump = SomeOutputSchema().dump(ormObject)

        # Lots of other code that uses outputDump...

        # Only commit here in case 
        # anything goes wrong above

        db.session.commit()
        
        return jsonify({"data": outputDump}), 201

I have

So basically the process is

  1. Create the new resource
  2. Create a schema dump of that resource to use
  3. Commit
  4. Return the schema dump

So finally, the problem is: outputDump's @pre_dump actually alters ormObject, so that it is now a fully resolved path by the time db.session.commit() is called. My first instinct here was to create a deep copy of ormObject but that fails with

"Parent instance <MyModel at 0x7f31cdd44240> is not bound to a Session; lazy load operation of attribute 'parent' cannot proceed (Background on this error at: http://sqlalche.me/e/14/bhk3)"

It's not that this is a difficult thing to solve, but it seems to be difficult to solve elegantly with my current knowledge. I need the path to be relative for the database, and resolved otherwise. My current solution is to tell the SomeOutputSchema to skip the @pre_dump in this case, then take the outputDump and then resolve the file paths just after the schema dump. But this feels really gross to me.

I would love to hear any ideas on this, as currently my code feels messy and I don't like the idea of just leaving it and pushing on.


Solution

  • Solved by using a @post_dump and using pass_original=True to get access to the original object

    class SomeOutputSchema(ma.SQLAlchemyAutoSchema)
        class Meta:
            model = MyModel
    
        @post_dump(pass_original=True)
        def resolveFilePath(self, data, ormObject, many):
            data['filePath'] = os.path.join(ormObject.parent.rootDir, ormObject.filePath)