flask-restx icon indicating copy to clipboard operation
flask-restx copied to clipboard

Nested field as part of entity

Open RamanKhakhlou opened this issue 5 years ago • 4 comments

Hi, folks

So, in DB I have some flatten entity which represents account. It looks like:

data = {
    "id": 1,
    "name": "name",
    "is_active": True,
    "status": "INITIALIZING",
    "created_date": "2020-08-12 15:47:15",
    "created_user_id": "user 0001",
    "updated_date": "2020-08-12 16:47:15",
    "updated_user_id": "user 0002",
}

What I need is to transform it in structure like that:

{
    "id": 1,
    "name": "name",
    "isActive": true,
    "status": "INITIALIZING",
    "created": {"user_id": null},
    "updated": {"user_id": null}
}

Here is my code:

from flask_restx import fields, marshal, Namespace
import json

api = Namespace("sub-account", description="Sub account related operations")

sub_account_created_model = api.model(
    "SubAccountCreated", {"user_id": fields.String(attribute="created_user_id")}
)

sub_account_updated_model = api.model(
    "SubAccountUpdated", {"user_id": fields.String(attribute="updated_user_id")}
)


sub_account_model = api.model(
    "SubAccount",
    {
        "id": fields.Integer(),
        "name": fields.String(),
        "isActive": fields.Boolean(attribute="is_active"),
        "status": fields.String(),
        "created": fields.Nested(sub_account_created_model),
        "updated": fields.Nested(sub_account_updated_model),
    },
)

data = {
    "id": 1,
    "name": "name",
    "is_active": True,
    "status": "INITIALIZING",
    "created_date": "2020-08-12 15:47:15",
    "created_user_id": "user 0001",
    "updated_date": "2020-08-12 16:47:15",
    "updated_user_id": "user 0002",
}

print(json.dumps(marshal(data, sub_account_model)))

But for some reason it doesn't populate nested user_id fields and result looks like: {"id": 1, "name": "name", "isActive": true, "status": "INITIALIZING", "created": {"user_id": null}, "updated": {"user_id": null}}

Can some one point me what I am missing? It works if I change Nested fields to simple dict but it breaks me swagger:

sub_account_model = api.model(
    "SubAccount",
    {
        "id": fields.Integer(),
        "name": fields.String(),
        "isActive": fields.Boolean(attribute="is_active"),
        "status": fields.String(),
        "created": {"user_id": fields.String(attribute="created_user_id")},
        "updated": {"user_id": fields.String(attribute="updated_user_id")},
    },
)

Used version: flask-restx = "==0.2.0"

RamanKhakhlou avatar Aug 13 '20 11:08 RamanKhakhlou

The Nested field only uses the value of the attribute and not the whole object when marshalling with the nested model.

Probably the easiest solution is to subclass the Nested field and replace the output function:

class NestedFields(Nested):

    def __init__(self, model, **kwargs):
        super().__init__(model=model, **kwargs)

    def output(self, key, obj, ordered=False):
        if obj is None:
            if self.allow_null:
                return None
            elif self.default is not None:
                return self.default

        # directly marshal with obj instead of obj.key or obj.attribute
        return marshal(obj, self.nested, skip_none=self.skip_none, ordered=ordered)

buehlefs avatar Oct 03 '20 17:10 buehlefs

@buehlefs thank you for the solution. This worked for me.

tsengorz avatar Oct 13 '20 13:10 tsengorz

Thanks for the solution. How about adding some parameters like from_root=True then it can marshal using whole obj..?

smlee729 avatar Mar 10 '21 19:03 smlee729

The Nested field only uses the value of the attribute and not the whole object when marshalling with the nested model.

Probably the easiest solution is to subclass the Nested field and replace the output function:

class NestedFields(Nested):

    def __init__(self, model, **kwargs):
        super().__init__(model=model, **kwargs)

    def output(self, key, obj, ordered=False):
        if obj is None:
            if self.allow_null:
                return None
            elif self.default is not None:
                return self.default

        # directly marshal with obj instead of obj.key or obj.attribute
        return marshal(obj, self.nested, skip_none=self.skip_none, ordered=ordered)

Thank you so much, this worked like a charm!

To the maintainers: is it in plan to implement this feature natively (maybe with a parameter, like @smlee729 said)?

LukeSavefrogs avatar May 31 '23 03:05 LukeSavefrogs