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
Leave logic of deciding on extra args to schema #267
Comments
Thanks for the suggestion @tuukkamustonen ; I think it's a good one. I don't think there's a [good] reason for Would you be up for sending a PR? |
I can try to come up with something (hopefully within a few days or so). Edit: Turns out I bumped into other tickets/issues and this got delayed. Still hoping I (or anyone) can dig up time for it! |
Related to #173. |
Hi all, Finding all fields seems less trivial than I had hoped, but I have tried to think up a solution with minimal changes to the existing code. My idea was to have each parser provide a list of fields it knows at each location. Then Benefits of this approach:
Downsides to this:
Would something like this be interesting to pursue? (I did not finish or test it.) |
From my perspective, this was very confusing. I read about marshmallow's support for I was looking at doing something for this based on the above, but I struggled a bit with it. I might try again, but I'd like to share the failing test I wrote in case it helps someone. failing test with strict schemadiff --git a/tests/test_core.py b/tests/test_core.py
index 0df18bc..5767cc8 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -4,7 +4,8 @@ import mock
import datetime
import pytest
-from marshmallow import Schema, post_load, pre_load, class_registry, validates_schema
+from marshmallow import (Schema, post_load, pre_load, class_registry, validates_schema,
+ RAISE)
from werkzeug.datastructures import MultiDict as WerkMultiDict
from django.utils.datastructures import MultiValueDict as DjMultiDict
from bottle import MultiDict as BotMultiDict
@@ -944,6 +945,17 @@ def test_parse_basic(web_request, parser):
assert result == {"foo": 42}
+def test_parse_with_strict_schema(web_request, parser):
+ class StrictSchema(Schema):
+ foo = fields.Int()
+
+ strict_schema = StrictSchema(unknown=RAISE)
+
+ web_request.json = {"foo": "42", "bar": "baz"}
+ with pytest.raises(ValidationError):
+ parser.parse(strict_schema, web_request)
+
+
def test_parse_raises_validation_error_if_data_invalid(web_request, parser):
args = {"email": fields.Email()}
web_request.json = {"email": "invalid"} |
Using latest webargs and marshmallow 3, I just got surprised by the fact that unknown parameters are ignored in top-level schema but trigger an error in nested schemas. |
@lafrech, can you give me an example to work with as a test case for #410 ? I suspect that the current code in #410 does not handle this correctly. |
@sirosen here's a piece of code that illustrates what I reported. Just a quick draft, not a test I'd integrate as is. Hope it helps. Put this somewhere in def test_parser_nested_unknown(web_request):
class MyNestedSchema(Schema):
i = fields.Int()
class MySchema(Schema):
n = fields.Nested(MyNestedSchema)
parser = MockRequestParser()
web_request.json = {
'n': {
'i': 12,
#'j': 12,
},
'o': 12,
}
parsed = parser.parse(MySchema, web_request)
print(parsed)
# {'n': {'i': 12}} Uncomment the
while the Indeed, at the top level, webargs is responsible for picking the attributes, so it doesn't notice the unknown ones. Nested schemas are managed by marshmallow so things go as expected. I didn't try the other way around (having It would be nice to let the marshmallow Schema do everything by passing it the request object, even proxified, rather than calling its top-level fields one by one, which duplicates the logic. I don't know if this is feasible. Might be a great refactor. |
At a quick glance, this is pretty hard to do right now. I think the main issue is that the location can be specified in each field so a schema can have different locations depending on the field. I don't know if there is widespread use of this feature. In practice, if several locations are used, one could call How many times do we actually do this? class InputSchema:
q1 = ma.fields.Int(location="query")
q2 = ma.fields.Int(location="query")
h1 = ma.fields.Int(location="headers")
c1 = ma.fields.Int(location="cookies") Perhaps removing this feature would allow webargs to parse the request first and then feed each Schema the whole data it expects at once. Or maybe I'm totally mistaken and this feature is a must have for a reason I'm missing. Apparently, apispec also uses the In any case, it would be a huge rewrite. |
This seems like the right approach. I'm not entirely opposed to getting rid of the |
In my two months of using webargs, I haven't touched it. So clearly nobody uses it. 😜 Huge 👍 for the idea of webargs dropping this feature. I think it would be a net reduction in complexity, not just for webargs but also for users. I think this might also require other changes, however. At a glance, class FooSchema:
foo_id = ma.fields.UUID()
class BarSchema:
bar_id = ma.fields.Integer()
@app.route("/get_foo/<foo_id>")
@parser.use_args({"view_args": FooSchema()})
def get_foo(foo_id):
...
@app.route("/add_foo/<bar_id>")
@parser.use_args({"query": FooSchema(), "view_args": BarSchema()})
def add_foo_to_bar(bar_id, foo_id):
... right now I'm doing this (and maybe there's a better way today) with things like foo_id_path = parser.use_args(FooSchema(), locations=("view_args",))
@app.route("/get_foo/<foo_id>")
@foo_id_path
def get_foo(foo_id):
... |
You could just have multiple @app.route("/add_foo/<bar_id>")
@parser.use_args(BarSchema(), locations=('view_args', ))
@parser.use_args(FooSchema(), locations=('query', ))
def add_foo_to_bar(bar_id, foo_id): |
What about calling the decorator twice? @app.route("/add_foo/<bar_id>")
@parser.use_args(FooSchema, locations=('query', ))
@parser.use_args(BarSchema, locations=('view_args', ))
def add_foo_to_bar(bar_id, foo_id):
... Do you use the same schema so often it is worth declaring Edit: @sloria, great spirits meet. |
Sorry, I wrote a bad example. I bring up the @parser.use_args(FooSchema, locations=("query", "view_args"))
def get_foo(foo_id):
... Does that mean that That's why the notion of @parser.use_args({"view_args": FooSchema})
def get_foo(foo_id):
...
# or
@parser.use_args(view_args_schema=FooSchema)
... appeals as an interface. You specify "the schema for query params" and "the schema for view args" etc. Cases like the above, with Aside:
Actually yes, but that's somewhat OT. 😄 |
Yes, that's what that would do.
True. If multiple use_[kw]args calls are the way forward, then we should deprecate support for passing a collection ( Can't say whether |
I always use a single location. In fact, our framework (flask-rest-api) only takes a single location as input and make it a list to pass it to webargs' I wouldn't mind removing the multi-locations feature and only taking a single It could read: @parser.use_args(FooSchema, location="query") with I like the multiple However, if we went this way, I don't know what workaround we could propose to people who actually rely on the multi-locations feature to allow a parameter to be passed in different locations. To put it another way, do we want to support this and what would it do? @parser.use_args(FooSchema, location="query")
@parser.use_args(FooSchema, location="view_args") Because as is
|
I think the fact that you need to ask that question -- "what would it do?" -- means it should not be supported. If you want to try to load arguments from multiple locations, you could do this: def myparse_wrapper(func):
def decorated(*args, **kwargs):
try:
return parser.use_args(FooSchema, location="query")(func)(*args, **kwargs)
except ValidationError: # slightly imprecise, since this would capture `func` raising ValidationError
return parser.use_args(FooSchema, location="view_args")(func)(*args, **kwargs)
return decorated Can (should?) webargs facilitate such usage by allowing you to specify |
We're on the same line. All I'm saying is that not supporting this removes a feature with no real workaround. Currently, using a Schema with multiple locations means for each field, try to fetch the data from one location then from the other if the first fail. Each field may come from any location. The workaround you propose tries the whole Schema on a location then the whole Schema again on the second if the first fails validation. This is not equivalent. To propose something equivalent, you'd need to load partial from the first location, then the second, then merge both results. Then check required fields. It already sounds awful. I can't really think of an API that would expect some inputs from several locations. I mean an API may accept a payload in json and query parameters in the same request, but I'd find it weird to build a loose API with a POST /users/ resource where user.age could be specified either in json or in query param, etc. Mixed location is fine, as long as each field can only come from one location. In other words, I wouldn't mind dropping this feature and enforce a single location for a field. On one hand, people may be relying on it, but we don't know that. On the other hand, if someone wanted to add this feature, we'd probably reject that because it adds complexity for what is arguably a smelly edge case. Overall, the proposal is
Whether we use nested calls to |
Let's discuss this refactor in #419. |
Fixed in #420. |
Is there a workaround for this issue? I'm using aiohttp-apispec which depends on webargs==5.x but I'm using marshmallow 3.x for all my argmaps (schemas). I want the same unknown param behavior as what I set in my schemas, but it only applies for nested fields. |
It's been 1.5 years since we did the 6.0 release. If a tool doesn't support 6.0 or later, it might no longer be maintained. You could try subclassing the webargs 5.x parser and making the |
Wow wasn't expecting such a fast response. Thanks for the tip, I'll take a quick look at writing my custom parser but maybe it'll make more sense I try build on aiohttp-apispec to support webargs >=6. |
Now that marshmallow 3.x is in beta and comes with configurable behavior on extra args, how about just including all fields in
Parser._parse_request()
and passing them on toSchema.load()
? It would be up to passed Schema to determine what to do with them.The effect would be that:
RAISE
, extra fields would be denied.INCLUDE
, extra fields would be allowed and passed on.EXCLUDE
, extra fields would be ignored.This would allow something like:
Actually, I don't think this even needs marshmallow
3.x
. Why not just always pass all fields from_parse_request()
toSchema.load()
? On2.x
you would need custom@validates_schema
implementation in addition, but on3.x
you would not.Maybe I'm missing something...
The text was updated successfully, but these errors were encountered: