pynamodb / PynamoDB

A pythonic interface to Amazon's DynamoDB
http://pynamodb.readthedocs.io
MIT License
2.46k stars 427 forks source link

why do I have an empty attribute_values field in my DynamicMapAttribute attribute #999

Open wobeng opened 3 years ago

wobeng commented 3 years ago

Hello,

I have an attribute setup with way:


class MediaMap(MapAttribute):
    ct = NumberAttribute(default_for_new=0)
    items = DynamicMapAttribute(default_for_new=dict)

class MediaGroupMap(MapAttribute):
    docs = MediaMap(default_for_new=dict)
    files = MediaMap(default_for_new=dict)
    audios = MediaMap(default_for_new=dict)
    images = MediaMap(default_for_new=dict)
    videos = MediaMap(default_for_new=dict)

When I insert data and pull it back out I get an empty attribute_values map. Is it by design?

   "media":{
      "audios":{
         "ct":1,
         "items":{
            "attribute_values":{

            },
            "2021_11_18T09_22_11_071586_a72a8227_64f3_4bcf_9e27_0c127d71feaa":{
               "execution_id":"2eef13d2-a8ab-4837-84fe-56ccc3593cdf",
               "media_size":443926,
               "content_type":"application/octet-stream",
               "etag":"2ca80224c6de3f27c3db8ebc8a7444b3",
               "source":"upload",
               "media_group":"audios"
            }
         }
      },
      "docs":{
         "ct":1,
         "items":{
            "attribute_values":{

            },
            "2021_11_18T09_22_11_267344_a45b4a73_9f9e_40fa_949f_8bd54ff83fa5":{
               "execution_id":"b7b37d90-9252-441c-af1f-ec626bcd5564",
               "media_size":32768,
               "content_type":"application/octet-stream",
               "etag":"50f723d62aa1ff16cfbe5af1e93b7008",
               "source":"upload",
               "media_group":"docs"
            }
         }
      },
      "files":{
         "ct":1,
         "items":{
            "attribute_values":{

            },
            "2021_11_18T09_22_11_467357_40dce364_7efc_4a62_8a5b_5264d141126f":{
               "execution_id":"e6441017-2ad3-4807-ad0f-9c72de5421ca",
               "media_size":8,
               "content_type":"application/octet-stream",,
               "etag":"eb1a3227cdc3fedbaec2fe38bf6c044a",
               "source":"upload",
               "media_group":"files"
            }
         }
      },
      "images":{
         "ct":1,
         "items":{
            "attribute_values":{

            },
            "2021_11_18T09_22_11_590072_4168e196_bd79_4a58_b47a_a059408abe9e":{
               "execution_id":"c59cb061-8107-401e-8bb5-c9de546f93de",
               "media_size":350749,
               "content_type":"application/octet-stream",
               "etag":"5297bd123ad4ddad723483c176e35f6e",
               "source":"upload",
               "media_group":"images"
            }
         }
      },
      "videos":{
         "ct":1,
         "items":{
            "attribute_values":{

            },
            "2021_11_18T09_22_11_846882_88b55dc6_0508_4a76_9176_6f6c11349495":{
               "execution_id":"550ce829-f643-4fe0-8742-2b6d53e50cfc",
               "media_size":1055736,
               "content_type":"application/octet-stream",
               "etag":"d55bddf8d62910879ed9f605522149a8",
               "source":"upload",
               "media_group":"videos"
            }
         }
      }
   }

Thank you so much in advance

Yaronn44 commented 2 years ago

This is a problem for me too. I wanted to upgrade my PynamoDB version and start using DynamicMapAttribute, but several tests of mines are failing because of this behavior and I can't move forward.

The only thing I can add about this behavior is that it only happens with the update operations. The save() does not do had the attribute_values in the DynamicMapAttribute.

OGoodness commented 2 years ago

+1 same for me

fdobrovolny commented 2 years ago

I did some investigation and found what is happening.

Basically, when we deserialize the object, the method _container_deserialize gets called. The issue is that inside that function, it sets self.attribute_values = {} to initialize it. Normally this works fine, but on MapAttribute, it calls __setattr__, which instead of "self.attribute_values = {}" does self.attribute_values["attribute_values"] = {}.

Going of the comment in __setattr__:

# "Raw" (i.e. non-subclassed) instances set their name-value pairs in the `attribute_values` dictionary.
# MapAttribute subclasses should set attributes via the Attribute descriptors.
if self.is_raw() and self._is_attribute_container():
    self.attribute_values[name] = value
...

I think the issue is in the implementation of the is_raw method.

On MapAttribute it looks like this:

    @classmethod
    def is_raw(cls):
        return cls == MapAttribute

But on DynamicMapAttribute it looks like this this:

    @classmethod
    def is_raw(cls):
        # All subclasses of DynamicMapAttribute should be treated like "raw" map attributes.
        return True

Hope this helps.

bencwallace commented 2 years ago

I also took some time to look into this and found the same issue as @fdobrovolny. I also found a fix that treats the "attribute_values" attribute as a special case in the __setattr__ method. However, technically it may break backwards-compatibility if anyone actually wants an attribute called "attribute_values". To be honest, I don't really see a way around this, though.

half2me commented 1 year ago

So it's not just me who gets this.

I have a use case where I keep a map of maps like so:

"myMap": {
  "aba75878-c068-4f64-8559-ea62706b9c11": {
    "foo": "bar",
    "bar": "foo"
  },
  "eb6009b4-883e-46b9-a066-151e88f7c7d4": {
    "foo": "bar",
    "bar": "foo"
  },
}

Basically a list of maps, but using a Map instead of a list. I could define each item with a MapAttribute but my key names are just generated strings, so I'd need to use the DynamicMapAttribute but that keeps adding this attribute_values key, which is breaking my app.

Any idea what I could use to get around this?