Skip to content

Commit 2d81ae5

Browse files
author
Chris Rossi
authored
fix: respect _code_name in StructuredProperty.__getattr__ (#453)
Restores a linear search for subproperty by Python name if different from datastore name, when getting subproperties in `StructuredProperty.__getattr__`. This bit of code just got overlooked when porting from legacy. Fixes #449
1 parent 1c08a88 commit 2d81ae5

File tree

3 files changed

+46
-0
lines changed

3 files changed

+46
-0
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3990,17 +3990,29 @@ def __getattr__(self, attrname):
39903990
"""Dynamically get a subproperty."""
39913991
# Optimistically try to use the dict key.
39923992
prop = self._model_class._properties.get(attrname)
3993+
3994+
# We're done if we have a hit and _code_name matches.
3995+
if prop is None or prop._code_name != attrname:
3996+
# Otherwise, use linear search looking for a matching _code_name.
3997+
for candidate in self._model_class._properties.values():
3998+
if candidate._code_name == attrname:
3999+
prop = candidate
4000+
break
4001+
39934002
if prop is None:
39944003
raise AttributeError(
39954004
"Model subclass %s has no attribute %s"
39964005
% (self._model_class.__name__, attrname)
39974006
)
4007+
39984008
prop_copy = copy.copy(prop)
39994009
prop_copy._name = self._name + "." + prop_copy._name
4010+
40004011
# Cache the outcome, so subsequent requests for the same attribute
40014012
# name will get the copied property directly rather than going
40024013
# through the above motions all over again.
40034014
setattr(self, attrname, prop_copy)
4015+
40044016
return prop_copy
40054017

40064018
def _comparison(self, op, value):

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,30 @@ def make_entities():
969969
results[1].bar.three
970970

971971

972+
@pytest.mark.usefixtures("client_context")
973+
def test_query_structured_property_rename_subproperty(dispose_of):
974+
"""Regression test for #449
975+
976+
https://github.com/googleapis/python-ndb/issues/449
977+
"""
978+
979+
class OtherKind(ndb.Model):
980+
one = ndb.StringProperty("a_different_name")
981+
982+
class SomeKind(ndb.Model):
983+
bar = ndb.StructuredProperty(OtherKind)
984+
985+
key = SomeKind(bar=OtherKind(one="pish")).put()
986+
dispose_of(key._key)
987+
988+
eventually(SomeKind.query().fetch, length_equals(1))
989+
990+
query = SomeKind.query().filter(SomeKind.bar.one == "pish")
991+
results = query.fetch()
992+
assert len(results) == 1
993+
assert results[0].bar.one == "pish"
994+
995+
972996
@pytest.mark.usefixtures("client_context")
973997
def test_query_repeated_structured_property_with_properties(dispose_of):
974998
class OtherKind(ndb.Model):

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3058,6 +3058,16 @@ class Mine(model.Model):
30583058
assert isinstance(prop.foo, model.StringProperty)
30593059
assert prop.foo._name == "bar.foo"
30603060

3061+
@staticmethod
3062+
def test___getattr__use_codename():
3063+
class Mine(model.Model):
3064+
foo = model.StringProperty("notfoo")
3065+
3066+
prop = model.StructuredProperty(Mine)
3067+
prop._name = "bar"
3068+
assert isinstance(prop.foo, model.StringProperty)
3069+
assert prop.foo._name == "bar.notfoo"
3070+
30613071
@staticmethod
30623072
def test___getattr___bad_prop():
30633073
class Mine(model.Model):

0 commit comments

Comments
 (0)