Skip to content

Commit aa5adc2

Browse files
committed
Adding parent to Key constructor.
Addresses eighth part of #451.
1 parent 0e57202 commit aa5adc2

3 files changed

Lines changed: 74 additions & 8 deletions

File tree

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: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,18 @@ def __init__(self, *path_args, **kwargs):
5959
:type dataset_id: string
6060
:param dataset_id: The dataset ID associated with the key. This is
6161
required. Can only be passed as a keyword argument.
62+
63+
:type parent: :class:`gcloud.datastore.key.Key`
64+
:param parent: The parent of the key. Can only be passed as a
65+
keyword argument.
6266
"""
63-
self._path = self._parse_path(path_args)
6467
self._flat_path = path_args
65-
self._parent = None
68+
self._parent = kwargs.get('parent')
6669
self._namespace = kwargs.get('namespace')
6770
self._dataset_id = kwargs.get('dataset_id')
71+
# _flat_path, _parent, _namespace and _dataset_id must be set before
72+
# _combine_args() is called.
73+
self._path = self._combine_args()
6874
self._validate_dataset_id()
6975

7076
def _validate_dataset_id(self):
@@ -86,6 +92,11 @@ def _validate_dataset_id(self):
8692
def _parse_path(path_args):
8793
"""Parses positional arguments into key path with kinds and IDs.
8894
95+
:type path_args: :class:`tuple`
96+
:param path_args: A tuple from positional arguments. Should be
97+
alternating list of kinds (string) and id/name
98+
parts (int or string).
99+
89100
:rtype: list of dict
90101
:returns: A list of key parts with kind and id or name set.
91102
:raises: `ValueError` if there are no `path_args`, if one of the
@@ -121,17 +132,42 @@ def _parse_path(path_args):
121132

122133
return result
123134

135+
def _combine_args(self):
136+
"""Sets protected data by combining raw data set from the constructor.
137+
138+
If a _parent is set, updates the _flat_path and sets the
139+
_namespace and _dataset_id if not already set.
140+
141+
:rtype: list of dict
142+
:returns: A list of key parts with kind and id or name set.
143+
:raises: `ValueError` if the parent key is not complete.
144+
"""
145+
child_path = self._parse_path(self._flat_path)
146+
147+
if self._parent is not None:
148+
if self._parent.is_partial:
149+
raise ValueError('Parent key must be complete.')
150+
151+
# We know that _parent.path() will return a copy.
152+
child_path = self._parent.path + child_path
153+
self._flat_path = self._parent.flat_path + self._flat_path
154+
self._namespace = self._namespace or self._parent.namespace
155+
self._dataset_id = self._dataset_id or self._parent.dataset_id
156+
157+
return child_path
158+
124159
def _clone(self):
125160
"""Duplicates the Key.
126161
127-
We make a shallow copy of the :class:`gcloud.datastore.dataset.Dataset`
128-
because it holds a reference an authenticated connection,
129-
which we don't want to lose.
162+
Most attributes are simple types, so don't require copying. Other
163+
attributes like `parent` are long-lived and so we re-use them rather
164+
than creating copies.
130165
131166
:rtype: :class:`gcloud.datastore.key.Key`
132-
:returns: a new `Key` instance
167+
:returns: A new `Key` instance with the same data as the current one.
133168
"""
134-
return copy.deepcopy(self)
169+
return Key(*self.flat_path, parent=self.parent,
170+
dataset_id=self.dataset_id, namespace=self.namespace)
135171

136172
def complete_key(self, id_or_name):
137173
"""Creates new key from existing partial key by adding final ID/name.

gcloud/datastore/test_key.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,36 @@ 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+
4474
def test_ctor_explicit(self):
4575
_DATASET = 'DATASET-ALT'
4676
_NAMESPACE = 'NAMESPACE'

0 commit comments

Comments
 (0)