1515"""Create / interact with gcloud datastore keys."""
1616
1717import copy
18+ from itertools import izip
19+ import six
1820
1921from gcloud .datastore import datastore_v1_pb2 as datastore_pb
2022
2123
2224class Key (object ):
2325 """An immutable representation of a datastore Key.
2426
27+ To create a basic key:
28+
29+ >>> Key('EntityKind', 1234)
30+ <Key[{'kind': 'EntityKind', 'id': 1234}]>
31+ >>> Key('EntityKind', 'foo')
32+ <Key[{'kind': 'EntityKind', 'name': 'foo'}]>
33+
34+ To create a key with a parent:
35+
36+ >>> Key('Parent', 'foo', 'Child', 1234)
37+ <Key[{'kind': 'Parent', 'name': 'foo'}, {'kind': 'Child', 'id': 1234}]>
38+
39+ To create a paritial key:
40+
41+ >>> Key('Parent', 'foo', 'Child')
42+ <Key[{'kind': 'Parent', 'name': 'foo'}, {'kind': 'Child'}]>
43+
2544 .. automethod:: __init__
2645 """
2746
28- def __init__ (self , path = None , namespace = None , dataset_id = None ):
47+ def __init__ (self , * path_args , ** kwargs ):
2948 """Constructor / initializer for a key.
3049
31- :type namespace: :class:`str`
32- :param namespace: A namespace identifier for the key.
50+ :type path_args: tuple of strings and ints
51+ :param path_args: May represent a partial (odd length) or full (even
52+ length) key path.
3353
34- :type path: sequence of dicts
35- :param path: Each dict must have keys 'kind' (a string) and optionally
36- 'name' (a string) or 'id' (an integer) .
54+ :type namespace: :class:`str`
55+ :param namespace: A namespace identifier for the key. Can only be
56+ passed as a keyword argument .
3757
3858 :type dataset_id: string
39- :param dataset: The dataset ID assigned by back-end for the key.
59+ :param dataset_id: The dataset ID associated with the key. Can only be
60+ passed as a keyword argument.
61+
62+ # This note will be obsolete by the end of #451.
4063
4164 .. note::
4265 The key's ``_dataset_id`` field must be None for keys created
@@ -46,10 +69,51 @@ def __init__(self, path=None, namespace=None, dataset_id=None):
4669 returned from the datastore backend. The application
4770 **must** treat any value set by the back-end as opaque.
4871 """
49- self ._path = path or [{'kind' : '' }]
72+ self ._path = self ._parse_path (path_args )
73+ self ._flat_path = path_args
5074 self ._parent = None
51- self ._namespace = namespace
52- self ._dataset_id = dataset_id
75+ self ._namespace = kwargs .get ('namespace' )
76+ self ._dataset_id = kwargs .get ('dataset_id' )
77+
78+ @staticmethod
79+ def _parse_path (path_args ):
80+ """Parses positional arguments into key path with kinds and IDs.
81+
82+ :rtype: list of dict
83+ :returns: A list of key parts with kind and id or name set.
84+ :raises: `ValueError` if there are no `path_args`, if one of the
85+ kinds is not a string or if one of the IDs/names is not
86+ a string or an integer.
87+ """
88+ if len (path_args ) == 0 :
89+ raise ValueError ('Key path must not be empty.' )
90+
91+ kind_list = path_args [::2 ]
92+ id_or_name_list = path_args [1 ::2 ]
93+ # Dummy sentinel value to pad incomplete key to even length path.
94+ partial_ending = object ()
95+ if len (path_args ) % 2 == 1 :
96+ id_or_name_list += (partial_ending ,)
97+
98+ result = []
99+ for kind , id_or_name in izip (kind_list , id_or_name_list ):
100+ curr_key_part = {}
101+ if isinstance (kind , six .string_types ):
102+ curr_key_part ['kind' ] = kind
103+ else :
104+ raise ValueError (kind , 'Kind was not a string.' )
105+
106+ if isinstance (id_or_name , six .string_types ):
107+ curr_key_part ['name' ] = id_or_name
108+ elif isinstance (id_or_name , six .integer_types ):
109+ curr_key_part ['id' ] = id_or_name
110+ elif id_or_name is not partial_ending :
111+ raise ValueError (id_or_name ,
112+ 'ID/name was not a string or integer.' )
113+
114+ result .append (curr_key_part )
115+
116+ return result
53117
54118 def _clone (self ):
55119 """Duplicates the Key.
@@ -74,8 +138,8 @@ def to_protobuf(self):
74138 if self .dataset_id is not None :
75139 key .partition_id .dataset_id = self .dataset_id
76140
77- if self ._namespace :
78- key .partition_id .namespace = self ._namespace
141+ if self .namespace :
142+ key .partition_id .namespace = self .namespace
79143
80144 for item in self .path :
81145 element = key .path_element .add ()
@@ -118,15 +182,23 @@ def path(self):
118182 """
119183 return copy .deepcopy (self ._path )
120184
185+ @property
186+ def flat_path (self ):
187+ """Getter for the key path as a tuple.
188+
189+ :rtype: :class:`tuple` of string and int
190+ :returns: The tuple of elements in the path.
191+ """
192+ return self ._flat_path
193+
121194 @property
122195 def kind (self ):
123196 """Kind getter. Based on the last element of path.
124197
125198 :rtype: :class:`str`
126199 :returns: The kind of the current key.
127200 """
128- if self .path :
129- return self .path [- 1 ].get ('kind' )
201+ return self .path [- 1 ]['kind' ]
130202
131203 @property
132204 def id (self ):
@@ -135,8 +207,7 @@ def id(self):
135207 :rtype: :class:`int`
136208 :returns: The (integer) ID of the key.
137209 """
138- if self .path :
139- return self .path [- 1 ].get ('id' )
210+ return self .path [- 1 ].get ('id' )
140211
141212 @property
142213 def name (self ):
@@ -145,8 +216,7 @@ def name(self):
145216 :rtype: :class:`str`
146217 :returns: The (string) name of the key.
147218 """
148- if self .path :
149- return self .path [- 1 ].get ('name' )
219+ return self .path [- 1 ].get ('name' )
150220
151221 @property
152222 def id_or_name (self ):
@@ -178,14 +248,17 @@ def _make_parent(self):
178248 element of self's path. If self has only one path element,
179249 returns None.
180250 """
181- parent_path = self .path [:- 1 ]
182- if parent_path :
183- return Key (path = parent_path , dataset_id = self .dataset_id ,
251+ if self .is_partial :
252+ parent_args = self .flat_path [:- 1 ]
253+ else :
254+ parent_args = self .flat_path [:- 2 ]
255+ if parent_args :
256+ return Key (* parent_args , dataset_id = self .dataset_id ,
184257 namespace = self .namespace )
185258
186259 @property
187260 def parent (self ):
188- """Getter: return a new key for the next highest element in path .
261+ """The parent of the current key .
189262
190263 :rtype: :class:`gcloud.datastore.key.Key` or `NoneType`
191264 :returns: a new `Key` instance, whose path consists of all but the last
0 commit comments