Skip to content
This repository was archived by the owner on May 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions google/cloud/ndb/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,17 +279,9 @@ class Key(object):
_hash_value = None

def __new__(cls, *path_args, **kwargs):
# Avoid circular import in Python 2.7
from google.cloud.ndb import context as context_module

_constructor_handle_positional(path_args, kwargs)
instance = super(Key, cls).__new__(cls)

# Make sure to pass in the namespace if it's not explicitly set.
if kwargs.get("namespace", UNDEFINED) is UNDEFINED:
context = context_module.get_context()
kwargs["namespace"] = context.get_namespace()

if "reference" in kwargs or "serialized" in kwargs or "urlsafe" in kwargs:
ds_key, reference = _parse_from_ref(cls, **kwargs)
elif "pairs" in kwargs or "flat" in kwargs:
Expand Down Expand Up @@ -1319,7 +1311,7 @@ def _parse_from_ref(


def _parse_from_args(
pairs=None, flat=None, project=None, app=None, namespace=None, parent=None
pairs=None, flat=None, project=None, app=None, namespace=UNDEFINED, parent=None
):
"""Construct a key the path (and possibly a parent key).

Expand All @@ -1344,6 +1336,9 @@ def _parse_from_args(
Raises:
.BadValueError: If ``parent`` is passed but is not a ``Key``.
"""
# Avoid circular import in Python 2.7
from google.cloud.ndb import context as context_module

flat = _get_path(flat, pairs)
_clean_flat_path(flat)

Expand All @@ -1355,12 +1350,20 @@ def _parse_from_args(
parent_ds_key = None
if parent is None:
project = _project_from_app(app)
if namespace is UNDEFINED:
context = context_module.get_context()
namespace = context.get_namespace()

else:
project = _project_from_app(app, allow_empty=True)
if not isinstance(parent, Key):
raise exceptions.BadValueError(
"Expected Key instance, got {!r}".format(parent)
)

if namespace is UNDEFINED:
namespace = None

# Offload verification of parent to ``google.cloud.datastore.Key()``.
parent_ds_key = parent._key

Expand Down
8 changes: 7 additions & 1 deletion google/cloud/ndb/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -1392,7 +1392,13 @@ def __init__(
else:
project = ancestor.app()
if namespace is not None:
if namespace != ancestor.namespace():
# if namespace is the empty string, that means default
# namespace, but after a put, if the ancestor is using
# the default namespace, its namespace will be None,
# so skip the test to avoid a false mismatch error.
if namespace == "" and ancestor.namespace() is None:
pass
elif namespace != ancestor.namespace():
raise TypeError("ancestor/namespace mismatch")
else:
namespace = ancestor.namespace()
Expand Down
50 changes: 50 additions & 0 deletions tests/system/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,56 @@ class SomeKind(ndb.Model):
assert [entity.foo for entity in results] == [-1, 0, 1, 2, 3, 4]


def test_ancestor_query_with_namespace(client_context, dispose_of, other_namespace):
class Dummy(ndb.Model):
foo = ndb.StringProperty(default="")

entity1 = Dummy(foo="bar", namespace="xyz")
parent_key = entity1.put()
dispose_of(entity1.key._key)

entity2 = Dummy(foo="child", parent=parent_key, namespace=None)
entity2.put()
dispose_of(entity2.key._key)

entity3 = Dummy(foo="childless", namespace="xyz")
entity3.put()
dispose_of(entity3.key._key)

with client_context.new(namespace=other_namespace).use():
query = Dummy.query(ancestor=parent_key, namespace="xyz")
results = eventually(query.fetch, length_equals(2))

assert results[0].foo == "bar"
assert results[1].foo == "child"


def test_ancestor_query_with_default_namespace(
client_context, dispose_of, other_namespace
):
class Dummy(ndb.Model):
foo = ndb.StringProperty(default="")

entity1 = Dummy(foo="bar", namespace="")
parent_key = entity1.put()
dispose_of(entity1.key._key)

entity2 = Dummy(foo="child", parent=parent_key)
entity2.put()
dispose_of(entity2.key._key)

entity3 = Dummy(foo="childless", namespace="")
entity3.put()
dispose_of(entity3.key._key)

with client_context.new(namespace=other_namespace).use():
query = Dummy.query(ancestor=parent_key, namespace="")
results = eventually(query.fetch, length_equals(2))

assert results[0].foo == "bar"
assert results[1].foo == "child"


@pytest.mark.usefixtures("client_context")
def test_projection(ds_entity):
entity_id = test_utils.system.unique_resource_id()
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/test_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,22 @@ def test_constructor_with_parent(self):
)
assert key._reference is None

@pytest.mark.usefixtures("in_context")
def test_constructor_with_parent_and_namespace(self):
parent = key_module.Key(urlsafe=self.URLSAFE)
key = key_module.Key("Zip", 10, parent=parent, namespace=None)

assert key._key == google.cloud.datastore.Key(
"Kind", "Thing", "Zip", 10, project="fire"
)
assert key._reference is None

@pytest.mark.usefixtures("in_context")
def test_constructor_with_parent_and_mismatched_namespace(self):
parent = key_module.Key(urlsafe=self.URLSAFE)
with pytest.raises(ValueError):
key_module.Key("Zip", 10, parent=parent, namespace="foo")

@pytest.mark.usefixtures("in_context")
def test_constructor_with_parent_bad_type(self):
parent = mock.sentinel.parent
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,13 @@ def test_constructor_with_ancestor_and_namespace():
query = query_module.Query(ancestor=key, namespace="space")
assert query.namespace == "space"

@staticmethod
@pytest.mark.usefixtures("in_context")
def test_constructor_with_ancestor_and_default_namespace():
key = key_module.Key("a", "b", namespace=None)
query = query_module.Query(ancestor=key, namespace="")
assert query.namespace == ""

@staticmethod
@pytest.mark.usefixtures("in_context")
def test_constructor_with_ancestor_parameterized_thing():
Expand Down