-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Key.compare_to_proto to check pb keys against existing.
Addresses sixth part of #451.
- Loading branch information
Showing
4 changed files
with
173 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,7 +27,7 @@ class NoDataset(RuntimeError): | |
"""Exception raised by Entity methods which require a dataset.""" | ||
|
||
|
||
class Entity(object): | ||
class Entity(_implicit_environ._DatastoreBase): | ||
"""Entities are akin to rows in a relational database | ||
An entity storing the actual instance of data. | ||
|
@@ -94,9 +94,7 @@ class Entity(object): | |
""" | ||
|
||
def __init__(self, dataset=None, kind=None, exclude_from_indexes=()): | ||
# Does not inherit from object, so we don't use | ||
# _implicit_environ._DatastoreBase to avoid split MRO. | ||
self._dataset = dataset or _implicit_environ.DATASET | ||
super(Entity, self).__init__(dataset=dataset) | ||
self._data = {} | ||
if kind: | ||
self._key = Key(kind) | ||
|
@@ -286,7 +284,7 @@ def save(self): | |
key_pb = connection.save_entity( | ||
dataset_id=dataset.id(), | ||
key_pb=key.to_protobuf(), | ||
properties=self._data, | ||
properties=self.to_dict(), | ||
This comment has been minimized.
Sorry, something went wrong. |
||
exclude_from_indexes=self.exclude_from_indexes()) | ||
|
||
# If we are in a transaction and the current entity needs an | ||
|
@@ -296,22 +294,8 @@ def save(self): | |
transaction.add_auto_id_entity(self) | ||
|
||
if isinstance(key_pb, datastore_pb.Key): | ||
# Update the path (which may have been altered). | ||
# NOTE: The underlying namespace can't have changed in a save(). | ||
# The value of the dataset ID may have changed from implicit | ||
# (i.e. None, with the ID implied from the dataset.Dataset | ||
# object associated with the Entity/Key), but if it was | ||
# implicit before the save() we leave it as implicit. | ||
path = [] | ||
for element in key_pb.path_element: | ||
key_part = {} | ||
for descriptor, value in element._fields.items(): | ||
key_part[descriptor.name] = value | ||
path.append(key_part) | ||
# This is temporary. Will be addressed throughout #451. | ||
clone = key._clone() | ||
clone._path = path | ||
self._key = clone | ||
# Update the key (which may have been altered). | ||
self.key(self.key().compare_to_proto(key_pb)) | ||
This comment has been minimized.
Sorry, something went wrong.
tseaver
Contributor
|
||
|
||
return self | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -152,6 +152,92 @@ def complete_key(self, id_or_name): | |
new_key._flat_path += (id_or_name,) | ||
return new_key | ||
|
||
def _validate_protobuf_dataset_id(self, protobuf): | ||
"""Checks that dataset ID on protobuf matches current one. | ||
The value of the dataset ID may have changed from unprefixed | ||
(e.g. 'foo') to prefixed (e.g. 's~foo' or 'e~foo'). | ||
:type protobuf: :class:`gcloud.datastore.datastore_v1_pb2.Key` | ||
:param protobuf: A protobuf representation of the key. Expected to be | ||
returned after a datastore operation. | ||
:rtype: :class:`str` | ||
""" | ||
proto_dataset_id = protobuf.partition_id.dataset_id | ||
if proto_dataset_id == self.dataset_id: | ||
return | ||
|
||
# Since they don't match, we check to see if `proto_dataset_id` has a | ||
# prefix. | ||
unprefixed = None | ||
prefix = proto_dataset_id[:2] | ||
if prefix in ('s~', 'e~'): | ||
unprefixed = proto_dataset_id[2:] | ||
|
||
if unprefixed != self.dataset_id: | ||
raise ValueError('Dataset ID on protobuf does not match.', | ||
proto_dataset_id, self.dataset_id) | ||
|
||
def compare_to_proto(self, protobuf): | ||
"""Checks current key against a protobuf; updates if partial. | ||
If the current key is partial, returns a new key that has been | ||
completed otherwise returns the current key. | ||
The value of the dataset ID may have changed from implicit (i.e. None, | ||
with the ID implied from the dataset.Dataset object associated with the | ||
Entity/Key), but if it was implicit before, we leave it as implicit. | ||
:type protobuf: :class:`gcloud.datastore.datastore_v1_pb2.Key` | ||
:param protobuf: A protobuf representation of the key. Expected to be | ||
returned after a datastore operation. | ||
:rtype: :class:`gcloud.datastore.key.Key` | ||
:returns: The current key if not partial. | ||
:raises: `ValueError` if the namespace or dataset ID of `protobuf` | ||
don't match the current values or if the path from `protobuf` | ||
doesn't match. | ||
""" | ||
if self.namespace is None: | ||
if protobuf.partition_id.HasField('namespace'): | ||
raise ValueError('Namespace unset on key but set on protobuf.') | ||
elif protobuf.partition_id.namespace != self.namespace: | ||
raise ValueError('Namespace on protobuf does not match.', | ||
protobuf.partition_id.namespace, self.namespace) | ||
|
||
# Check that dataset IDs match if not implicit. | ||
if self.dataset_id is not None: | ||
self._validate_protobuf_dataset_id(protobuf) | ||
|
||
path = [] | ||
This comment has been minimized.
Sorry, something went wrong.
tseaver
Contributor
|
||
for element in protobuf.path_element: | ||
key_part = {} | ||
for descriptor, value in element._fields.items(): | ||
key_part[descriptor.name] = value | ||
path.append(key_part) | ||
|
||
if path == self.path: | ||
return self | ||
|
||
if not self.is_partial: | ||
raise ValueError('Proto path does not match completed key.', | ||
path, self.path) | ||
|
||
last_part = path[-1] | ||
id_or_name = None | ||
if 'id' in last_part: | ||
id_or_name = last_part.pop('id') | ||
elif 'name' in last_part: | ||
id_or_name = last_part.pop('name') | ||
This comment has been minimized.
Sorry, something went wrong.
tseaver
Contributor
|
||
|
||
# We have edited path by popping from the last part, so check again. | ||
if path != self.path: | ||
raise ValueError('Proto path does not match partial key.', | ||
path, self.path) | ||
|
||
return self.complete_key(id_or_name) | ||
|
||
def to_protobuf(self): | ||
"""Return a protobuf corresponding to the key. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Why not pass
self._data
: we can trust the connection not to mutate it, right? Making a copy seems wrong.