jfinkels / flask-restless

NO LONGER MAINTAINED - A Flask extension for creating simple ReSTful JSON APIs from SQLAlchemy models.
https://flask-restless.readthedocs.io
GNU Affero General Public License v3.0
1.02k stars 301 forks source link

PATCHing a model with Enum fields fails on serialization #679

Open kiptoomm opened 6 years ago

kiptoomm commented 6 years ago

setup

An SQLalchemy model that has

  1. An Enum field
  2. A DateTime field to track updated_at timestamp on the model
  3. A marshmallow-based custom de/serialization (and the corresponding marshmallow_enum) to correctly deserialize enums

problem

PATCH requests to update enum fields fail because the resources.py:patch() relies on directly setting the attributes of the sqlalchemy model instance to whatever values are provided by the request data dictionary (which are usually string/unicode representations of model field values since the 'data' object comes in as JSON). Normally, upon session.commit(), SQLAlchemy would convert the literal values to the proper representations of the model fields (in this case, to something like <EnumClass.TYPE_A: 0> instead of to 'TYPE_A', its literal representation). However, the patch method has to flush() the changes first in order to allow post-processors to make any changes. Unfortunately, the serializer is called before the sqlalchemy commit(), and the custom serializer fails because it expected an actual Enum but instead received a string representation of the field. Stacktrace of the failing unit test:

self = <fields.EnumField(default=<marshmallow.missing>, attribute=None, validate=None...'type': u'Invalid input type.', 'must_be_string': 'Enum name must be string'})> value = 'HOME', attr = 'address_type', obj = <tests.test_updating.Address object at 0x1099f9c90> AttributeError: 'unicode' object has no attribute 'name' venv/lib/python2.7/site-packages/marshmallow_enum/__init__.py:74: AttributeError

A more detailed background on the problem is discussed on this issue https://github.com/justanr/marshmallow_enum/issues/19

and demonstrated by this sample project: https://github.com/kiptoomm/flask_and_restless

attempted fix

Updated to first deserialize the partial document (request data passed to patch()) in order to force the conversion of the input enum value into its Enum representation so that setattr() in resources.py:_update_instance() sets the correct representation. This approach seems to solve the issue, but it causes most tests in test_updating.py to fail because the DefaultDeserializer's allow_client_generated_ids flag isn't set, and I cannot find a way to set it to True to avoid the ClientGeneratedIDNotAllowed

Please see commit af963701ee020f71ab2fe51331a35aac1c7e2d85 in the debug_issue_679 branch of my fork: https://github.com:kiptoomm/flask-restless

Any ideas on how to fix the exception, or advice on a cleaner approach?