Martmists

generics.py

Dec 10th, 2020
877
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.99 KB | None | 0 0
  1. import sys
  2. from functools import wraps
  3. from inspect import isfunction, signature, _empty
  4. from types import FunctionType
  5. from typing import TypeVar, _GenericAlias, ForwardRef
  6.  
  7. try:
  8.     from pytypes import is_of_type
  9.     TYPE_CLS = (type, _GenericAlias)
  10. except ImportError:
  11.     is_of_type = isinstance
  12.     TYPE_CLS = (type,)
  13.  
  14.  
  15. def get_type_hints(x):
  16.     ann = getattr(x, "__annotations__", {})
  17.  
  18.     if isinstance(x, type) or isfunction(x):
  19.         # Class
  20.         base_globals = sys.modules[x.__module__].__dict__
  21.         for k, v in ann.items():
  22.             if isinstance(v, str):
  23.                 try:
  24.                     ann[k] = eval(v, base_globals)
  25.                 except NameError:
  26.                     pass
  27.  
  28.     if ann:
  29.         setattr(x, "__annotations__", ann)
  30.  
  31.     return ann
  32.  
  33.  
  34. def _stringify_type(t):
  35.     if isinstance(t, str):
  36.         return t
  37.     if isinstance(t, type):
  38.         return t.__qualname__
  39.     raise Exception(str(t))
  40.  
  41.  
  42. def makeprop(name, typ, initial):
  43.     def setter(_, val: typ):
  44.         if isinstance(typ, type):
  45.             assert is_of_type(val, typ), \
  46.                 f"Attempted to set property {name} of type {typ.__qualname__}" \
  47.                 f" to value of type {type(val).__qualname__}"
  48.  
  49.         props = getattr(_, "__generic_properties", {})
  50.         props[name] = val
  51.         setattr(_, "__generic_properties", props)
  52.  
  53.     def getter(_) -> typ:
  54.         props = getattr(_, "__generic_properties", {})
  55.         val = props.get(name, initial)
  56.         setattr(_, "__generic_properties", props)
  57.  
  58.         assert val is not GenericWrapper.NO_VALUE, \
  59.             f"Attempted to get property {name} but was never set"
  60.  
  61.         if isinstance(typ, type):
  62.             assert isinstance(val, typ), \
  63.                 f"Property {name} of type {typ.__qualname__} turned into a value of type {type(val).__qualname__}"
  64.         return val
  65.  
  66.     prop = property(getter, setter)
  67.     return prop
  68.  
  69.  
  70. def _remap(r_type, mapping):
  71.     if isinstance(r_type, str):
  72.         return mapping.get(r_type, r_type)
  73.  
  74.     if isinstance(r_type, _GenericAlias):
  75.         r_type.__args__ = (*(
  76.             _remap(x.__forward_arg__ if isinstance(x, ForwardRef) else x, mapping)
  77.             for x in r_type.__args__
  78.         ),)
  79.         return r_type
  80.  
  81.     if not (hasattr(r_type, "__dict__") and "___ctor" in r_type.__dict__):
  82.         return r_type
  83.  
  84.     ctor = r_type.__dict__['___ctor']
  85.     args = ctor.args
  86.     map = {**r_type.__dict__['___mapping']}
  87.  
  88.     for g, t in map.items():
  89.         if t in mapping:
  90.             map[g] = mapping[t]
  91.         else:
  92.             map[g] = _remap(t, mapping)
  93.  
  94.     items = (map.get(x, x) for x in args)
  95.  
  96.     return ctor[(*items,)]
  97.  
  98.  
  99. def _wrap_func(func, mapping):
  100.     func.__hints_done = False
  101.  
  102.     if func.__annotations__:  # We can do type checking
  103.         @wraps(func)
  104.         def wrapper(*args, **kwargs):
  105.             if not func.__hints_done:
  106.                 na = {}
  107.                 for k, v in get_type_hints(func).items():
  108.                     nv = _remap(v, mapping)
  109.                     if isinstance(nv, _GenericAlias):
  110.                         nv.__qualname__ = nv
  111.                     na[k] = nv
  112.  
  113.                 func.__annotations__ = na
  114.                 func.__hints_done = True
  115.  
  116.             sig = signature(func)  # Prepare sig
  117.  
  118.             # ====
  119.  
  120.             bound = sig.bind(*args, **kwargs)  # Bind arguments passed
  121.  
  122.             for k, v in sig.parameters.items():
  123.                 if v.kind == v.VAR_POSITIONAL:
  124.                     # Tuple check
  125.                     if isinstance(v.annotation, TYPE_CLS) and v.annotation is not _empty:
  126.                         assert all(is_of_type(x, v.annotation) for x in bound.arguments.get(k, [])), \
  127.                             (f"Parameter '{k}' was of type '{type(bound.arguments.get(k, [])).__qualname__}', "
  128.                              f"expected '{v.annotation.__qualname__}'")
  129.  
  130.                 elif v.kind == v.VAR_KEYWORD:
  131.                     # dict check
  132.                     print("WARNING: Unable to check var_keyword arguments (i.e. **kwargs)")
  133.                 else:
  134.                     if isinstance(v.annotation, type) and v.annotation is not _empty:
  135.                         assert is_of_type(bound.arguments[k], v.annotation), \
  136.                             (f"Parameter '{k}' was of type '{type(bound.arguments[k]).__qualname__}', "
  137.                              f"expected '{v.annotation.__qualname__}'")
  138.  
  139.             retval = func(*args, **kwargs)
  140.  
  141.             if isinstance(sig.return_annotation, TYPE_CLS) and sig.return_annotation is not _empty:
  142.                 assert is_of_type(retval, sig.return_annotation), \
  143.                     f"Return type was of type '{type(retval).__qualname__}', expected " \
  144.                     f"'{sig.return_annotation.__qualname__}'"
  145.  
  146.             return retval
  147.         return wrapper
  148.     return func
  149.  
  150.  
  151. class GenericWrapper:
  152.     CACHE = {}
  153.  
  154.     class NO_VALUE:
  155.         pass
  156.  
  157.     def __init__(self, cls, args):
  158.         assert isinstance(cls, type)
  159.         self.cls = cls
  160.         self.CACHE[cls.__qualname__] = self.CACHE.get(cls.__qualname__, {})
  161.         self.bases = [*self.cls.__bases__]
  162.         self.props = {**get_type_hints(self.cls)}
  163.         self.attrs = {**self.cls.__dict__}
  164.         self.args = args
  165.  
  166.     def copy(self):
  167.         new = GenericWrapper(self.cls, (*self.args,))
  168.         new.bases = [*self.bases]
  169.         new.props = {**self.props}
  170.         new.attrs = {**self.attrs}
  171.         return new
  172.  
  173.     def __getitem__(self, item):
  174.         # Everything below here should happen in a different class or make modifications to another object,
  175.         # to prevent it leaking over into other instances
  176.         assert isinstance(item, (tuple, type, str, TypeVar))
  177.         types = item if isinstance(item, tuple) else (item,)
  178.  
  179.         if types in self.CACHE[self.cls.__qualname__]:
  180.             return self.CACHE[self.cls.__qualname__][types]
  181.  
  182.         obj = self.copy()
  183.  
  184.         mapping = {k: v for k, v in zip(self.args, types)}
  185.         mcs = getattr(self.cls, "__metaclass__", type)
  186.  
  187.         obj.remap_props(mapping)
  188.         obj.remap_bases(mapping)
  189.         obj.remap_attrs(mapping)
  190.  
  191.         cls = mcs(
  192.             f"{obj.cls.__name__.split('[')[0]}[{', '.join(_stringify_type(mapping.get(v, v)) for v in obj.args)}]",
  193.             (*obj.bases,),
  194.             {**obj.attrs,
  195.              "__annotations__": {**obj.props},
  196.  
  197.              "___mapping": mapping,
  198.              "_": lambda s, p: mapping[p],
  199.              "___ctor": obj}
  200.         )
  201.         self.CACHE[self.cls.__qualname__][types] = cls
  202.         return cls
  203.  
  204.     def remap_props(self, mapping):
  205.         for k, v in self.props.items():
  206.             # k: prop name
  207.             # v: type
  208.             # mapping: map[old_type => new_type]
  209.             t = _remap(v, mapping)
  210.             if isinstance(t, str):
  211.                 continue
  212.  
  213.             self.props[k] = t
  214.  
  215.             initial_value = self.attrs.get(k, self.NO_VALUE)
  216.             if isinstance(initial_value, property) and hasattr(initial_value.fget, "_generic_generated"):
  217.                 initial_value = initial_value.fget._generic_generated
  218.  
  219.             prop = makeprop(k, t, initial_value)
  220.  
  221.             self.attrs[k] = prop
  222.  
  223.     def remap_bases(self, mapping):
  224.         self.bases = [
  225.             _remap(v, mapping)
  226.             for v in self.bases
  227.         ]
  228.  
  229.     def remap_attrs(self, mapping):
  230.         for k, v in self.attrs.items():
  231.             # only methods
  232.             if isfunction(v):
  233.                 v = getattr(v, "__wrapped__", v)
  234.                 nf = FunctionType(v.__code__, v.__globals__, v.__qualname__, v.__defaults__, v.__closure__)
  235.                 nf.__annotations__ = v.__annotations__
  236.                 self.attrs[k] = _wrap_func(nf, mapping)
  237.  
  238.     def __repr__(self):
  239.         return f"<untyped generic class '{self.cls.__module__}.{self.cls.__qualname__}'>"
  240.  
  241.  
  242. def generic(*args: str):
  243.     def generic_inner(cls):
  244.         return GenericWrapper(cls, args)
  245.     return generic_inner
  246.  
Advertisement
Add Comment
Please, Sign In to add comment