Skip to content

Commit 1f0752b

Browse files
committed
Contents transferred from valid8. Namespaces fixed so that code generation and tests work.
1 parent b453847 commit 1f0752b

File tree

10 files changed

+2223
-0
lines changed

10 files changed

+2223
-0
lines changed

code_generation/Readme

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The .mako file is a mako template. You might wish to associate this file extension with the python source file type,
2+
if your IDE does not support mako (like me), so that at least you get some syntax highlighting.
Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
from sys import getsizeof
2+
from typing import Tuple, Set, Optional, Any
3+
4+
import os
5+
6+
# from quik import Template
7+
from mako import exceptions
8+
from mako.template import Template
9+
10+
# from sortedcontainers import SortedSet
11+
from ordered_set import OrderedSet
12+
13+
from autoclass import autoclass
14+
from enforce import runtime_validation
15+
16+
17+
@runtime_validation
18+
@autoclass
19+
class Override:
20+
def __init__(self, method_name: str,
21+
# we cannot use Callable, see https://github.com/RussBaz/enforce/issues/58
22+
unbound_method: Optional[Any] = None,
23+
operator: Optional[str] = None, is_operator_left: bool = True,
24+
self_operator: Optional[str] = None):
25+
"""
26+
A descriptor for a method to override in _InputEvaluatorGenerated.
27+
* If only method_name is provided, the overriden method adds itself to the stack
28+
(see StackableFunctionEvaluator)
29+
* If unbound_method is provided, the overriden method adds the provided unbound method to the stack
30+
* If operator and is_operator_left are provided, the overriden method adds a method performing
31+
(args <operator> x) if is_operator_left=False or (x <operator> args) if is_operator_left=True
32+
* If self_operator is provided, the overriden method adds a method performing <operator> x
33+
34+
:param method_name: the method name
35+
:param unbound_method:
36+
:param operator: for pairwise operators e.g. a * b
37+
:param is_operator_left:
38+
:param self_operator: for self-operator e.g. -x
39+
"""
40+
pass
41+
42+
def __hash__(self):
43+
return hash(self.method_name)
44+
45+
def __str__(self):
46+
if self.self_operator:
47+
return '{}: Self operator {}'.format(self.method_name, self.self_operator)
48+
elif self.operator:
49+
return '{}: Pairwise operator {} {}'.format(self.method_name, self.operator,
50+
'left' if self.is_operator_left else 'right')
51+
elif self.unbound_method:
52+
return '{}: Unbound method {}'.format(self.method_name, self.unbound_method.__name__)
53+
else:
54+
return '{}: Standard default'.format(self.method_name)
55+
56+
57+
@runtime_validation
58+
@autoclass
59+
class OverExc:
60+
def __init__(self, method_name: str,
61+
module_method_name: Optional[str] = None,
62+
# we cannot use Callable, see https://github.com/RussBaz/enforce/issues/58
63+
unbound_method: Optional[Any] = None):
64+
"""
65+
A descriptor for a method to override with exception in _InputEvaluatorGenerated, and for which a module-level
66+
replacement method needs to be provided.
67+
68+
The method_name will raise an exception and indicate that module_method_name is the replacement method. The
69+
latter will implement by adding method_name to the stack (see StackableFunctionEvaluator). If unbound_method is
70+
provided, the module method will add unbound_method to the stack instead of method_name.
71+
72+
By default module_method_name is equal to a capitalized, underscores-removed, version of the method name.
73+
For example __bytes__ become Bytes
74+
75+
:param method_name: the method name
76+
:param module_method_name:
77+
:param unbound_method:
78+
"""
79+
# this is executed AFTER @autoargs
80+
self.module_method_name = module_method_name or method_name.replace('__', '').capitalize()
81+
82+
def __hash__(self):
83+
return hash(self.method_name)
84+
85+
def __str__(self):
86+
return 'Exception {} replaced by module method {}'.format(self.method_name, self.module_method_name)
87+
88+
89+
def generate_code():
90+
"""
91+
This method reads the template file, fills it with the appropriate contents, and writes the result in the
92+
mini_lambda_generated.py file. All contents of the destination file are overriden by this operation.
93+
:return:
94+
"""
95+
96+
# generate the to-do list
97+
to_override, to_override_with_exception = define_what_needs_to_be_written()
98+
# check outside of the template that it works:
99+
for o in to_override:
100+
print(o)
101+
for o in to_override_with_exception:
102+
print(o)
103+
104+
# open the mako template file
105+
THIS_DIR = os.path.dirname(__file__)
106+
template_file = os.path.join(THIS_DIR, 'mini_lambda_template.mako')
107+
with open(template_file) as f:
108+
body = f.read()
109+
110+
try:
111+
# create the template
112+
temp = Template(text=body) # , module_directory=os.path.join(THIS_DIR, 'tmp'))
113+
114+
# fill it with the variables contents
115+
res = temp.render(to_override=to_override, to_override_with_exception=to_override_with_exception)
116+
117+
# write the result to the destination file
118+
dest_file = os.path.join(THIS_DIR, os.pardir, 'mini_lambda', 'generated.py')
119+
with open(dest_file, 'wt') as f:
120+
f.write(res)
121+
except:
122+
# mako user-friendly exception display
123+
print(exceptions.text_error_template().render())
124+
125+
126+
def __get_all_magic_methods(*classes):
127+
"""
128+
Helper method to return all magic methods in a given type. We do not use it anymore in the final code, but good for
129+
debug
130+
:param classes:
131+
:return:
132+
"""
133+
return {name for clazz in classes for name in dir(clazz) if name.startswith('__')}
134+
135+
136+
def define_what_needs_to_be_written() -> Tuple[Set[Override], Set[OverExc]]:
137+
"""
138+
Creates three sets containing the definition of what we want to write as methods in the generated class.
139+
:return: a tuple of two sorted sets. The first set contains Override definitions, the second one OverExc
140+
definitions
141+
"""
142+
143+
# init containers
144+
to_override = OrderedSet()
145+
to_override_with_exception = OrderedSet()
146+
to_skip = set()
147+
148+
# ** Base **
149+
# .__class__, .__mro__
150+
# .__doc__, .__name__, __module__, .__dict__
151+
to_skip.update({'__class__', '__mro__', '__doc__', '__name__', '__module__', '__dict__'})
152+
153+
# ** Iterable **
154+
# .__iter__
155+
# to_override.update(__get_all_magic_methods(Iterable))
156+
# Actually this COULD work but creates infinite loops when a list comprehension is used in the expression [i for i in x]
157+
# so we prefer to raise an exception and tell users that list comprehensions are forbidden
158+
# to_skip.update({'__iter__'})
159+
to_override_with_exception.update({OverExc('__iter__')})
160+
161+
# ** Iterator and Generator **
162+
# .__next__
163+
# to_override.update(__get_all_magic_methods(Iterator, Generator))
164+
to_override.add(Override('__next__', unbound_method=next))
165+
166+
# ** Initializable Object **
167+
# .__new__, .__init__, .__del__
168+
to_skip.update({'__new__', '__init__', '__del__'})
169+
170+
# ** Representable Object **
171+
# .__repr__, .__str__, .__bytes__, .__format__,
172+
# __sizeof__
173+
to_override_with_exception.update({OverExc('__str__', unbound_method=str),
174+
OverExc('__repr__', unbound_method=repr),
175+
OverExc('__bytes__', unbound_method=bytes),
176+
OverExc('__format__', unbound_method=format),
177+
OverExc('__sizeof__', unbound_method=getsizeof)})
178+
179+
# ** Comparable Objects **
180+
# .__lt__, .__le__, .__eq__, .__ne__, .__gt__, .__ge__
181+
# to_override.update(__get_all_magic_methods(Set))
182+
to_override.update({Override('__lt__', operator='<'),
183+
Override('__le__', operator='<='),
184+
Override('__eq__', operator='=='),
185+
Override('__ne__', operator='!='),
186+
Override('__gt__', operator='>'),
187+
Override('__ge__', operator='>=')})
188+
189+
# ** Hashable Object **
190+
# .__hash__
191+
# to_override.update(__get_all_magic_methods(Hashable))
192+
to_override_with_exception.update({OverExc('__hash__')})
193+
194+
# ** Truth-testable Object **
195+
# .__bool__
196+
to_override_with_exception.update({OverExc('__bool__')})
197+
198+
# ** Object = Field container **
199+
# .__getattribute__ (to avoid)
200+
# .__getattr__,.__setattr__, .__delattr__
201+
# .__dir__
202+
# .__slots__
203+
to_skip.update({'__getattribute__', '__setattr__', '__delattr__', '__dir__', '__slots__'})
204+
to_override.add(Override('__getattr__', unbound_method=getattr))
205+
206+
# ** Object Descriptors **
207+
# .__get__ , .__set__, .__delete__, .__set_name__
208+
# to_override.update({'__get__'})
209+
to_skip.update({'__get__', '__set__', '__delete__', '__set_name__'})
210+
211+
# ** Callable **
212+
# .__call__
213+
# to_override.update(__get_all_magic_methods(Callable))
214+
to_override.add(Override('__call__'))
215+
216+
# ** Class **
217+
# .__instancecheck__, .__subclasscheck__
218+
# .__init_subclass__
219+
# .__subclasshook__, .__abstractmethods__
220+
# IMPOSSIBLE TO OVERRIDE: these 2 methods are CLASS methods, carried by the SECOND argument, not the first.
221+
# so isintance(x, int) calls __instancecheck__ on int, not on x !
222+
to_skip.update({'__instancecheck__', '__subclasscheck__'})
223+
to_skip.update({'__init_subclass__', '__subclasshook__', '__abstractmethods__'})
224+
225+
# ** Container **
226+
# .__contains__
227+
# to_override.update(__get_all_magic_methods(Container))
228+
to_skip.update({'__contains__'})
229+
230+
# ** Sized Container **
231+
# .__len__, .__length_hint__
232+
to_override_with_exception.add(OverExc('__len__'))
233+
234+
# ** Iterable Container : see Iterable **
235+
# ** Reversible Container **
236+
# .__reversed__,
237+
# to_override.update(__get_all_magic_methods(Reversible))
238+
to_override.add(Override('__reversed__', unbound_method=reversed))
239+
240+
# ** Subscriptable / Mapping Container **
241+
# .__getitem__, .__missing__, .__setitem__, .__delitem__,
242+
# to_override.update(__get_all_magic_methods(Mapping))
243+
to_override.update({Override('__getitem__'),
244+
Override('__missing__')})
245+
to_skip.update({'__setitem__', '__delitem__'})
246+
247+
# ** Numeric types **
248+
# .__add__, .__radd__, .__sub__, .__rsub__, .__mul__, .__rmul__, .__truediv__, .__rtruediv__,
249+
# .__mod__, .__rmod__, .__divmod__, .__rdivmod__, .__pow__, .__rpow__
250+
# .__matmul__, .__floordiv__, .__rfloordiv__
251+
# .__lshift__, .__rshift__, __rlshift__, __rrshift__
252+
# .__neg__, .__pos__, .__abs__, .__invert__
253+
# to_override.update(__get_all_magic_methods(Integral))
254+
to_override.update({Override('__add__', operator='+'),
255+
Override('__radd__', operator='+', is_operator_left=False),
256+
Override('__sub__', operator='-'),
257+
Override('__rsub__', operator='-', is_operator_left=False),
258+
Override('__mul__', operator='*'),
259+
Override('__rmul__', operator='*', is_operator_left=False),
260+
Override('__truediv__', operator='/'),
261+
Override('__rtruediv__', operator='/', is_operator_left=False),
262+
Override('__mod__', operator='%'),
263+
Override('__rmod__', operator='%', is_operator_left=False),
264+
Override('__divmod__'), # TODO , unbound_method=divmod but how
265+
Override('__rdivmod__'),
266+
Override('__pow__', operator='**'),
267+
Override('__rpow__', operator='**', is_operator_left=False),
268+
Override('__matmul__', operator='@'),
269+
# Override('__rmatmul__', operator='@', is_operator_left=False),
270+
Override('__floordiv__', operator='//'),
271+
Override('__rfloordiv__', operator='//', is_operator_left=False),
272+
Override('__lshift__', operator='<<'),
273+
Override('__rlshift__', operator='<<', is_operator_left=False),
274+
Override('__rshift__', operator='>>'),
275+
Override('__rrshift__', operator='>>', is_operator_left=False),
276+
Override('__rshift__', operator='>>'),
277+
Override('__rshift__', operator='>>'),
278+
Override('__neg__', self_operator='-'),
279+
Override('__pos__', self_operator='+'),
280+
Override('__abs__', unbound_method=abs),
281+
Override('__invert__', self_operator='~'),
282+
})
283+
284+
285+
# ** Boolean types **
286+
# .__and__, .__xor__, .__or__, __rand__, __rxor__, __ror__
287+
to_skip.update({'__and__', '__xor__', '__or__', '__rand__', '__rxor__', '__ror__'})
288+
289+
# ** Type conversion **
290+
# __int__, __long__, __float__, __complex__, __oct__, __hex__, __index__, __trunc__, __coerce__
291+
to_override.update({Override('__trunc__'),
292+
Override('__coerce__')})
293+
to_skip.update({'__index__'})
294+
to_override_with_exception.update({OverExc('__int__', unbound_method=int),
295+
# OverExc('__long__', unbound_method=long),
296+
OverExc('__float__', unbound_method=float),
297+
OverExc('__complex__', unbound_method=complex),
298+
OverExc('__oct__', unbound_method=oct),
299+
OverExc('__hex__', unbound_method=hex),
300+
# ('Index', '__index__', None)
301+
})
302+
# ** Pickle **
303+
# __reduce__, __reduce_ex__
304+
to_skip.update({'__reduce__', '__reduce_ex__'})
305+
306+
# make sure that the ones noted 'to skip' are not in the other sets to return
307+
to_override_2 = OrderedSet()
308+
for overriden in to_override:
309+
if overriden not in to_skip and overriden not in to_override_with_exception:
310+
assert type(overriden) == Override
311+
to_override_2.add(overriden)
312+
313+
to_override_with_exception_2 = OrderedSet()
314+
for overriden_with_e in to_override_with_exception:
315+
if overriden_with_e not in to_skip:
316+
assert type(overriden_with_e) == OverExc
317+
to_override_with_exception_2.add(overriden_with_e)
318+
319+
return to_override_2, to_override_with_exception_2
320+
321+
322+
if __name__ == '__main__':
323+
generate_code()

0 commit comments

Comments
 (0)