From 60c293d039721f7e842ac8973a743642e182e4a5 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Fri, 30 Jul 2021 08:56:36 -0400 Subject: [PATCH] fix: allow for legacy repeated structured properties with empty values (#702) Fixes #694 --- google/cloud/ndb/model.py | 27 ++++++++++++++------------- tests/system/test_crud.py | 27 +++++++++++++++++++++++++++ tests/unit/test_model.py | 14 ++++++++++++++ 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/google/cloud/ndb/model.py b/google/cloud/ndb/model.py index 71ae0433..a51a280f 100644 --- a/google/cloud/ndb/model.py +++ b/google/cloud/ndb/model.py @@ -4302,23 +4302,24 @@ def _to_datastore(self, entity, data, prefix="", repeated=False): if not self._repeated: values = (values,) - props = tuple(_properties_of(*values)) + if values: + props = tuple(_properties_of(*values)) - for value in values: - if value is None: - keys.extend( - super(StructuredProperty, self)._to_datastore( - entity, data, prefix=prefix, repeated=repeated + for value in values: + if value is None: + keys.extend( + super(StructuredProperty, self)._to_datastore( + entity, data, prefix=prefix, repeated=repeated + ) ) - ) - continue + continue - for prop in props: - keys.extend( - prop._to_datastore( - value, data, prefix=next_prefix, repeated=next_repeated + for prop in props: + keys.extend( + prop._to_datastore( + value, data, prefix=next_prefix, repeated=next_repeated + ) ) - ) return set(keys) diff --git a/tests/system/test_crud.py b/tests/system/test_crud.py index 17ff6e20..34d737a1 100644 --- a/tests/system/test_crud.py +++ b/tests/system/test_crud.py @@ -1164,6 +1164,33 @@ class SomeKind(ndb.Model): assert isinstance(retrieved.bar[2], OtherKind) +@pytest.mark.usefixtures("client_context") +def test_legacy_repeated_structured_property_w_expando_empty( + ds_client, dispose_of, client_context +): + """Regression test for #669 + + https://github.com/googleapis/python-ndb/issues/669 + """ + + class OtherKind(ndb.Expando): + one = ndb.StringProperty() + + class SomeKind(ndb.Model): + foo = ndb.IntegerProperty() + bar = ndb.StructuredProperty(OtherKind, repeated=True) + + entity = SomeKind(foo=42, bar=[]) + + with client_context.new(legacy_data=True).use(): + key = entity.put() + dispose_of(key._key) + + retrieved = key.get() + assert retrieved.foo == 42 + assert retrieved.bar == [] + + @pytest.mark.usefixtures("client_context") def test_insert_expando(dispose_of): class SomeKind(ndb.Expando): diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py index bddbe333..16466e51 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -3524,6 +3524,20 @@ class SomeKind(model.Model): assert SomeKind.foo._to_datastore(entity, data) == {"foo.bar"} assert data == {"foo.bar": ["baz", "boz"]} + @staticmethod + def test__to_datastore_legacy_repeated_empty_value(in_context): + class SubKind(model.Model): + bar = model.Property() + + class SomeKind(model.Model): + foo = model.StructuredProperty(SubKind, repeated=True) + + with in_context.new(legacy_data=True).use(): + entity = SomeKind(foo=[]) + data = {} + assert SomeKind.foo._to_datastore(entity, data) == set() + assert data == {} + @staticmethod def test__prepare_for_put(): class SubKind(model.Model):