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
- A PUT endpoint that will create a new resource, then return the dump of that resource.
- An ORM object that has a filePath property. This must be stored as a relative path.
- A Marshmallow schema. It has a @pre_dump method to resolve the file path by the use of another property (
parent.rootDir
)
So basically the process is
- Create the new resource
- Create a schema dump of that resource to use
- Commit
- 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.
Advertisement
Answer
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)