SolarArbiter / solarforecastarbiter-api

HTTP API and database schema for the Solar Forecast Arbiter
https://api.solarforecastarbiter.org
MIT License
10 stars 6 forks source link

Backwards-incompatible change in flask-marshmallow 0.15.0 #331

Open dplarson opened 1 year ago

dplarson commented 1 year ago

The Python dependency flask-marshmallow released v0.15.0 on April 5, 2023, which includes a backwards-incompatible change from v0.14.0 (released September 27, 2020) that affects the sfa-api code: https://github.com/marshmallow-code/flask-marshmallow/blob/dev/CHANGELOG.rst

Backwards-incompatible: URLFor and AbsoluteURLFor now do not accept parameters for flask.url_for as top-level parameters. They must always be passed in the values dictionary, as explained in the v0.14.0 changelog.

Since our requirements.txt file didn't previously pin the version of flask-marshmallow, this lead to a (silent) upgrade which broke the sfa-api. The quick fix is to pin the version to v0.14.0, but we should also consider making the relevant changes in sfa-api to move to flask-marshmallow v0.15.0.

How the error showed up

If flask-marshmallow v0.15.0 is used, then GET /sites/ works fine but anything else that relates to Sites (e.g., GET /observations) will fail with this error in the logs:

[13/Apr/2023:22:42:54 +0000] - 200 "GET / HTTP/1.1" 548 "-" "-"  0.002208
[2023-04-13 22:43:13,250] ERROR in app: Exception on /observations/ [GET]
Traceback (most recent call last):
  File "/opt/app-root/lib/python3.9/site-packages/flask/app.py", line 2528, in wsgi_app
    response = self.full_dispatch_request()
  File "/opt/app-root/lib/python3.9/site-packages/flask/app.py", line 1825, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/opt/app-root/lib/python3.9/site-packages/flask/app.py", line 1823, in full_dispatch_request
    rv = self.dispatch_request()
  File "/opt/app-root/lib/python3.9/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/opt/app-root/lib/python3.9/site-packages/flask/views.py", line 107, in view
    return current_app.ensure_sync(self.dispatch_request)(**kwargs)
  File "/opt/app-root/lib/python3.9/site-packages/flask/views.py", line 188, in dispatch_request
    return current_app.ensure_sync(meth)(**kwargs)
  File "/opt/app-root/src/sfa_api/observations.py", line 52, in get
    return jsonify(ObservationSchema(many=True).dump(observations))
  File "/opt/app-root/lib/python3.9/site-packages/marshmallow/schema.py", line 557, in dump
    result = self._serialize(processed_obj, many=many)
  File "/opt/app-root/lib/python3.9/site-packages/marshmallow/schema.py", line 519, in _serialize
    return [
  File "/opt/app-root/lib/python3.9/site-packages/marshmallow/schema.py", line 520, in <listcomp>
    self._serialize(d, many=False)
  File "/opt/app-root/lib/python3.9/site-packages/marshmallow/schema.py", line 525, in _serialize
    value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
  File "/opt/app-root/lib/python3.9/site-packages/marshmallow/fields.py", line 344, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/opt/app-root/lib/python3.9/site-packages/flask_marshmallow/fields.py", line 180, in _serialize
    return _rapply(self.schema, _url_val, key=attr, obj=obj)
  File "/opt/app-root/lib/python3.9/site-packages/flask_marshmallow/fields.py", line 133, in _rapply
    return {key: _rapply(value, func, *args, **kwargs) for key, value in d.items()}
  File "/opt/app-root/lib/python3.9/site-packages/flask_marshmallow/fields.py", line 133, in <dictcomp>
    return {key: _rapply(value, func, *args, **kwargs) for key, value in d.items()}
  File "/opt/app-root/lib/python3.9/site-packages/flask_marshmallow/fields.py", line 135, in _rapply
    return func(d, *args, **kwargs)
  File "/opt/app-root/lib/python3.9/site-packages/flask_marshmallow/fields.py", line 143, in _url_val
    return val.serialize(key, obj, **kwargs)
  File "/opt/app-root/lib/python3.9/site-packages/marshmallow/fields.py", line 344, in serialize
    return self._serialize(value, attr, obj, **kwargs)
  File "/opt/app-root/lib/python3.9/site-packages/flask_marshmallow/fields.py", line 108, in _serialize
    return url_for(self.endpoint, **param_values)
  File "/opt/app-root/lib/python3.9/site-packages/flask/helpers.py", line 256, in url_for
    return current_app.url_for(
  File "/opt/app-root/lib/python3.9/site-packages/flask/app.py", line 2034, in url_for
    return self.handle_url_build_error(error, endpoint, values)
  File "/opt/app-root/lib/python3.9/site-packages/flask/app.py", line 2023, in url_for
    rv = url_adapter.build(  # type: ignore[union-attr]
  File "/opt/app-root/lib/python3.9/site-packages/werkzeug/routing/map.py", line 917, in build
    raise BuildError(endpoint, values, method, self)
werkzeug.routing.exceptions.BuildError: Could not build url for endpoint 'sites.single'. Did you forget to specify values ['site_id']?

where the key lines are about the current_app.url_for() call and the werkzeug.routing.exceptions.BuildError: Could not build url for endpoint 'sites.single'. Did you forget to specify values ['site_id']? message.