Skip to content

Commit

Permalink
Merge 76a8d05 into 11c8765
Browse files Browse the repository at this point in the history
  • Loading branch information
dhermes committed Oct 14, 2014
2 parents 11c8765 + 76a8d05 commit fdebba0
Show file tree
Hide file tree
Showing 38 changed files with 1,149 additions and 925 deletions.
2 changes: 2 additions & 0 deletions gcloud/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
"""GCloud API access in idiomatic Python."""


__version__ = '0.02.2'
4 changes: 3 additions & 1 deletion gcloud/connection.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""" Shared implementation of connections to API servers."""

from pkg_resources import get_distribution

import httplib2
Expand Down Expand Up @@ -28,6 +29,7 @@ def __init__(self, credentials=None):
:type credentials: :class:`oauth2client.client.OAuth2Credentials`
:param credentials: The OAuth2 Credentials to use for this connection.
"""
self._http = None
self._credentials = credentials

@property
Expand All @@ -45,7 +47,7 @@ def http(self):
:rtype: :class:`httplib2.Http`
:returns: A Http object used to transport data.
"""
if not hasattr(self, '_http'):
if self._http is None:
self._http = httplib2.Http()
if self._credentials:
self._http = self._credentials.authorize(self._http)
Expand Down
32 changes: 16 additions & 16 deletions gcloud/datastore/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
These functions are *not* part of the API.
"""
import calendar
from datetime import datetime, timedelta
import datetime

from google.protobuf.internal.type_checkers import Int64ValueChecker
import pytz
Expand Down Expand Up @@ -43,7 +43,7 @@ def _get_protobuf_attribute_and_value(val):
:returns: A tuple of the attribute name and proper value type.
"""

if isinstance(val, datetime):
if isinstance(val, datetime.datetime):
name = 'timestamp_microseconds'
# If the datetime is naive (no timezone), consider that it was
# intended to be UTC and replace the tzinfo to that effect.
Expand Down Expand Up @@ -88,32 +88,32 @@ def _get_value_from_protobuf(pb):
:returns: The value provided by the Protobuf.
"""

result = None
if pb.value.HasField('timestamp_microseconds_value'):
microseconds = pb.value.timestamp_microseconds_value
naive = (datetime.utcfromtimestamp(0) +
timedelta(microseconds=microseconds))
return naive.replace(tzinfo=pytz.utc)
naive = (datetime.datetime.utcfromtimestamp(0) +
datetime.timedelta(microseconds=microseconds))
result = naive.replace(tzinfo=pytz.utc)

elif pb.value.HasField('key_value'):
return Key.from_protobuf(pb.value.key_value)
result = Key.from_protobuf(pb.value.key_value)

elif pb.value.HasField('boolean_value'):
return pb.value.boolean_value
result = pb.value.boolean_value

elif pb.value.HasField('double_value'):
return pb.value.double_value
result = pb.value.double_value

elif pb.value.HasField('integer_value'):
return pb.value.integer_value
result = pb.value.integer_value

elif pb.value.HasField('string_value'):
return pb.value.string_value
result = pb.value.string_value

elif pb.value.HasField('entity_value'):
return Entity.from_protobuf(pb.value.entity_value)
result = Entity.from_protobuf(pb.value.entity_value)

else:
return None
return result


def _set_protobuf_value(value_pb, val):
Expand Down Expand Up @@ -142,9 +142,9 @@ def _set_protobuf_value(value_pb, val):
key = val.key()
if key is not None:
e_pb.key.CopyFrom(key.to_protobuf())
for k, v in val.items():
for item_key, value in val.iteritems():
p_pb = e_pb.property.add()
p_pb.name = k
_set_protobuf_value(p_pb.value, v)
p_pb.name = item_key
_set_protobuf_value(p_pb.value, value)
else: # scalar, just assign
setattr(value_pb, attr, val)
6 changes: 3 additions & 3 deletions gcloud/datastore/connection.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Connections to gcloud datastore API servers."""

from gcloud import connection
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
from gcloud.datastore import _helpers
Expand All @@ -23,7 +24,7 @@ class Connection(connection.Connection):
"""A template for the URL of a particular API call."""

def __init__(self, credentials=None):
self._credentials = credentials
super(Connection, self).__init__(credentials=credentials)
self._current_transaction = None

def _request(self, dataset_id, method, data):
Expand Down Expand Up @@ -243,8 +244,7 @@ def run_query(self, dataset_id, query_pb, namespace=None):
return ([e.entity for e in response.batch.entity_result],
response.batch.end_cursor,
response.batch.more_results,
response.batch.skipped_results,
)
response.batch.skipped_results)

def lookup(self, dataset_id, key_pbs):
"""Lookup keys from a dataset in the Cloud Datastore.
Expand Down
4 changes: 4 additions & 0 deletions gcloud/datastore/dataset.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Create / interact with gcloud datastore datasets."""


class Dataset(object):
"""A dataset in the Cloud Datastore.
Expand Down Expand Up @@ -69,6 +70,7 @@ def query(self, *args, **kwargs):
:rtype: :class:`gcloud.datastore.query.Query`
:returns: a new Query instance, bound to this dataset.
"""
# This import is here to avoid circular references.
from gcloud.datastore.query import Query
kwargs['dataset'] = self
return Query(*args, **kwargs)
Expand All @@ -82,6 +84,7 @@ def entity(self, kind):
:rtype: :class:`gcloud.datastore.entity.Entity`
:returns: a new Entity instance, bound to this dataset.
"""
# This import is here to avoid circular references.
from gcloud.datastore.entity import Entity
return Entity(dataset=self, kind=kind)

Expand All @@ -95,6 +98,7 @@ def transaction(self, *args, **kwargs):
:rtype: :class:`gcloud.datastore.transaction.Transaction`
:returns: a new Transaction instance, bound to this dataset.
"""
# This import is here to avoid circular references.
from gcloud.datastore.transaction import Transaction
kwargs['dataset'] = self
return Transaction(*args, **kwargs)
Expand Down
23 changes: 5 additions & 18 deletions gcloud/datastore/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from gcloud.datastore.key import Key


class Entity(dict): # pylint: disable=too-many-public-methods
class Entity(dict):
""":type dataset: :class:`gcloud.datastore.dataset.Dataset`
:param dataset: The dataset in which this entity belongs.
Expand Down Expand Up @@ -91,7 +91,9 @@ def key(self, key=None):
:type key: :class:`glcouddatastore.key.Key`
:param key: The key you want to set on the entity.
:returns: Either the current key or the :class:`Entity`.
:rtype: :class:`gcloud.datastore.key.Key` or :class:`Entity`.
:returns: Either the current key (on get) or the current
object (on set).
>>> entity.key(my_other_key) # This returns the original entity.
<Entity[{'kind': 'OtherKeyKind', 'id': 1234}] {'property': 'value'}>
Expand Down Expand Up @@ -137,7 +139,7 @@ def from_key(cls, key):
return cls().key(key)

@classmethod
def from_protobuf(cls, pb, dataset=None): # pylint: disable=invalid-name
def from_protobuf(cls, pb, dataset=None):
"""Factory method for creating an entity based on a protobuf.
The protobuf should be one returned from the Cloud Datastore
Expand Down Expand Up @@ -176,9 +178,7 @@ def reload(self):
"""

# Note that you must have a valid key, otherwise this makes no sense.
# pylint: disable=maybe-no-member
entity = self.dataset().get_entity(self.key().to_protobuf())
# pylint: enable=maybe-no-member

if entity:
self.update(entity)
Expand All @@ -190,26 +190,20 @@ def save(self):
:rtype: :class:`gcloud.datastore.entity.Entity`
:returns: The entity with a possibly updated Key.
"""
# pylint: disable=maybe-no-member
key_pb = self.dataset().connection().save_entity(
dataset_id=self.dataset().id(),
key_pb=self.key().to_protobuf(), properties=dict(self))
# pylint: enable=maybe-no-member

# If we are in a transaction and the current entity needs an
# automatically assigned ID, tell the transaction where to put that.
transaction = self.dataset().connection().transaction()
# pylint: disable=maybe-no-member
if transaction and self.key().is_partial():
transaction.add_auto_id_entity(self)
# pylint: enable=maybe-no-member

if isinstance(key_pb, datastore_pb.Key):
updated_key = Key.from_protobuf(key_pb)
# Update the path (which may have been altered).
# pylint: disable=maybe-no-member
key = self.key().path(updated_key.path())
# pylint: enable=maybe-no-member
self.key(key)

return self
Expand All @@ -222,20 +216,13 @@ def delete(self):
set on the entity. Whatever is stored remotely using the key on the
entity will be deleted.
"""
# NOTE: pylint thinks key() is an Entity, hence key().to_protobuf()
# is not defined. This is because one branch of the return
# in the key() definition returns self.
# pylint: disable=maybe-no-member
self.dataset().connection().delete_entity(
dataset_id=self.dataset().id(), key_pb=self.key().to_protobuf())
# pylint: enable=maybe-no-member

def __repr__(self): # pragma NO COVER
# An entity should have a key all the time (even if it's partial).
if self.key():
# pylint: disable=maybe-no-member
return '<Entity%s %s>' % (self.key().path(),
super(Entity, self).__repr__())
# pylint: enable=maybe-no-member
else:
return '<Entity %s>' % (super(Entity, self).__repr__())
15 changes: 8 additions & 7 deletions gcloud/datastore/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,9 @@ def is_partial(self):
:returns: True if the last element of the key's path does not have
an 'id' or a 'name'.
"""
return (self.id_or_name() is None)
return self.id_or_name() is None

# NOTE: This is identical to datastore.query.Query.dataset.
def dataset(self, dataset=None):
"""Dataset setter / getter.
Expand Down Expand Up @@ -231,19 +232,19 @@ def kind(self, kind=None):
elif self.path():
return self._path[-1]['kind']

def id(self, id=None):
def id(self, id_to_set=None):
"""ID setter / getter. Based on the last element of path.
:type kind: :class:`str`
:param kind: The new kind for the key.
:type id_to_set: :class:`int`
:param id_to_set: The new ID for the key.
:rtype: :class:`Key` (for setter); or :class:`int` (for getter)
:returns: a new key, cloned from self., with the given id (setter);
or self's id (getter).
or self's id (getter).
"""
if id:
if id_to_set:
clone = self._clone()
clone._path[-1]['id'] = id
clone._path[-1]['id'] = id_to_set
return clone
elif self.path():
return self._path[-1].get('id')
Expand Down
14 changes: 6 additions & 8 deletions gcloud/datastore/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,9 @@ def fetch(self, limit=None):
if limit:
clone = self.limit(limit)

(entity_pbs,
end_cursor,
more_results,
skipped_results) = self.dataset().connection().run_query(
query_results = self.dataset().connection().run_query(
query_pb=clone.to_protobuf(), dataset_id=self.dataset().id())
entity_pbs, end_cursor = query_results[:2]

self._cursor = end_cursor
return [Entity.from_protobuf(entity, dataset=self.dataset())
Expand Down Expand Up @@ -379,14 +377,14 @@ def order(self, *properties):
"""
clone = self._clone()

for p in properties:
for prop in properties:
property_order = clone._pb.order.add()

if p.startswith('-'):
property_order.property.name = p[1:]
if prop.startswith('-'):
property_order.property.name = prop[1:]
property_order.direction = property_order.DESCENDING
else:
property_order.property.name = p
property_order.property.name = prop
property_order.direction = property_order.ASCENDING

return clone
24 changes: 12 additions & 12 deletions gcloud/datastore/test___init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

class Test_get_connection(unittest2.TestCase):

def _callFUT(self, client_email, private_key_path):
@staticmethod
def _callFUT(client_email, private_key_path):
from gcloud.datastore import get_connection
return get_connection(client_email, private_key_path)

Expand All @@ -25,16 +26,16 @@ def test_it(self):
found = self._callFUT(CLIENT_EMAIL, f.name)
self.assertTrue(isinstance(found, Connection))
self.assertTrue(found._credentials is client._signed)
self.assertEqual(client._called_with,
{'service_account_name': CLIENT_EMAIL,
'private_key': PRIVATE_KEY,
'scope': SCOPE,
})
expected_called_with = {'service_account_name': CLIENT_EMAIL,
'private_key': PRIVATE_KEY,
'scope': SCOPE}
self.assertEqual(client._called_with, expected_called_with)


class Test_get_dataset(unittest2.TestCase):

def _callFUT(self, dataset_id, client_email, private_key_path):
@staticmethod
def _callFUT(dataset_id, client_email, private_key_path):
from gcloud.datastore import get_dataset
return get_dataset(dataset_id, client_email, private_key_path)

Expand All @@ -59,8 +60,7 @@ def test_it(self):
self.assertTrue(isinstance(found, Dataset))
self.assertTrue(isinstance(found.connection(), Connection))
self.assertEqual(found.id(), DATASET_ID)
self.assertEqual(client._called_with,
{'service_account_name': CLIENT_EMAIL,
'private_key': PRIVATE_KEY,
'scope': SCOPE,
})
expected_called_with = {'service_account_name': CLIENT_EMAIL,
'private_key': PRIVATE_KEY,
'scope': SCOPE}
self.assertEqual(client._called_with, expected_called_with)

0 comments on commit fdebba0

Please sign in to comment.