Skip to content

Commit b79d862

Browse files
committed
Merge pull request #476 from dhermes/fix-451-part8
Address eighth part of 451: Add parent to constructor
2 parents 8b04561 + 2fe9e6e commit b79d862

File tree

3 files changed

+93
-10
lines changed

3 files changed

+93
-10
lines changed

gcloud/datastore/dataset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def transaction(self, *args, **kwargs):
120120
def get_entity(self, key):
121121
"""Retrieves entity from the dataset, along with its attributes.
122122
123-
:type key: :class:`gcloud.datastore.key.Key` or path
123+
:type key: :class:`gcloud.datastore.key.Key`
124124
:param key: The key of the entity to be retrieved.
125125
126126
:rtype: :class:`gcloud.datastore.entity.Entity` or `NoneType`

gcloud/datastore/key.py

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,18 @@ def __init__(self, *path_args, **kwargs):
6060
:param dataset_id: The dataset ID associated with the key. Required,
6161
unless the implicit dataset ID has been set. Can
6262
only be passed as a keyword argument.
63+
64+
:type parent: :class:`gcloud.datastore.key.Key`
65+
:param parent: The parent of the key. Can only be passed as a
66+
keyword argument.
6367
"""
64-
self._path = self._parse_path(path_args)
6568
self._flat_path = path_args
66-
self._parent = None
69+
self._parent = kwargs.get('parent')
6770
self._namespace = kwargs.get('namespace')
6871
self._dataset_id = kwargs.get('dataset_id')
72+
# _flat_path, _parent, _namespace and _dataset_id must be set before
73+
# _combine_args() is called.
74+
self._path = self._combine_args()
6975
self._validate_dataset_id()
7076

7177
def _validate_dataset_id(self):
@@ -87,6 +93,11 @@ def _validate_dataset_id(self):
8793
def _parse_path(path_args):
8894
"""Parses positional arguments into key path with kinds and IDs.
8995
96+
:type path_args: :class:`tuple`
97+
:param path_args: A tuple from positional arguments. Should be
98+
alternating list of kinds (string) and id/name
99+
parts (int or string).
100+
90101
:rtype: list of dict
91102
:returns: A list of key parts with kind and id or name set.
92103
:raises: `ValueError` if there are no `path_args`, if one of the
@@ -123,17 +134,49 @@ def _parse_path(path_args):
123134

124135
return result
125136

137+
def _combine_args(self):
138+
"""Sets protected data by combining raw data set from the constructor.
139+
140+
If a _parent is set, updates the _flat_path and sets the
141+
_namespace and _dataset_id if not already set.
142+
143+
:rtype: list of dict
144+
:returns: A list of key parts with kind and id or name set.
145+
:raises: `ValueError` if the parent key is not complete.
146+
"""
147+
child_path = self._parse_path(self._flat_path)
148+
149+
if self._parent is not None:
150+
if self._parent.is_partial:
151+
raise ValueError('Parent key must be complete.')
152+
153+
# We know that _parent.path() will return a copy.
154+
child_path = self._parent.path + child_path
155+
self._flat_path = self._parent.flat_path + self._flat_path
156+
if (self._namespace is not None and
157+
self._namespace != self._parent.namespace):
158+
raise ValueError('Child namespace must agree with parent\'s.')
159+
self._namespace = self._parent.namespace
160+
if (self._dataset_id is not None and
161+
self._dataset_id != self._parent.dataset_id):
162+
raise ValueError('Child dataset ID must agree with parent\'s.')
163+
self._dataset_id = self._parent.dataset_id
164+
165+
return child_path
166+
126167
def _clone(self):
127168
"""Duplicates the Key.
128169
129-
We make a shallow copy of the :class:`gcloud.datastore.dataset.Dataset`
130-
because it holds a reference an authenticated connection,
131-
which we don't want to lose.
170+
Most attributes are simple types, so don't require copying. Other
171+
attributes like `parent` are long-lived and so we re-use them rather
172+
than creating copies.
132173
133174
:rtype: :class:`gcloud.datastore.key.Key`
134-
:returns: a new `Key` instance
175+
:returns: A new `Key` instance with the same data as the current one.
135176
"""
136-
return copy.deepcopy(self)
177+
return self.__class__(*self.flat_path, parent=self.parent,
178+
dataset_id=self.dataset_id,
179+
namespace=self.namespace)
137180

138181
def completed_key(self, id_or_name):
139182
"""Creates new key from existing partial key by adding final ID/name.
@@ -285,8 +328,8 @@ def _make_parent(self):
285328
else:
286329
parent_args = self.flat_path[:-2]
287330
if parent_args:
288-
return Key(*parent_args, dataset_id=self.dataset_id,
289-
namespace=self.namespace)
331+
return self.__class__(*parent_args, dataset_id=self.dataset_id,
332+
namespace=self.namespace)
290333

291334
@property
292335
def parent(self):

gcloud/datastore/test_key.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,46 @@ def test_ctor_no_dataset(self):
4141
with _Monkey(_implicit_environ, DATASET=None):
4242
self.assertRaises(ValueError, klass, 'KIND')
4343

44+
def test_ctor_parent(self):
45+
_PARENT_KIND = 'KIND1'
46+
_PARENT_ID = 1234
47+
_PARENT_DATASET = 'DATASET-ALT'
48+
_PARENT_NAMESPACE = 'NAMESPACE'
49+
parent_key = self._makeOne(_PARENT_KIND, _PARENT_ID,
50+
dataset_id=_PARENT_DATASET,
51+
namespace=_PARENT_NAMESPACE)
52+
_CHILD_KIND = 'KIND2'
53+
_CHILD_ID = 2345
54+
_PATH = [
55+
{'kind': _PARENT_KIND, 'id': _PARENT_ID},
56+
{'kind': _CHILD_KIND, 'id': _CHILD_ID},
57+
]
58+
key = self._makeOne(_CHILD_KIND, _CHILD_ID, parent=parent_key)
59+
self.assertEqual(key.dataset_id, parent_key.dataset_id)
60+
self.assertEqual(key.namespace, parent_key.namespace)
61+
self.assertEqual(key.kind, _CHILD_KIND)
62+
self.assertEqual(key.path, _PATH)
63+
self.assertTrue(key.parent is parent_key)
64+
65+
def test_ctor_partial_parent(self):
66+
parent_key = self._makeOne('KIND')
67+
with self.assertRaises(ValueError):
68+
self._makeOne('KIND2', 1234, parent=parent_key)
69+
70+
def test_ctor_parent_bad_type(self):
71+
with self.assertRaises(AttributeError):
72+
self._makeOne('KIND2', 1234, parent=('KIND1', 1234))
73+
74+
def test_ctor_parent_bad_namespace(self):
75+
parent_key = self._makeOne('KIND', 1234, namespace='FOO')
76+
with self.assertRaises(ValueError):
77+
self._makeOne('KIND2', 1234, namespace='BAR', parent=parent_key)
78+
79+
def test_ctor_parent_bad_dataset_id(self):
80+
parent_key = self._makeOne('KIND', 1234, dataset_id='FOO')
81+
with self.assertRaises(ValueError):
82+
self._makeOne('KIND2', 1234, dataset_id='BAR', parent=parent_key)
83+
4484
def test_ctor_explicit(self):
4585
_DATASET = 'DATASET-ALT'
4686
_NAMESPACE = 'NAMESPACE'

0 commit comments

Comments
 (0)