From d7e1d9c598ea219a5efc41d3fb8e14beb6c0d6cb Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 28 Oct 2015 01:13:56 -0700 Subject: [PATCH] Refactored utils --- graphene/utils.py | 155 ------------------ graphene/utils/__init__.py | 7 + graphene/utils/caching.py | 35 ++++ graphene/utils/lazymap.py | 42 +++++ graphene/utils/proxy_snake_dict.py | 63 +++++++ graphene/utils/str_converters.py | 17 ++ ...test_utils.py => test_proxy_snake_dict.py} | 12 +- tests/utils/test_str_converter.py | 14 ++ 8 files changed, 179 insertions(+), 166 deletions(-) delete mode 100644 graphene/utils.py create mode 100644 graphene/utils/__init__.py create mode 100644 graphene/utils/caching.py create mode 100644 graphene/utils/lazymap.py create mode 100644 graphene/utils/proxy_snake_dict.py create mode 100644 graphene/utils/str_converters.py rename tests/utils/{test_utils.py => test_proxy_snake_dict.py} (62%) create mode 100644 tests/utils/test_str_converter.py diff --git a/graphene/utils.py b/graphene/utils.py deleted file mode 100644 index 00aadf6e..00000000 --- a/graphene/utils.py +++ /dev/null @@ -1,155 +0,0 @@ -import collections -import re -from functools import wraps - - -class cached_property(object): - """ - A property that is only computed once per instance and then replaces itself - with an ordinary attribute. Deleting the attribute resets the property. - Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 - """ # noqa - - def __init__(self, func): - self.__doc__ = getattr(func, '__doc__') - self.func = func - - def __get__(self, obj, cls): - if obj is None: - return self - value = obj.__dict__[self.func.__name__] = self.func(obj) - return value - - -def memoize(fun): - """A simple memoize decorator for functions supporting positional args.""" - @wraps(fun) - def wrapper(*args, **kwargs): - key = (args, frozenset(sorted(kwargs.items()))) - try: - return cache[key] - except KeyError: - ret = cache[key] = fun(*args, **kwargs) - return ret - cache = {} - return wrapper - - -# From this response in Stackoverflow -# http://stackoverflow.com/a/19053800/1072990 -def to_camel_case(snake_str): - components = snake_str.split('_') - # We capitalize the first letter of each component except the first one - # with the 'title' method and join them together. - return components[0] + "".join(x.title() for x in components[1:]) - - -# From this response in Stackoverflow -# http://stackoverflow.com/a/1176023/1072990 -def to_snake_case(name): - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() - - -class ProxySnakeDict(collections.MutableMapping): - __slots__ = ('data') - - def __init__(self, data): - self.data = data - - def __contains__(self, key): - return key in self.data or to_camel_case(key) in self.data - - def get(self, key, default=None): - try: - return self.__getitem__(key) - except KeyError: - return default - - def __iter__(self): - return self.iterkeys() - - def __len__(self): - return len(self.data) - - def __delitem__(self): - raise TypeError('ProxySnakeDict does not support item deletion') - - def __setitem__(self): - raise TypeError('ProxySnakeDict does not support item assignment') - - def __getitem__(self, key): - if key in self.data: - item = self.data[key] - else: - camel_key = to_camel_case(key) - if camel_key in self.data: - item = self.data[camel_key] - else: - raise KeyError(key, camel_key) - - if isinstance(item, dict): - return ProxySnakeDict(item) - return item - - def keys(self): - return list(self.iterkeys()) - - def items(self): - return list(self.iteritems()) - - def iterkeys(self): - for k in self.data.keys(): - yield to_snake_case(k) - return - - def iteritems(self): - for k in self.iterkeys(): - yield k, self[k] - - def __repr__(self): - return dict(self.iteritems()).__repr__() - - -class LazyMap(object): - def __init__(self, origin, _map, state=None): - self._origin = origin - self._origin_iter = origin.__iter__() - self._state = state or [] - self._finished = False - self._map = _map - - def __iter__(self): - return self if not self._finished else iter(self._state) - - def iter(self): - return self.__iter__() - - def __len__(self): - return self._origin.__len__() - - def __next__(self): - try: - n = next(self._origin_iter) - n = self._map(n) - except StopIteration as e: - self._finished = True - raise e - else: - self._state.append(n) - return n - - def next(self): - return self.__next__() - - def __getitem__(self, key): - item = self._origin.__getitem__(key) - if isinstance(key, slice): - return LazyMap(item, self._map) - return self._map(item) - - def __getattr__(self, name): - return getattr(self._origin, name) - - def __repr__(self): - return "" % repr(self._origin) diff --git a/graphene/utils/__init__.py b/graphene/utils/__init__.py new file mode 100644 index 00000000..68022865 --- /dev/null +++ b/graphene/utils/__init__.py @@ -0,0 +1,7 @@ +from .str_converters import to_camel_case, to_snake_case +from .proxy_snake_dict import ProxySnakeDict +from .caching import cached_property, memoize +from .lazymap import LazyMap + +__all__ = ['to_camel_case', 'to_snake_case', 'ProxySnakeDict', + 'cached_property', 'memoize', 'LazyMap'] diff --git a/graphene/utils/caching.py b/graphene/utils/caching.py new file mode 100644 index 00000000..b0a77df5 --- /dev/null +++ b/graphene/utils/caching.py @@ -0,0 +1,35 @@ +from functools import wraps + + +class CachedPropery(object): + """ + A property that is only computed once per instance and then replaces itself + with an ordinary attribute. Deleting the attribute resets the property. + Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 + """ # noqa + + def __init__(self, func): + self.__doc__ = getattr(func, '__doc__') + self.func = func + + def __get__(self, obj, cls): + if obj is None: + return self + value = obj.__dict__[self.func.__name__] = self.func(obj) + return value + +cached_property = CachedPropery + + +def memoize(fun): + """A simple memoize decorator for functions supporting positional args.""" + @wraps(fun) + def wrapper(*args, **kwargs): + key = (args, frozenset(sorted(kwargs.items()))) + try: + return cache[key] + except KeyError: + ret = cache[key] = fun(*args, **kwargs) + return ret + cache = {} + return wrapper diff --git a/graphene/utils/lazymap.py b/graphene/utils/lazymap.py new file mode 100644 index 00000000..9c873619 --- /dev/null +++ b/graphene/utils/lazymap.py @@ -0,0 +1,42 @@ +class LazyMap(object): + def __init__(self, origin, _map, state=None): + self._origin = origin + self._origin_iter = origin.__iter__() + self._state = state or [] + self._finished = False + self._map = _map + + def __iter__(self): + return self if not self._finished else iter(self._state) + + def iter(self): + return self.__iter__() + + def __len__(self): + return self._origin.__len__() + + def __next__(self): + try: + n = next(self._origin_iter) + n = self._map(n) + except StopIteration as e: + self._finished = True + raise e + else: + self._state.append(n) + return n + + def next(self): + return self.__next__() + + def __getitem__(self, key): + item = self._origin.__getitem__(key) + if isinstance(key, slice): + return LazyMap(item, self._map) + return self._map(item) + + def __getattr__(self, name): + return getattr(self._origin, name) + + def __repr__(self): + return "" % repr(self._origin) diff --git a/graphene/utils/proxy_snake_dict.py b/graphene/utils/proxy_snake_dict.py new file mode 100644 index 00000000..1a5a0bce --- /dev/null +++ b/graphene/utils/proxy_snake_dict.py @@ -0,0 +1,63 @@ +import collections + +from .str_converters import to_camel_case, to_snake_case + + +class ProxySnakeDict(collections.MutableMapping): + __slots__ = ('data') + + def __init__(self, data): + self.data = data + + def __contains__(self, key): + return key in self.data or to_camel_case(key) in self.data + + def get(self, key, default=None): + try: + return self.__getitem__(key) + except KeyError: + return default + + def __iter__(self): + return self.iterkeys() + + def __len__(self): + return len(self.data) + + def __delitem__(self): + raise TypeError('ProxySnakeDict does not support item deletion') + + def __setitem__(self): + raise TypeError('ProxySnakeDict does not support item assignment') + + def __getitem__(self, key): + if key in self.data: + item = self.data[key] + else: + camel_key = to_camel_case(key) + if camel_key in self.data: + item = self.data[camel_key] + else: + raise KeyError(key, camel_key) + + if isinstance(item, dict): + return ProxySnakeDict(item) + return item + + def keys(self): + return list(self.iterkeys()) + + def items(self): + return list(self.iteritems()) + + def iterkeys(self): + for k in self.data.keys(): + yield to_snake_case(k) + return + + def iteritems(self): + for k in self.iterkeys(): + yield k, self[k] + + def __repr__(self): + return dict(self.iteritems()).__repr__() diff --git a/graphene/utils/str_converters.py b/graphene/utils/str_converters.py new file mode 100644 index 00000000..c275a281 --- /dev/null +++ b/graphene/utils/str_converters.py @@ -0,0 +1,17 @@ +import re + + +# From this response in Stackoverflow +# http://stackoverflow.com/a/19053800/1072990 +def to_camel_case(snake_str): + components = snake_str.split('_') + # We capitalize the first letter of each component except the first one + # with the 'title' method and join them together. + return components[0] + "".join(x.title() for x in components[1:]) + + +# From this response in Stackoverflow +# http://stackoverflow.com/a/1176023/1072990 +def to_snake_case(name): + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() diff --git a/tests/utils/test_utils.py b/tests/utils/test_proxy_snake_dict.py similarity index 62% rename from tests/utils/test_utils.py rename to tests/utils/test_proxy_snake_dict.py index 351166d1..19d08421 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_proxy_snake_dict.py @@ -1,12 +1,4 @@ -from graphene.utils import ProxySnakeDict, to_snake_case - - -def test_snake_case(): - assert to_snake_case('snakesOnAPlane') == 'snakes_on_a_plane' - assert to_snake_case('SnakesOnAPlane') == 'snakes_on_a_plane' - assert to_snake_case('snakes_on_a_plane') == 'snakes_on_a_plane' - assert to_snake_case('IPhoneHysteria') == 'i_phone_hysteria' - assert to_snake_case('iPhoneHysteria') == 'i_phone_hysteria' +from graphene.utils import ProxySnakeDict def test_proxy_snake_dict(): @@ -32,5 +24,3 @@ def test_proxy_snake_dict_as_kwargs(): def func(**kwargs): return kwargs.get('my_data') assert func(**p) == 1 - - diff --git a/tests/utils/test_str_converter.py b/tests/utils/test_str_converter.py new file mode 100644 index 00000000..74940596 --- /dev/null +++ b/tests/utils/test_str_converter.py @@ -0,0 +1,14 @@ +from graphene.utils import to_snake_case, to_camel_case + + +def test_snake_case(): + assert to_snake_case('snakesOnAPlane') == 'snakes_on_a_plane' + assert to_snake_case('SnakesOnAPlane') == 'snakes_on_a_plane' + assert to_snake_case('snakes_on_a_plane') == 'snakes_on_a_plane' + assert to_snake_case('IPhoneHysteria') == 'i_phone_hysteria' + assert to_snake_case('iPhoneHysteria') == 'i_phone_hysteria' + + +def test_camel_case(): + assert to_camel_case('snakes_on_a_plane') == 'snakesOnAPlane' + assert to_camel_case('i_phone_hysteria') == 'iPhoneHysteria'