Skip to content

Commit 4fbcf7c

Browse files
authored
fix: do not allow empty key parts for key constructor in namespaced model (#401)
* fix: do not allow empty key parts for key constructor in namespaced model refs #384
1 parent b16ad59 commit 4fbcf7c

5 files changed

Lines changed: 87 additions & 9 deletions

File tree

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4751,13 +4751,13 @@ def __init__(_self, **kwargs):
47514751
project = app
47524752

47534753
key_parts_unspecified = (
4754-
id_ is None
4755-
and parent is None
4756-
and project is None
4757-
and namespace is key_module.UNDEFINED
4754+
id_ is None and parent is None and project is None
47584755
)
47594756
if key is not None:
4760-
if not key_parts_unspecified:
4757+
if (
4758+
not key_parts_unspecified
4759+
or namespace is not key_module.UNDEFINED
4760+
):
47614761
raise exceptions.BadArgumentError(
47624762
"Model constructor given 'key' does not accept "
47634763
"'id', 'project', 'app', 'namespace', or 'parent'."

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import functools
2020
import operator
2121
import os
22+
import pickle
2223
import random
2324
import threading
2425
import zlib
@@ -1343,3 +1344,39 @@ class SomeKind(ndb.Model):
13431344

13441345
retrieved = key.get()
13451346
assert isinstance(retrieved.entry, OtherKind)
1347+
1348+
1349+
@pytest.mark.usefixtures("client_context")
1350+
def test_serialization(dispose_of):
1351+
"""Regression test for #384
1352+
1353+
https://github.com/googleapis/python-ndb/issues/384
1354+
"""
1355+
1356+
# THis is needed because pickle can't serialize local objects
1357+
global SomeKind, OtherKind
1358+
1359+
class OtherKind(ndb.Model):
1360+
foo = ndb.IntegerProperty()
1361+
1362+
@classmethod
1363+
def _get_kind(cls):
1364+
return "OtherKind"
1365+
1366+
class SomeKind(ndb.Model):
1367+
other = ndb.StructuredProperty(OtherKind)
1368+
1369+
@classmethod
1370+
def _get_kind(cls):
1371+
return "SomeKind"
1372+
1373+
entity = SomeKind(
1374+
other=OtherKind(foo=1, namespace="Test"), namespace="Test"
1375+
)
1376+
key = entity.put()
1377+
dispose_of(key._key)
1378+
1379+
retrieved = key.get()
1380+
assert retrieved.other.key is None or retrieved.other.key.id() is None
1381+
entity = pickle.loads(pickle.dumps(retrieved))
1382+
assert entity.other.foo == 1

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ class AnyKind(ndb.Model):
3939
class MyKind(ndb.Model):
4040
bar = ndb.StringProperty()
4141

42-
entity1 = AnyKind(foo=1, namespace="_test_namespace_")
42+
entity1 = AnyKind(foo=1, id="x", namespace="_test_namespace_")
4343
entity1.put()
4444
dispose_of(entity1.key._key)
4545

46-
entity2 = MyKind(bar="x", namespace="_test_namespace_")
46+
entity2 = MyKind(bar="x", id="x", namespace="_test_namespace_")
4747
entity2.put()
4848
dispose_of(entity2.key._key)
4949

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,11 +284,11 @@ class SomeKind(ndb.Model):
284284
foo = ndb.IntegerProperty()
285285
bar = ndb.StringProperty()
286286

287-
entity1 = SomeKind(foo=1, bar="a", namespace=other_namespace)
287+
entity1 = SomeKind(foo=1, bar="a", id="x", namespace=other_namespace)
288288
entity1.put()
289289
dispose_of(entity1.key._key)
290290

291-
entity2 = SomeKind(foo=2, bar="b")
291+
entity2 = SomeKind(foo=2, bar="b", id="x")
292292
entity2.put()
293293
dispose_of(entity2.key._key)
294294

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4043,6 +4043,12 @@ def test_constructor_key_parts():
40434043
key = key_module.Key("Model", 124)
40444044
assert entity.__dict__ == {"_values": {}, "_entity_key": key}
40454045

4046+
@staticmethod
4047+
@pytest.mark.usefixtures("in_context")
4048+
def test_constructor_namespace_no_key_parts():
4049+
entity = model.Model(namespace="myspace")
4050+
assert entity.__dict__ == {"_values": {}}
4051+
40464052
@staticmethod
40474053
@pytest.mark.usefixtures("in_context")
40484054
def test_constructor_app():
@@ -4070,6 +4076,13 @@ def test_constructor_key_and_key_parts():
40704076
with pytest.raises(exceptions.BadArgumentError):
40714077
model.Model(key=key, id=124)
40724078

4079+
@staticmethod
4080+
@pytest.mark.usefixtures("in_context")
4081+
def test_constructor_key_and_key_parts_with_namespace():
4082+
key = key_module.Key("Foo", "bar")
4083+
with pytest.raises(exceptions.BadArgumentError):
4084+
model.Model(key=key, namespace="myspace")
4085+
40734086
@staticmethod
40744087
def test_constructor_user_property_collision():
40754088
class SecretMap(model.Model):
@@ -5754,6 +5767,34 @@ def test_get_indexes():
57545767
model.get_indexes()
57555768

57565769

5770+
@pytest.mark.usefixtures("in_context")
5771+
def test_serialization():
5772+
5773+
# THis is needed because pickle can't serialize local objects
5774+
global SomeKind, OtherKind
5775+
5776+
class OtherKind(model.Model):
5777+
foo = model.IntegerProperty()
5778+
5779+
@classmethod
5780+
def _get_kind(cls):
5781+
return "OtherKind"
5782+
5783+
class SomeKind(model.Model):
5784+
other = model.StructuredProperty(OtherKind)
5785+
5786+
@classmethod
5787+
def _get_kind(cls):
5788+
return "SomeKind"
5789+
5790+
entity = SomeKind(
5791+
other=OtherKind(foo=1, namespace="Test"), namespace="Test"
5792+
)
5793+
assert entity.other.key is None or entity.other.key.id() is None
5794+
entity = pickle.loads(pickle.dumps(entity))
5795+
assert entity.other.foo == 1
5796+
5797+
57575798
def ManyFieldsFactory():
57585799
"""Model type class factory.
57595800

0 commit comments

Comments
 (0)