Skip to content

Commit

Permalink
Merge pull request #667 from dhermes/lazy-loading-attempt-4
Browse files Browse the repository at this point in the history
Adding lazy loading support for dataset ID.
  • Loading branch information
dhermes committed Feb 19, 2015
2 parents 26dba83 + b9d96ed commit 56f60bb
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 26 deletions.
91 changes: 70 additions & 21 deletions gcloud/datastore/_implicit_environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,6 @@
_GCD_DATASET_ENV_VAR_NAME = 'DATASTORE_DATASET'


class _DefaultsContainer(object):
"""Container for defaults.
:type connection: :class:`gcloud.datastore.connection.Connection`
:param connection: Persistent implied connection from environment.
:type dataset_id: string
:param dataset_id: Persistent implied dataset ID from environment.
"""

def __init__(self, connection=None, dataset_id=None, implicit=False):
self.implicit = implicit
self.connection = connection
self.dataset_id = dataset_id


def app_engine_id():
"""Gets the App Engine application ID if it can be inferred.
Expand Down Expand Up @@ -150,6 +134,15 @@ def set_default_dataset_id(dataset_id=None):
raise EnvironmentError('No dataset ID could be inferred.')


def get_default_dataset_id():
"""Get default dataset ID.
:rtype: string or ``NoneType``
:returns: The default dataset ID if one has been set.
"""
return _DEFAULTS.dataset_id


def get_default_connection():
"""Get default connection.
Expand All @@ -159,13 +152,69 @@ def get_default_connection():
return _DEFAULTS.connection


def get_default_dataset_id():
"""Get default dataset ID.
class _LazyProperty(object):
"""Descriptor for lazy loaded property.
:rtype: string or ``NoneType``
:returns: The default dataset ID if one has been set.
This follows the reify pattern: lazy evaluation and then replacement
after evaluation.
:type name: string
:param name: The name of the attribute / property being evaluated.
:type deferred_callable: callable that takes no arguments
:param deferred_callable: The function / method used to evaluate the
property.
"""
return _DEFAULTS.dataset_id

def __init__(self, name, deferred_callable):
self._name = name
self._deferred_callable = deferred_callable

def __get__(self, obj, objtype):
if obj is None or objtype is not _DefaultsContainer:
return self

setattr(obj, self._name, self._deferred_callable())
return getattr(obj, self._name)


def _lazy_property_deco(deferred_callable):
"""Decorator a method to create a :class:`_LazyProperty`.
:type deferred_callable: callable that takes no arguments
:param deferred_callable: The function / method used to evaluate the
property.
:rtype: :class:`_LazyProperty`.
:returns: A lazy property which defers the deferred_callable.
"""
if isinstance(deferred_callable, staticmethod):
# H/T: http://stackoverflow.com/a/9527450/1068170
# For Python2.7+ deferred_callable.__func__ would suffice.
deferred_callable = deferred_callable.__get__(True)
return _LazyProperty(deferred_callable.__name__, deferred_callable)


class _DefaultsContainer(object):
"""Container for defaults.
:type connection: :class:`gcloud.datastore.connection.Connection`
:param connection: Persistent implied connection from environment.
:type dataset_id: string
:param dataset_id: Persistent implied dataset ID from environment.
"""

@_lazy_property_deco
@staticmethod
def dataset_id():
"""Return the implicit default dataset ID."""
return _determine_default_dataset_id()

def __init__(self, connection=None, dataset_id=None, implicit=False):
self.connection = connection
if dataset_id is not None or not implicit:
self.dataset_id = dataset_id


_DEFAULTS = _DefaultsContainer(implicit=True)
4 changes: 2 additions & 2 deletions gcloud/datastore/_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ def _monkey_defaults(*args, **kwargs):
return _Monkey(_implicit_environ, _DEFAULTS=mock_defaults)


def _setup_defaults(test_case):
def _setup_defaults(test_case, *args, **kwargs):
test_case._replaced_defaults = _implicit_environ._DEFAULTS
_implicit_environ._DEFAULTS = _DefaultsContainer()
_implicit_environ._DEFAULTS = _DefaultsContainer(*args, **kwargs)


def _tear_down_defaults(test_case):
Expand Down
76 changes: 76 additions & 0 deletions gcloud/datastore/test__implicit_environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,82 @@ def test_set_implicit_three_env_appengine_and_compute(self):
self.assertEqual(connection.timeout, None)


class Test__lazy_property_deco(unittest2.TestCase):

def _callFUT(self, deferred_callable):
from gcloud.datastore._implicit_environ import _lazy_property_deco
return _lazy_property_deco(deferred_callable)

def test_on_function(self):
def test_func():
pass # pragma: NO COVER never gets called

lazy_prop = self._callFUT(test_func)
self.assertTrue(lazy_prop._deferred_callable is test_func)
self.assertEqual(lazy_prop._name, 'test_func')

def test_on_staticmethod(self):
def test_func():
pass # pragma: NO COVER never gets called

lazy_prop = self._callFUT(staticmethod(test_func))
self.assertTrue(lazy_prop._deferred_callable is test_func)
self.assertEqual(lazy_prop._name, 'test_func')


class Test_lazy_loaded_dataset_id(unittest2.TestCase):

def setUp(self):
from gcloud.datastore._testing import _setup_defaults
_setup_defaults(self, implicit=True)

def tearDown(self):
from gcloud.datastore._testing import _tear_down_defaults
_tear_down_defaults(self)

def test_prop_default(self):
from gcloud.datastore import _implicit_environ
from gcloud.datastore._implicit_environ import _DefaultsContainer
from gcloud.datastore._implicit_environ import _LazyProperty

self.assertTrue(isinstance(_DefaultsContainer.dataset_id,
_LazyProperty))
self.assertEqual(_implicit_environ._DEFAULTS.dataset_id, None)

def test_prop_on_wrong_class(self):
from gcloud.datastore._implicit_environ import _LazyProperty

# Don't actually need a callable for ``method`` since
# __get__ will just return ``self`` in this test.
data_prop = _LazyProperty('dataset_id', None)

class FakeEnv(object):
dataset_id = data_prop

self.assertTrue(FakeEnv.dataset_id is data_prop)
self.assertTrue(FakeEnv().dataset_id is data_prop)

def test_prop_descriptor(self):
from gcloud._testing import _Monkey
from gcloud.datastore import _implicit_environ

self.assertFalse(
'dataset_id' in _implicit_environ._DEFAULTS.__dict__)

DEFAULT = object()

def mock_default():
return DEFAULT

with _Monkey(_implicit_environ,
_determine_default_dataset_id=mock_default):
lazy_loaded = _implicit_environ._DEFAULTS.dataset_id

self.assertEqual(lazy_loaded, DEFAULT)
self.assertTrue(
'dataset_id' in _implicit_environ._DEFAULTS.__dict__)


class _AppIdentity(object):

def __init__(self, app_id):
Expand Down
5 changes: 5 additions & 0 deletions pylintrc_default
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,18 @@ ignore =
# identical implementation but different docstrings.
# - star-args: standard Python idioms for varargs:
# ancestor = Query().filter(*order_props)
# - method-hidden: Decorating a method in a class (e.g. in _DefaultsContainer)
# @_lazy_property_deco
# def dataset_id():
# ...
disable =
maybe-no-member,
no-member,
protected-access,
redefined-builtin,
similarities,
star-args,
method-hidden,


[REPORTS]
Expand Down
3 changes: 2 additions & 1 deletion regression/clear_datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
from six.moves import input

from gcloud import datastore
from gcloud.datastore import _implicit_environ


datastore._DATASET_ENV_VAR_NAME = 'GCLOUD_TESTS_DATASET_ID'
_implicit_environ._DATASET_ENV_VAR_NAME = 'GCLOUD_TESTS_DATASET_ID'
datastore.set_defaults()


Expand Down
3 changes: 2 additions & 1 deletion regression/datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
import unittest2

from gcloud import datastore
from gcloud.datastore import _implicit_environ
# This assumes the command is being run via tox hence the
# repository root is the current directory.
from regression import populate_datastore


datastore._DATASET_ENV_VAR_NAME = 'GCLOUD_TESTS_DATASET_ID'
_implicit_environ._DATASET_ENV_VAR_NAME = 'GCLOUD_TESTS_DATASET_ID'
datastore.set_defaults()


Expand Down
3 changes: 2 additions & 1 deletion regression/populate_datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
from six.moves import zip

from gcloud import datastore
from gcloud.datastore import _implicit_environ


datastore._DATASET_ENV_VAR_NAME = 'GCLOUD_TESTS_DATASET_ID'
_implicit_environ._DATASET_ENV_VAR_NAME = 'GCLOUD_TESTS_DATASET_ID'
datastore.set_defaults()


Expand Down

0 comments on commit 56f60bb

Please sign in to comment.