Skip to content

Commit 8eca8c8

Browse files
author
Chris Rossi
authored
Fix handling of repeated properties with projection queries. (#257)
This fixes issues, generally, with the handling of repeated properties with projection queries, but also specifically for the "class" property of PolyModel entities. Fixes #248
1 parent adcd72d commit 8eca8c8

4 files changed

Lines changed: 106 additions & 12 deletions

File tree

packages/google-cloud-ndb/google/cloud/ndb/model.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -533,20 +533,20 @@ def _entity_from_ds_entity(ds_entity, model_class=None):
533533
"""
534534
class_key = ds_entity.get("class")
535535
if class_key:
536-
kind = class_key[-1]
536+
# If this is a projection query, we'll get multiple entities with
537+
# scalar values rather than single entities with array values.
538+
# It's weird:
539+
# https://cloud.google.com/datastore/docs/concepts/queries#datastore-datastore-array-value-python
540+
if not isinstance(class_key, list):
541+
kind = class_key
542+
else:
543+
kind = class_key[-1]
537544
else:
538545
kind = ds_entity.kind
539546

540547
model_class = model_class or Model._lookup_model(kind)
541548
entity = model_class()
542549

543-
# Check if we are dealing with a PolyModel, and if so get correct subclass.
544-
# We need to import here to avoid circular import.
545-
from google.cloud.ndb import PolyModel
546-
547-
if isinstance(entity, PolyModel) and "class" in ds_entity:
548-
entity = entity._class_map[tuple(ds_entity["class"])]()
549-
550550
if ds_entity.key:
551551
entity._key = key_module.Key._from_ds_key(ds_entity.key)
552552

@@ -640,10 +640,18 @@ def new_entity(key):
640640

641641
if value is not None:
642642
if prop._repeated:
643-
value = [
644-
(_BaseValue(sub_value) if sub_value else None)
645-
for sub_value in value
646-
]
643+
# A repeated property will have a scalar value if this is a
644+
# projection query.
645+
if isinstance(value, list):
646+
# Not a projection
647+
value = [
648+
(_BaseValue(sub_value) if sub_value else None)
649+
for sub_value in value
650+
]
651+
else:
652+
# Projection
653+
value = [_BaseValue(value)]
654+
647655
else:
648656
value = _BaseValue(value)
649657

packages/google-cloud-ndb/tests/system/index.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@ indexes:
2121
- name: foo
2222
- name: bar.one
2323
- name: bar.two
24+
25+
- kind: Animal
26+
properties:
27+
- name: class
28+
- name: foo

packages/google-cloud-ndb/tests/system/test_query.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,46 @@ class Cat(Animal):
607607
assert isinstance(results[0], Cat)
608608

609609

610+
@pytest.mark.usefixtures("client_context")
611+
def test_polymodel_query_class_projection(ds_entity):
612+
"""Regression test for Issue #248
613+
614+
https://github.com/googleapis/python-ndb/issues/248
615+
"""
616+
617+
class Animal(ndb.PolyModel):
618+
foo = ndb.IntegerProperty()
619+
620+
class Cat(Animal):
621+
pass
622+
623+
animal = Animal(foo=1)
624+
animal.put()
625+
cat = Cat(foo=2)
626+
cat.put()
627+
628+
query = Animal.query(projection=["class", "foo"])
629+
results = eventually(query.fetch, _length_equals(3))
630+
631+
# Mostly reproduces odd behavior of legacy code
632+
results = sorted(results, key=operator.attrgetter("foo"))
633+
634+
assert isinstance(results[0], Animal)
635+
assert not isinstance(results[0], Cat)
636+
assert results[0].foo == 1
637+
assert results[0].class_ == ["Animal"]
638+
639+
assert isinstance(results[1], Animal)
640+
assert not isinstance(results[1], Cat)
641+
assert results[1].foo == 2
642+
assert results[1].class_ == ["Animal"]
643+
644+
assert isinstance(results[2], Animal)
645+
assert isinstance(results[2], Cat) # This would be False in legacy
646+
assert results[2].foo == 2
647+
assert results[2].class_ == ["Cat"]
648+
649+
610650
@pytest.mark.usefixtures("client_context")
611651
def test_query_repeated_property(ds_entity):
612652
entity_id = test_utils.system.unique_resource_id()

packages/google-cloud-ndb/tests/unit/test_model.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from google.cloud.ndb import key as key_module
3838
from google.cloud.ndb import model
3939
from google.cloud.ndb import _options
40+
from google.cloud.ndb import polymodel
4041
from google.cloud.ndb import query as query_module
4142
from google.cloud.ndb import tasklets
4243
from google.cloud.ndb import utils as ndb_utils
@@ -5091,6 +5092,46 @@ class ThisKind(model.Model):
50915092
assert entity.baz[2].bar == "iminjail"
50925093
assert entity.copacetic is True
50935094

5095+
@staticmethod
5096+
@pytest.mark.usefixtures("in_context")
5097+
def test_polymodel():
5098+
class Animal(polymodel.PolyModel):
5099+
foo = model.IntegerProperty()
5100+
5101+
class Cat(Animal):
5102+
bar = model.StringProperty()
5103+
5104+
key = datastore.Key("Animal", 123, project="testing")
5105+
datastore_entity = datastore.Entity(key=key)
5106+
datastore_entity.update(
5107+
{"foo": 42, "bar": "himom!", "class": ["Animal", "Cat"]}
5108+
)
5109+
5110+
entity = model._entity_from_ds_entity(datastore_entity)
5111+
assert isinstance(entity, Cat)
5112+
assert entity.foo == 42
5113+
assert entity.bar == "himom!"
5114+
assert entity.class_ == ["Animal", "Cat"]
5115+
5116+
@staticmethod
5117+
@pytest.mark.usefixtures("in_context")
5118+
def test_polymodel_projection():
5119+
class Animal(polymodel.PolyModel):
5120+
foo = model.IntegerProperty()
5121+
5122+
class Cat(Animal):
5123+
bar = model.StringProperty()
5124+
5125+
key = datastore.Key("Animal", 123, project="testing")
5126+
datastore_entity = datastore.Entity(key=key)
5127+
datastore_entity.update({"foo": 42, "bar": "himom!", "class": "Cat"})
5128+
5129+
entity = model._entity_from_ds_entity(datastore_entity)
5130+
assert isinstance(entity, Cat)
5131+
assert entity.foo == 42
5132+
assert entity.bar == "himom!"
5133+
assert entity.class_ == ["Cat"]
5134+
50945135

50955136
class Test_entity_to_protobuf:
50965137
@staticmethod

0 commit comments

Comments
 (0)