Skip to content
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

Marshmallow Two-way Nesting Schemas Validation #55

Open
froggylab opened this issue Jan 27, 2020 · 5 comments
Open

Marshmallow Two-way Nesting Schemas Validation #55

froggylab opened this issue Jan 27, 2020 · 5 comments

Comments

@froggylab
Copy link

froggylab commented Jan 27, 2020

In order to avoid circular import in a flask/marshmallow project, it's possible to reference the Nested field by using its name (as described here https://marshmallow.readthedocs.io/en/latest/nesting.html#two-way-nesting)

Unfortunately, flask_accepts doesn't support it :

 File env/lib/python3.8/site-packages/flask_accepts/decorators/decorators.py", line 110, in decorator
body = for_swagger(
File "env/lib/python3.8/site-packages/flask_accepts/utils.py", line 63, in for_swagger
fields = {
File "env/lib/python3.8/site-packages/flask_accepts/utils.py", line 64, in
k: map_type(v, api, model_name, operation)
File "env/lib/python3.8/site-packages/flask_accepts/utils.py", line 182, in map_type
return type_map[value_type](val, api, model_name, operation)
File "env/lib/python3.8/site-packages/flask_accepts/utils.py", line 19, in unpack_nested
model_name = get_default_model_name(val.nested)
File "env/lib/python3.8/site-packages/flask_accepts/utils.py", line 152, in get_default_model_name
return "".join(schema.name.rsplit("Schema", 1))

AttributeError: 'str' object has no attribute 'name'

A change will also be necessary for the method map_type() since you have to give the object iteself while it's not yet charged.
Can you please add the possibility to support this configuration ?
Thank you

@froggylab
Copy link
Author

In order to help, you can find attached the basic configuration I am using :
case-accepts.tar.gz

thank you,

@v1shwa
Copy link

v1shwa commented May 25, 2021

@froggylab Were you able to find a solution/workaround for this?

@froggylab
Copy link
Author

@v1shwa The only two possibilities I found out were :

  • Transmit only the ID of the item you want to send in order for the frontend to do a second request
  • import one of the schema directly in your schema class definition (but it's ugly)

@v1shwa
Copy link

v1shwa commented May 25, 2021

Thanks for quick response @froggylab . I don't think asking frontend to make another call is an option. I am planning to look into the source & see if we can tweak something. Hopefully, I will raise a PR for this soon.

@ottj3
Copy link

ottj3 commented Aug 12, 2021

Just ran into this myself, for anyone interested here's my workaround, exploiting the fact that marshmallow calculates these string-name-defined schema lazily and stores the result on the schema attribute of the field:

def unpack_nested(val, api, model_name: str = None, operation: str = "dump"):
    if val.nested == "self":
        return unpack_nested_self(val, api, model_name, operation)

+    if isinstance(val.nested, str):
+        parent = val.parent.parent if is_list_field(val.parent) else val.parent
+        parent_name = get_default_model_name(parent)
+        model_name = val.nested + '-in-' + parent_name
+        nested_schema = val.schema
+    else:
        model_name = get_default_model_name(val.nested)
+        nested_schema = val.nested

    if val.many:
        return fr.List(
            fr.Nested(
-                map_type(val.nested, api, model_name, operation), **_ma_field_to_fr_field(val)
+                map_type(nested_schema, api, model_name, operation), **_ma_field_to_fr_field(val)
        )
    )

    return fr.Nested(
-        map_type(val.nested, api, model_name, operation), **_ma_field_to_fr_field(val)
+        map_type(nested_schema, api, model_name, operation), **_ma_field_to_fr_field(val)
    )

I additionally change the model name to <child>-in-<parent>(-load/dump) to prevent conflicts in case your nested fields use only/include/exclude and thus have different fields in their schema. (for_swagger also has to be modified to deal with this, simply add conditions in here: https://github.com/apryor6/flask_accepts/blob/master/flask_accepts/utils.py#L74)

This method can be monkey-patched in via utils.type_map.update({fr.Nested: unpack_nested}) if you want to define it yourself instead of editing the library. (for_swagger can be likewise monkey-patched by updating Schema/SchemaMeta in type_map)

This probably needs some cleanup before being PR-ready material (not even sure if this is a good approach), but it might be a usable workaround for anyone who needs it in the interim.

Bonus: hacky workaround for pluck fields by replacing the first half of that update_nested with

if isinstance(val.nested, str):
    nested_schema = val.schema
else:
    nested_schema = val.nested()

plucked_field = nested_schema.fields[val.field_name]

(and pass plucked_field into map_type instead of nested_schema)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants