|
| 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