Skip to content

Commit f7c1509

Browse files
committed
Moves VariantFunction and VariantMethod class definitions out of closure
1 parent 50cad84 commit f7c1509

File tree

1 file changed

+82
-59
lines changed

1 file changed

+82
-59
lines changed

src/variants/_variants.py

Lines changed: 82 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,87 +6,110 @@
66
__all__ = ['primary']
77

88

9-
def primary(f):
10-
"""
11-
Decorator to register a function that has variant forms.
9+
VARIANT_WRAPPED_ATTRIBUTES = (
10+
'__module__', '__name__', '__doc__', '__qualname__', '__annotations__'
11+
)
1212

13-
Decorate the main form of the function with this decorator, and then
14-
subsequent variants should be declared with the same name as the original
15-
function.
1613

17-
.. example::
14+
def variant_wraps(vfunc, wrapped_attributes=VARIANT_WRAPPED_ATTRIBUTES):
15+
"""Update the variant function wrapper a la ``functools.wraps``."""
16+
f = vfunc.__main_form__
1817

19-
@variants
20-
def myfunc(fpath):
21-
with open(fpath, 'r') as f:
22-
do_something(f.read())
18+
class SentinelObject:
19+
"""A unique sentinel that is not None."""
2320

24-
@myfunc.variant('from_url') as f:
25-
def myfunc(url):
26-
r = requests.get(url)
27-
do_something(r.text)
28-
"""
21+
sentinel = SentinelObject()
2922

30-
class VariantFunction(object):
31-
__doc__ = f.__doc__
23+
for attr in wrapped_attributes:
24+
attr_val = getattr(f, attr, sentinel)
25+
if attr is not sentinel:
26+
setattr(vfunc, attr, attr_val)
3227

33-
def __init__(self):
34-
self._variants = set()
28+
return vfunc
3529

36-
def __call__(self, *args, **kwargs):
37-
return f(*args, **kwargs)
3830

39-
def _add_variant(self, var_name, vfunc):
40-
self._variants.add(var_name)
41-
setattr(self, var_name, vfunc)
31+
class VariantFunction(object):
32+
"""Wrapper class for functions with variant forms."""
4233

43-
def variant(self, func_name):
44-
"""Decorator to add a new variant form to the function."""
45-
def decorator(vfunc):
46-
self._add_variant(func_name, vfunc)
34+
def __init__(self, primary_func):
35+
self._variants = set()
36+
self.__main_form__ = primary_func
4737

48-
return self
38+
def __call__(self, *args, **kwargs):
39+
return self.__main_form__(*args, **kwargs)
4940

50-
return decorator
41+
def _add_variant(self, var_name, vfunc):
42+
self._variants.add(var_name)
43+
setattr(self, var_name, vfunc)
5144

52-
def __get__(self, obj, objtype=None):
53-
# This is necessary to bind instance methods
54-
if obj is not None:
55-
return VariantMethod(self, obj)
45+
def variant(self, func_name):
46+
"""Decorator to add a new variant form to the function."""
47+
def decorator(vfunc):
48+
self._add_variant(func_name, vfunc)
5649

5750
return self
5851

59-
def __repr__(self):
60-
return '<{} {}>'.format(self.__class__.__name__, self.__name__)
52+
return decorator
53+
54+
def __get__(self, obj, objtype=None):
55+
# This is necessary to bind instance methods
56+
if obj is not None:
57+
rv = VariantMethod(self, obj)
58+
return variant_wraps(rv)
59+
60+
return self
61+
62+
def __repr__(self):
63+
return '<{} {}>'.format(self.__class__.__name__, self.__name__)
64+
65+
66+
class VariantMethod(VariantFunction):
67+
"""Wrapper class for methods with variant forms."""
6168

62-
class VariantMethod(VariantFunction):
63-
def __init__(self, variant_func, instance):
64-
self.__instance = instance
65-
self.__name__ = variant_func.__name__
66-
self.__doc__ = variant_func.__doc__
69+
def __init__(self, variant_func, instance):
70+
self.__instance = instance
6771

68-
# Convert existing variants to methods
69-
for vname in variant_func._variants:
70-
vfunc = getattr(variant_func, vname)
71-
vmethod = self._as_bound_method(vfunc)
72+
# Convert existing variants to methods
73+
for vname in variant_func._variants:
74+
vfunc = getattr(variant_func, vname)
75+
vmethod = self._as_bound_method(vfunc)
7276

73-
setattr(self, vname, vmethod)
77+
setattr(self, vname, vmethod)
7478

75-
def __call__(self, *args, **kwargs):
76-
return f(self.__instance, *args, **kwargs)
79+
self.__main_form__ = self._as_bound_method(variant_func.__main_form__)
7780

78-
def _as_bound_method(self, vfunc):
79-
@functools.wraps(vfunc)
80-
def bound_method(*args, **kwargs):
81-
return vfunc(self.__instance, *args, **kwargs)
81+
def _as_bound_method(self, vfunc):
82+
@functools.wraps(vfunc)
83+
def bound_method(*args, **kwargs):
84+
return vfunc(self.__instance, *args, **kwargs)
8285

83-
return bound_method
86+
return bound_method
8487

85-
def _add_variant(self, var_name, vfunc):
86-
self._variants.add(var_name)
87-
setattr(self, var_name, self._as_bound_method(vfunc))
88+
def _add_variant(self, var_name, vfunc):
89+
self._variants.add(var_name)
90+
setattr(self, var_name, self._as_bound_method(vfunc))
8891

89-
f_out = VariantFunction()
90-
f_out.__name__ = f.__name__
92+
93+
def primary(f):
94+
"""
95+
Decorator to register a function that has variant forms.
96+
97+
Decorate the main form of the function with this decorator, and then
98+
subsequent variants should be declared with the same name as the original
99+
function.
100+
101+
.. example::
102+
103+
@variants
104+
def myfunc(fpath):
105+
with open(fpath, 'r') as f:
106+
do_something(f.read())
107+
108+
@myfunc.variant('from_url') as f:
109+
def myfunc(url):
110+
r = requests.get(url)
111+
do_something(r.text)
112+
"""
113+
f_out = variant_wraps(VariantFunction(f))
91114

92115
return f_out

0 commit comments

Comments
 (0)