-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix bug that prevents returning Flask.Response
s.
#153
Conversation
1b0420b
to
52b08a7
Compare
Thanks for the PR twosigmajab! I think in this case, the functionality The confusion seems to be over setting The use case you supply in that test is technically not supported per the documentation. It supplies a dictionary to
@registry.handles( The documentation does state that dictionary values should be Schema objects. Setting one to The Hope that helps. |
I don’t think that’s accurate, @airstandley (though agreed the docs should be clearer about this, and need improvement in some other ways too (shout out to #153)).
Flask-Rebar's unit tests demonstrate using flask-rebar/tests/test_rebar.py Line 300 in 367bfa8
See also the following comment: #26 (comment) (whose changes were eventually incorporated by @barakalon as part of #28 to improve Flask-Rebar’s support for such use cases). Assuming this is just a momentary mixup and Flask-Rebar does deliberately support dicts with Also looks like @mjbryant tried to get similar changes to this landed in #16 a while ago, which is more evidence of demand for this. That was before the changes from #28 were merged. Now that #28 has landed, it seems like it’s time to land these changes too. |
@jab True, I wasn't entirely accurate. I was hoping to avoid the complications of intentions vs behaviour vs documentation since all of those are constantly changing by just focusing on what we currently promise in the documentation. Sorry, and thanks for all the details, there's stuff in there I was not aware of. @twosigmajab As an example:
Rather than just skipping marshaling/validation when a Response object is returned, why not look at the rv.status, and then validate the rv.data if a schema is defined for that status? |
Seems like we could potentially simplify the logic quite a bit if we first passed |
Thanks for taking another look, @airstandley! Hoping to get time to take another look at this later this week (but if at any point someone else is eager to submit a patch please don't hesitate). Sorry for the delay and will check back in asap! |
Hum that's weird because I have some code that return a 302 Response object without a tuple and it works :S from flask import redirect
@registry.handles(
rule="/oauth/callback",
method="GET",
query_string_schema=OAuthCallbackSchema(),
)
def callback(service: OAuthService):
...
return redirect(login_redirect_uri) |
@Sytten, you would have to add |
I see, then I think the test isn't really testing the new behaviour? |
What makes you think that? The test includes |
Ho right, but shouldn't we check for the status code of the response object before returning it? Otherwise we can basically specify anything in the |
cf2c567
to
2f55259
Compare
@airstandley, @RookieRick, I added the additional validation of the response body as requested, along with corresponding tests. Another look? Note that this requires an extra deserialization of the response body that the user has just serialized, since the pre-serialized object the user passed into |
as promised by https://flask-rebar.readthedocs.io/en/latest/quickstart/basics.html#marshaling > the return value must be a return value that Flask supports, e.g. a > string or a Flask.Response object
2f55259
to
b163ce4
Compare
Rather than deal with Python 3.5-only test failures, I submitted #221 to drop support for 3.5 (it's passed end-of-life) and rebased on top of that branch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@twosigmajab Thank you so much for finding the time to pick this back up!
Approving because at this point this is definitely a clear improvement over what we have, though there are some potential gotchas I see.
@@ -146,11 +146,19 @@ def wrapped(*args, **kwargs): | |||
if not response_body_schema: | |||
return rv | |||
|
|||
data, status_code, headers = _unpack_view_func_return_value(rv) | |||
if isinstance(rv, current_app.response_class): | |||
schema = response_body_schema[rv.status_code] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
schema = response_body_schema[rv.status_code] | |
schema = response_body_schema.get(rv.status_code, None) |
I know this is just duplicating the logic on 159, but I think it's worth noting I see issues with this approach.
IMO, we should either throw an explicit "invalid status" error if the status is not one defined in the response body schema, or return a default; throwing a KeyError here has the potential to cause unnecessary confusion.
return rv | ||
# Otherwise, ensure the response body conforms to the promised schema. | ||
schema.loads(rv.data) # May raise ValidationError. | ||
return rv |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit concerned that the branching return here is an indicator that this code is in need of some refactoring. There schema validation assumes a dump were this validation requires a load, but I think otherwise we should be able to share one path. As it stands I'm worried about accidental drift. See normalize note.
if schema is None: | ||
return rv | ||
# Otherwise, ensure the response body conforms to the promised schema. | ||
schema.loads(rv.data) # May raise ValidationError. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before calling schema.loads
I think we may need to call normalize_schema or risk breaking when schema is a class and not an instance.
resp = app.test_client().get(path="/foo") | ||
self.assertEqual(resp.status_code, 302) | ||
self.assertEqual(resp.headers["Location"], "http://foo.com") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May want to add tests for response_body_schema = {status: Schema()} and response_body_schema = Schema cases.
Thanks @airstandley and thanks @twosigmajab ! In the spirit of incremental improvement, I'll get this one merged this afternoon and will open a new Issue for the things noted 🎉 |
https://flask-rebar.readthedocs.io/en/latest/quickstart/basics.html#marshaling currently documents the following: "This means the if
response_body_schema
isNone
, the return value must be a return value that Flask supports, e.g. a string or aFlask.Response
object.However, Flask-Rebar currently fails to interoperate properly with a
Flask.Response
object, as demonstrated by the included test. Without this bugfix to rebar.py, the included test fails withKeyError: 200
. This is because the code currently passes rv to_unpack_view_func_return_value
, which always returns a 200 status when rv is not a tuple, not realizing that aFlask.Response
instance can get passed through as rv which can have a non-200.status_code
(and also custom.headers
as well).