Skip to content

Commit d7e1d9c

Browse files
committed
Refactored utils
1 parent 3d6041e commit d7e1d9c

File tree

8 files changed

+179
-166
lines changed

8 files changed

+179
-166
lines changed

graphene/utils.py

Lines changed: 0 additions & 155 deletions
This file was deleted.

graphene/utils/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .str_converters import to_camel_case, to_snake_case
2+
from .proxy_snake_dict import ProxySnakeDict
3+
from .caching import cached_property, memoize
4+
from .lazymap import LazyMap
5+
6+
__all__ = ['to_camel_case', 'to_snake_case', 'ProxySnakeDict',
7+
'cached_property', 'memoize', 'LazyMap']

graphene/utils/caching.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from functools import wraps
2+
3+
4+
class CachedPropery(object):
5+
"""
6+
A property that is only computed once per instance and then replaces itself
7+
with an ordinary attribute. Deleting the attribute resets the property.
8+
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
9+
""" # noqa
10+
11+
def __init__(self, func):
12+
self.__doc__ = getattr(func, '__doc__')
13+
self.func = func
14+
15+
def __get__(self, obj, cls):
16+
if obj is None:
17+
return self
18+
value = obj.__dict__[self.func.__name__] = self.func(obj)
19+
return value
20+
21+
cached_property = CachedPropery
22+
23+
24+
def memoize(fun):
25+
"""A simple memoize decorator for functions supporting positional args."""
26+
@wraps(fun)
27+
def wrapper(*args, **kwargs):
28+
key = (args, frozenset(sorted(kwargs.items())))
29+
try:
30+
return cache[key]
31+
except KeyError:
32+
ret = cache[key] = fun(*args, **kwargs)
33+
return ret
34+
cache = {}
35+
return wrapper

graphene/utils/lazymap.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
class LazyMap(object):
2+
def __init__(self, origin, _map, state=None):
3+
self._origin = origin
4+
self._origin_iter = origin.__iter__()
5+
self._state = state or []
6+
self._finished = False
7+
self._map = _map
8+
9+
def __iter__(self):
10+
return self if not self._finished else iter(self._state)
11+
12+
def iter(self):
13+
return self.__iter__()
14+
15+
def __len__(self):
16+
return self._origin.__len__()
17+
18+
def __next__(self):
19+
try:
20+
n = next(self._origin_iter)
21+
n = self._map(n)
22+
except StopIteration as e:
23+
self._finished = True
24+
raise e
25+
else:
26+
self._state.append(n)
27+
return n
28+
29+
def next(self):
30+
return self.__next__()
31+
32+
def __getitem__(self, key):
33+
item = self._origin.__getitem__(key)
34+
if isinstance(key, slice):
35+
return LazyMap(item, self._map)
36+
return self._map(item)
37+
38+
def __getattr__(self, name):
39+
return getattr(self._origin, name)
40+
41+
def __repr__(self):
42+
return "<LazyMap %s>" % repr(self._origin)

graphene/utils/proxy_snake_dict.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import collections
2+
3+
from .str_converters import to_camel_case, to_snake_case
4+
5+
6+
class ProxySnakeDict(collections.MutableMapping):
7+
__slots__ = ('data')
8+
9+
def __init__(self, data):
10+
self.data = data
11+
12+
def __contains__(self, key):
13+
return key in self.data or to_camel_case(key) in self.data
14+
15+
def get(self, key, default=None):
16+
try:
17+
return self.__getitem__(key)
18+
except KeyError:
19+
return default
20+
21+
def __iter__(self):
22+
return self.iterkeys()
23+
24+
def __len__(self):
25+
return len(self.data)
26+
27+
def __delitem__(self):
28+
raise TypeError('ProxySnakeDict does not support item deletion')
29+
30+
def __setitem__(self):
31+
raise TypeError('ProxySnakeDict does not support item assignment')
32+
33+
def __getitem__(self, key):
34+
if key in self.data:
35+
item = self.data[key]
36+
else:
37+
camel_key = to_camel_case(key)
38+
if camel_key in self.data:
39+
item = self.data[camel_key]
40+
else:
41+
raise KeyError(key, camel_key)
42+
43+
if isinstance(item, dict):
44+
return ProxySnakeDict(item)
45+
return item
46+
47+
def keys(self):
48+
return list(self.iterkeys())
49+
50+
def items(self):
51+
return list(self.iteritems())
52+
53+
def iterkeys(self):
54+
for k in self.data.keys():
55+
yield to_snake_case(k)
56+
return
57+
58+
def iteritems(self):
59+
for k in self.iterkeys():
60+
yield k, self[k]
61+
62+
def __repr__(self):
63+
return dict(self.iteritems()).__repr__()

graphene/utils/str_converters.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import re
2+
3+
4+
# From this response in Stackoverflow
5+
# http://stackoverflow.com/a/19053800/1072990
6+
def to_camel_case(snake_str):
7+
components = snake_str.split('_')
8+
# We capitalize the first letter of each component except the first one
9+
# with the 'title' method and join them together.
10+
return components[0] + "".join(x.title() for x in components[1:])
11+
12+
13+
# From this response in Stackoverflow
14+
# http://stackoverflow.com/a/1176023/1072990
15+
def to_snake_case(name):
16+
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
17+
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

tests/utils/test_utils.py renamed to tests/utils/test_proxy_snake_dict.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
from graphene.utils import ProxySnakeDict, to_snake_case
2-
3-
4-
def test_snake_case():
5-
assert to_snake_case('snakesOnAPlane') == 'snakes_on_a_plane'
6-
assert to_snake_case('SnakesOnAPlane') == 'snakes_on_a_plane'
7-
assert to_snake_case('snakes_on_a_plane') == 'snakes_on_a_plane'
8-
assert to_snake_case('IPhoneHysteria') == 'i_phone_hysteria'
9-
assert to_snake_case('iPhoneHysteria') == 'i_phone_hysteria'
1+
from graphene.utils import ProxySnakeDict
102

113

124
def test_proxy_snake_dict():
@@ -32,5 +24,3 @@ def test_proxy_snake_dict_as_kwargs():
3224
def func(**kwargs):
3325
return kwargs.get('my_data')
3426
assert func(**p) == 1
35-
36-

tests/utils/test_str_converter.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from graphene.utils import to_snake_case, to_camel_case
2+
3+
4+
def test_snake_case():
5+
assert to_snake_case('snakesOnAPlane') == 'snakes_on_a_plane'
6+
assert to_snake_case('SnakesOnAPlane') == 'snakes_on_a_plane'
7+
assert to_snake_case('snakes_on_a_plane') == 'snakes_on_a_plane'
8+
assert to_snake_case('IPhoneHysteria') == 'i_phone_hysteria'
9+
assert to_snake_case('iPhoneHysteria') == 'i_phone_hysteria'
10+
11+
12+
def test_camel_case():
13+
assert to_camel_case('snakes_on_a_plane') == 'snakesOnAPlane'
14+
assert to_camel_case('i_phone_hysteria') == 'iPhoneHysteria'

0 commit comments

Comments
 (0)