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

custom serializer doesn't work on collections #657

Closed tumluliu closed 7 years ago

tumluliu commented 7 years ago

I found that the custom serializer doesn't work on collections API endpoint, while it works well on single item endpoints. I am using 0.17 of flask_restless.

In my case, when access https://luliu.me/tracks/api/v1/trackinfo/2, I can get

{
  "2D length": 172031.0, 
  "3D length": 172182.0, 
  "Downhill": 1862.1, 
  "End lat": 49.5396, 
  "End lon": 8.64872, 
  "Ended at": "2006-09-17 01:56:55", 
  "GPX ID": 110, 
  "ID": 2, 
  "Max speed": 0.0, 
  "Moving time": "0:00:03", 
  "Points": 4187, 
  "Segments": 1, 
  "Start lat": 49.5394, 
  "Start lon": 8.64866, 
  "Started at": "2006-09-17 01:56:52", 
  "Stopped time": "None", 
  "Uphill": 1862.1
}

as expected, which means the serializer works. But when I access https://luliu.me/tracks/api/v1/trackinfo, I got such errors:

Traceback (most recent call last):
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask_restless/views.py", line 157, in decorator
    return func(*args, **kw)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/mimerender.py", line 265, in wrapper
    content = renderer(**result)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask_restless/views.py", line 303, in jsonpify
    response = jsonify(*args, **kw)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask_restless/views.py", line 219, in jsonify
    response = _jsonify(*args, **kw)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/json.py", line 263, in jsonify
    (dumps(data, indent=indent, separators=separators), '\n'),
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/json.py", line 123, in dumps
    rv = _json.dumps(obj, **kwargs)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/json/__init__.py", line 250, in dumps
    sort_keys=sort_keys, **kw).encode(obj)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/json/encoder.py", line 209, in encode
    chunks = list(chunks)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/json/encoder.py", line 434, in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/json/encoder.py", line 408, in _iterencode_dict
    for chunk in chunks:
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/json/encoder.py", line 332, in _iterencode_list
    for chunk in chunks:
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/json/encoder.py", line 408, in _iterencode_dict
    for chunk in chunks:
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/json/encoder.py", line 442, in _iterencode
    o = _default(o)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/site-packages/flask/json.py", line 80, in default
    return _json.JSONEncoder.default(self, o)
  File "/usr/local/var/pyenv/versions/2.7.10/lib/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: datetime.timedelta(0, 3) is not JSON serializable

That means that there is a column of INTERVAL(postgresql) type can not be serialized with default serializer. That's a very ridiculous error because I provide the custom serializer when creating the API:

app = Flask(__name__)
cors = CORS(app)
manager = flask_restless.APIManager(app, session=Session)

manager.create_api(
   TrackInfo,
   methods=['GET'],
   results_per_page=10,
   max_results_per_page=50,
   serializer=trackinfo_serializer)

where the trackinfo_serializer is defined as:

def trackinfo_serializer(instance):
    ti_dict = {}
    ti_dict['ID']           = instance.ogc_fid
    ti_dict['GPX ID']       = instance.gpx_id
    ti_dict['Segments']     = instance.segments
    ti_dict['2D length']    = instance.length_2d
    ti_dict['3D length']    = instance.length_3d
    ti_dict['Moving time']  = str(instance.moving_time)
    ti_dict['Stopped time'] = str(instance.stopped_time)
    ti_dict['Max speed']    = instance.max_speed
    ti_dict['Uphill']       = instance.uphill
    ti_dict['Downhill']     = instance.downhill
    ti_dict['Started at']   = str(instance.started)
    ti_dict['Ended at']     = str(instance.ended)
    ti_dict['Points']       = instance.points
    ti_dict['Start lon']    = instance.start_lon
    ti_dict['Start lat']    = instance.start_lat
    ti_dict['End lon']      = instance.end_lon
    ti_dict['End lat']      = instance.end_lat
    logger.debug("Serialized trackinfo: %s", ti_dict)
    return ti_dict

The full source codes are quite simple and available in the https://github.com/tumluliu/tracks-rest-api repo. I am wondering if there is anything changed w.r.t the custom serialization in the latest version of flask_restless. I noticed that there is a new serializer_class introduced, but I don't think the serialize_many function is for this case. Thanks a lot!

tumluliu commented 7 years ago

well, I found the reason. the self.serialize was not even called in the to_dict function in helpers.py. I made a monkey patch in my forked version here: tumluliu/flask-restless@ 47693f6f67d638d8dac1d7350bba215482584072 .

not intended to commit a PR because the custom serialization strategy was totally changed in the latest version (1.x.x)