Skip to content

Commit

Permalink
Make Key constructor easier to use by taking positional args.
Browse files Browse the repository at this point in the history
Addresses fourth part of googleapis#451.
  • Loading branch information
dhermes committed Dec 30, 2014
1 parent 9aff877 commit 03f5dd8
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 255 deletions.
2 changes: 1 addition & 1 deletion gcloud/datastore/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def __init__(self, dataset=None, kind=None, exclude_from_indexes=()):
# _implicit_environ._DatastoreBase to avoid split MRO.
self._dataset = dataset or _implicit_environ.DATASET
if kind:
self._key = Key(path=[{'kind': kind}])
self._key = Key(kind)
else:
self._key = None
self._exclude_from_indexes = set(exclude_from_indexes)
Expand Down
14 changes: 5 additions & 9 deletions gcloud/datastore/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,15 @@ def key_from_protobuf(pb):
:rtype: :class:`gcloud.datastore.key.Key`
:returns: a new `Key` instance
"""
path = []
path_args = []
for element in pb.path_element:
element_dict = {'kind': element.kind}

path_args.append(element.kind)
if element.HasField('id'):
element_dict['id'] = element.id

path_args.append(element.id)
# This is safe: we expect proto objects returned will only have
# one of `name` or `id` set.
if element.HasField('name'):
element_dict['name'] = element.name

path.append(element_dict)
path_args.append(element.name)

dataset_id = None
if pb.partition_id.HasField('dataset_id'):
Expand All @@ -86,7 +82,7 @@ def key_from_protobuf(pb):
if pb.partition_id.HasField('namespace'):
namespace = pb.partition_id.namespace

return Key(path, namespace, dataset_id)
return Key(*path_args, namespace=namespace, dataset_id=dataset_id)


def _pb_attr_value(val):
Expand Down
116 changes: 94 additions & 22 deletions gcloud/datastore/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,51 @@
"""Create / interact with gcloud datastore keys."""

import copy
from itertools import izip
import six

from gcloud.datastore import datastore_v1_pb2 as datastore_pb


class Key(object):
"""An immutable representation of a datastore Key.
To create a basic key:
>>> Key('EntityKind', 1234)
<Key[{'kind': 'EntityKind', 'id': 1234}]>
>>> Key('EntityKind', 'foo')
<Key[{'kind': 'EntityKind', 'name': 'foo'}]>
To create a key with a parent:
>>> Key('Parent', 'foo', 'Child', 1234)
<Key[{'kind': 'Parent', 'name': 'foo'}, {'kind': 'Child', 'id': 1234}]>
To create a paritial key:
>>> Key('Parent', 'foo', 'Child')
<Key[{'kind': 'Parent', 'name': 'foo'}, {'kind': 'Child'}]>
.. automethod:: __init__
"""

def __init__(self, path=None, namespace=None, dataset_id=None):
def __init__(self, *path_args, **kwargs):
"""Constructor / initializer for a key.
:type namespace: :class:`str`
:param namespace: A namespace identifier for the key.
:type path_args: tuple of strings and ints
:param path_args: May represent a partial (odd length) or full (even
length) key path.
:type path: sequence of dicts
:param path: Each dict must have keys 'kind' (a string) and optionally
'name' (a string) or 'id' (an integer).
:type namespace: :class:`str`
:param namespace: A namespace identifier for the key. Can only be
passed as a keyword argument.
:type dataset_id: string
:param dataset: The dataset ID assigned by back-end for the key.
:param dataset_id: The dataset ID associated with the key. Can only be
passed as a keyword argument.
# This note will be obsolete by the end of #451.
.. note::
The key's ``_dataset_id`` field must be None for keys created
Expand All @@ -46,10 +69,50 @@ def __init__(self, path=None, namespace=None, dataset_id=None):
returned from the datastore backend. The application
**must** treat any value set by the back-end as opaque.
"""
self._path = path or [{'kind': ''}]
self._path = self._parse_path(path_args)
self._flat_path = path_args
self._parent = None
self._namespace = namespace
self._dataset_id = dataset_id
self._namespace = kwargs.get('namespace')
self._dataset_id = kwargs.get('dataset_id')

@staticmethod
def _parse_path(path_args):
"""Parses positional arguments into key path with kinds and IDs.
:rtype: list of dict
:returns: A list of key parts with kind and id or name set.
:raises: `ValueError` if there are no `path_args`, if one of the
kinds is not a string or if one of the IDs/names is not
a string or an integer.
"""
if len(path_args) == 0:
raise ValueError('Key path must not be empty.')

kind_list = path_args[::2]
id_or_name_list = path_args[1::2]
if len(path_args) % 2 == 1:
# Add dummy None to be ignored below.
id_or_name_list += (None,)

result = []
for kind, id_or_name in izip(kind_list, id_or_name_list):
curr_key_part = {}
if isinstance(kind, six.string_types):
curr_key_part['kind'] = kind
else:
raise ValueError(kind, 'Kind was not a string.')

if isinstance(id_or_name, six.string_types):
curr_key_part['name'] = id_or_name
elif isinstance(id_or_name, six.integer_types):
curr_key_part['id'] = id_or_name
elif id_or_name is not None:
raise ValueError(id_or_name,
'ID/name was not a string or integer.')

result.append(curr_key_part)

return result

def _clone(self):
"""Duplicates the Key.
Expand All @@ -74,8 +137,8 @@ def to_protobuf(self):
if self.dataset_id is not None:
key.partition_id.dataset_id = self.dataset_id

if self._namespace:
key.partition_id.namespace = self._namespace
if self.namespace:
key.partition_id.namespace = self.namespace

for item in self.path:
element = key.path_element.add()
Expand Down Expand Up @@ -118,15 +181,23 @@ def path(self):
"""
return copy.deepcopy(self._path)

@property
def flat_path(self):
"""Getter for the key path as a tuple.
:rtype: :class:`tuple` of string and int
:returns: The tuple of elements in the path.
"""
return self._flat_path

@property
def kind(self):
"""Kind getter. Based on the last element of path.
:rtype: :class:`str`
:returns: The kind of the current key.
"""
if self.path:
return self.path[-1].get('kind')
return self.path[-1]['kind']

@property
def id(self):
Expand All @@ -135,8 +206,7 @@ def id(self):
:rtype: :class:`int`
:returns: The (integer) ID of the key.
"""
if self.path:
return self.path[-1].get('id')
return self.path[-1].get('id')

@property
def name(self):
Expand All @@ -145,8 +215,7 @@ def name(self):
:rtype: :class:`str`
:returns: The (string) name of the key.
"""
if self.path:
return self.path[-1].get('name')
return self.path[-1].get('name')

@property
def id_or_name(self):
Expand Down Expand Up @@ -178,14 +247,17 @@ def _make_parent(self):
element of self's path. If self has only one path element,
returns None.
"""
parent_path = self.path[:-1]
if parent_path:
return Key(path=parent_path, dataset_id=self.dataset_id,
if self.is_partial:
parent_args = self.flat_path[:-1]
else:
parent_args = self.flat_path[:-2]
if parent_args:
return Key(*parent_args, dataset_id=self.dataset_id,
namespace=self.namespace)

@property
def parent(self):
"""Getter: return a new key for the next highest element in path.
"""The parent of the current key.
:rtype: :class:`gcloud.datastore.key.Key` or `NoneType`
:returns: a new `Key` instance, whose path consists of all but the last
Expand Down
2 changes: 1 addition & 1 deletion gcloud/datastore/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def ancestor(self, ancestor):
This will return a clone of the current :class:`Query` filtered
by the ancestor provided. For example::
>>> parent_key = Key(path=[{'kind': 'Person', 'name': '1'}])
>>> parent_key = Key('Person', '1')
>>> query = dataset.query('Person')
>>> filtered_query = query.ancestor(parent_key)
Expand Down
2 changes: 1 addition & 1 deletion gcloud/datastore/test___init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ def test_allocate_ids(self):
from gcloud._testing import _Monkey

CUSTOM_DATASET = _Dataset()
INCOMPLETE_KEY = Key()
NUM_IDS = 2
with _Monkey(_implicit_environ, DATASET=CUSTOM_DATASET):
INCOMPLETE_KEY = Key('KIND')
result = gcloud.datastore.allocate_ids(INCOMPLETE_KEY, NUM_IDS)

# Check the IDs returned.
Expand Down
Loading

0 comments on commit 03f5dd8

Please sign in to comment.