|
|
|
@ -1,26 +1,34 @@
@@ -1,26 +1,34 @@
|
|
|
|
|
import string |
|
|
|
|
import sys |
|
|
|
|
from functools import wraps |
|
|
|
|
from inspect import isfunction, signature, _empty |
|
|
|
|
from types import FunctionType |
|
|
|
|
from typing import get_type_hints as gth |
|
|
|
|
from typing import TypeVar, _GenericAlias, ForwardRef |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GENERIC_TYPES = {char_: type(char_, (object,), {'__no_type_check__': True}) for char_ in string.ascii_uppercase} |
|
|
|
|
try: |
|
|
|
|
from pytypes import is_of_type |
|
|
|
|
TYPE_CLS = (type, _GenericAlias) |
|
|
|
|
except ImportError: |
|
|
|
|
is_of_type = isinstance |
|
|
|
|
TYPE_CLS = (type,) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_type_hints(x): |
|
|
|
|
""" |
|
|
|
|
base_globals = sys.modules[x.__module__].__dict__ |
|
|
|
|
base_globals.update(GENERIC_TYPES) |
|
|
|
|
return gth(x, globalns=base_globals) |
|
|
|
|
""" |
|
|
|
|
return getattr(x, "__annotations__", {}) |
|
|
|
|
ann = getattr(x, "__annotations__", {}) |
|
|
|
|
|
|
|
|
|
if isinstance(x, type) or isfunction(x): |
|
|
|
|
# Class |
|
|
|
|
base_globals = sys.modules[x.__module__].__dict__ |
|
|
|
|
for k, v in ann.items(): |
|
|
|
|
if isinstance(v, str): |
|
|
|
|
try: |
|
|
|
|
ann[k] = eval(v, base_globals) |
|
|
|
|
except NameError: |
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
if ann: |
|
|
|
|
setattr(x, "__annotations__", ann) |
|
|
|
|
|
|
|
|
|
def hook(o): |
|
|
|
|
if hasattr(o, "__globals__"): |
|
|
|
|
o.__globals__.update(GENERIC_TYPES) |
|
|
|
|
return ann |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _stringify_type(t): |
|
|
|
@ -31,17 +39,22 @@ def _stringify_type(t):
@@ -31,17 +39,22 @@ def _stringify_type(t):
|
|
|
|
|
raise Exception(str(t)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def makeprop(name, typ, val): |
|
|
|
|
def makeprop(name, typ, initial): |
|
|
|
|
def setter(_, val: typ): |
|
|
|
|
if isinstance(typ, type): |
|
|
|
|
assert isinstance(val, typ), \ |
|
|
|
|
assert is_of_type(val, typ), \ |
|
|
|
|
f"Attempted to set property {name} of type {typ.__qualname__}" \ |
|
|
|
|
f" to value of type {type(val).__qualname__}" |
|
|
|
|
|
|
|
|
|
getter._generic_generated = val |
|
|
|
|
props = getattr(_, "__generic_properties", {}) |
|
|
|
|
props[name] = val |
|
|
|
|
setattr(_, "__generic_properties", props) |
|
|
|
|
|
|
|
|
|
def getter(_) -> typ: |
|
|
|
|
val = getter._generic_generated |
|
|
|
|
props = getattr(_, "__generic_properties", {}) |
|
|
|
|
val = props.get(name, initial) |
|
|
|
|
setattr(_, "__generic_properties", props) |
|
|
|
|
|
|
|
|
|
assert val is not GenericWrapper.NO_VALUE, \ |
|
|
|
|
f"Attempted to get property {name} but was never set" |
|
|
|
|
|
|
|
|
@ -51,18 +64,25 @@ def makeprop(name, typ, val):
@@ -51,18 +64,25 @@ def makeprop(name, typ, val):
|
|
|
|
|
return val |
|
|
|
|
|
|
|
|
|
prop = property(getter, setter) |
|
|
|
|
prop.fget._generic_generated = val |
|
|
|
|
return prop |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _remap(r_type, mapping): |
|
|
|
|
if isinstance(r_type, str): |
|
|
|
|
return mapping.get(r_type, r_type) |
|
|
|
|
|
|
|
|
|
if isinstance(r_type, _GenericAlias): |
|
|
|
|
r_type.__args__ = (*( |
|
|
|
|
_remap(x.__forward_arg__ if isinstance(x, ForwardRef) else x, mapping) |
|
|
|
|
for x in r_type.__args__ |
|
|
|
|
),) |
|
|
|
|
return r_type |
|
|
|
|
|
|
|
|
|
if not (hasattr(r_type, "__dict__") and "___ctor" in r_type.__dict__): |
|
|
|
|
return r_type |
|
|
|
|
|
|
|
|
|
ctor = r_type.__dict__['___ctor'] |
|
|
|
|
args = r_type.__dict__['___args'] |
|
|
|
|
args = ctor.args |
|
|
|
|
map = {**r_type.__dict__['___mapping']} |
|
|
|
|
|
|
|
|
|
for g, t in map.items(): |
|
|
|
@ -77,44 +97,51 @@ def _remap(r_type, mapping):
@@ -77,44 +97,51 @@ def _remap(r_type, mapping):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _wrap_func(func, mapping): |
|
|
|
|
func = getattr(func, "__wrapped__", func) |
|
|
|
|
|
|
|
|
|
hook(func) |
|
|
|
|
if get_type_hints(func): # We can do type checking |
|
|
|
|
na = {} |
|
|
|
|
for k, v in get_type_hints(func).items(): |
|
|
|
|
nv = _remap(v, mapping) |
|
|
|
|
na[k] = nv |
|
|
|
|
|
|
|
|
|
sig = signature(func) # Prepare sig |
|
|
|
|
func.__hints_done = False |
|
|
|
|
|
|
|
|
|
if func.__annotations__: # We can do type checking |
|
|
|
|
@wraps(func) |
|
|
|
|
def wrapper(*args, **kwargs): |
|
|
|
|
if not func.__hints_done: |
|
|
|
|
na = {} |
|
|
|
|
for k, v in get_type_hints(func).items(): |
|
|
|
|
nv = _remap(v, mapping) |
|
|
|
|
if isinstance(nv, _GenericAlias): |
|
|
|
|
nv.__qualname__ = nv |
|
|
|
|
na[k] = nv |
|
|
|
|
|
|
|
|
|
func.__annotations__ = na |
|
|
|
|
func.__hints_done = True |
|
|
|
|
|
|
|
|
|
sig = signature(func) # Prepare sig |
|
|
|
|
|
|
|
|
|
# ==== |
|
|
|
|
|
|
|
|
|
bound = sig.bind(*args, **kwargs) # Bind arguments passed |
|
|
|
|
|
|
|
|
|
for k, v in sig.parameters.items(): |
|
|
|
|
if v.kind == v.VAR_POSITIONAL: |
|
|
|
|
# Tuple check |
|
|
|
|
if isinstance(na.get(k, v.annotation), type) and v.annotation is not _empty: |
|
|
|
|
assert all(isinstance(x, na.get(k, v.annotation)) for x in bound.arguments.get(k, [])), \ |
|
|
|
|
if isinstance(v.annotation, TYPE_CLS) and v.annotation is not _empty: |
|
|
|
|
assert all(is_of_type(x, v.annotation) for x in bound.arguments.get(k, [])), \ |
|
|
|
|
(f"Parameter '{k}' was of type '{type(bound.arguments.get(k, [])).__qualname__}', " |
|
|
|
|
f"expected '{na.get(k, v.annotation).__qualname__}'") |
|
|
|
|
f"expected '{v.annotation.__qualname__}'") |
|
|
|
|
|
|
|
|
|
elif v.kind == v.VAR_KEYWORD: |
|
|
|
|
# dict check |
|
|
|
|
print("WARNING: Unable to check var_keyword arguments (i.e. **kwargs)") |
|
|
|
|
else: |
|
|
|
|
if isinstance(na.get(k, v.annotation), type) and v.annotation is not _empty: |
|
|
|
|
assert isinstance(bound.arguments[k], na.get(k, v.annotation)), \ |
|
|
|
|
if isinstance(v.annotation, type) and v.annotation is not _empty: |
|
|
|
|
assert is_of_type(bound.arguments[k], v.annotation), \ |
|
|
|
|
(f"Parameter '{k}' was of type '{type(bound.arguments[k]).__qualname__}', " |
|
|
|
|
f"expected '{na.get(k, v.annotation).__qualname__}'") |
|
|
|
|
f"expected '{v.annotation.__qualname__}'") |
|
|
|
|
|
|
|
|
|
retval = func(*args, **kwargs) |
|
|
|
|
|
|
|
|
|
k = 'return' |
|
|
|
|
if isinstance(na.get(k, sig.return_annotation), type) and sig.return_annotation is not _empty: |
|
|
|
|
assert isinstance(retval, na.get(k, sig.return_annotation)), \ |
|
|
|
|
f"Return type was of type '{type(retval).__qualname__}', expected '{na.get(k, sig.return_annotation).__qualname__}'" |
|
|
|
|
if isinstance(sig.return_annotation, TYPE_CLS) and sig.return_annotation is not _empty: |
|
|
|
|
assert is_of_type(retval, sig.return_annotation), \ |
|
|
|
|
f"Return type was of type '{type(retval).__qualname__}', expected " \ |
|
|
|
|
f"'{sig.return_annotation.__qualname__}'" |
|
|
|
|
|
|
|
|
|
return retval |
|
|
|
|
return wrapper |
|
|
|
@ -132,7 +159,6 @@ class GenericWrapper:
@@ -132,7 +159,6 @@ class GenericWrapper:
|
|
|
|
|
self.cls = cls |
|
|
|
|
self.CACHE[cls.__qualname__] = self.CACHE.get(cls.__qualname__, {}) |
|
|
|
|
self.bases = [*self.cls.__bases__] |
|
|
|
|
hook(self.cls) |
|
|
|
|
self.props = {**get_type_hints(self.cls)} |
|
|
|
|
self.attrs = {**self.cls.__dict__} |
|
|
|
|
self.args = args |
|
|
|
@ -147,7 +173,7 @@ class GenericWrapper:
@@ -147,7 +173,7 @@ class GenericWrapper:
|
|
|
|
|
def __getitem__(self, item): |
|
|
|
|
# Everything below here should happen in a different class or make modifications to another object, |
|
|
|
|
# to prevent it leaking over into other instances |
|
|
|
|
assert isinstance(item, (tuple, type, str)) |
|
|
|
|
assert isinstance(item, (tuple, type, str, TypeVar)) |
|
|
|
|
types = item if isinstance(item, tuple) else (item,) |
|
|
|
|
|
|
|
|
|
if types in self.CACHE[self.cls.__qualname__]: |
|
|
|
@ -167,9 +193,9 @@ class GenericWrapper:
@@ -167,9 +193,9 @@ class GenericWrapper:
|
|
|
|
|
(*obj.bases,), |
|
|
|
|
{**obj.attrs, |
|
|
|
|
"__annotations__": {**obj.props}, |
|
|
|
|
"___args": (*obj.args,), |
|
|
|
|
|
|
|
|
|
"___mapping": mapping, |
|
|
|
|
"_": lambda s, p: getattr(s, '___mapping')[p], |
|
|
|
|
"_": lambda s, p: mapping[p], |
|
|
|
|
"___ctor": obj} |
|
|
|
|
) |
|
|
|
|
self.CACHE[self.cls.__qualname__][types] = cls |
|
|
|
@ -205,9 +231,8 @@ class GenericWrapper:
@@ -205,9 +231,8 @@ class GenericWrapper:
|
|
|
|
|
# only methods |
|
|
|
|
if isfunction(v): |
|
|
|
|
v = getattr(v, "__wrapped__", v) |
|
|
|
|
hook(v) |
|
|
|
|
nf = FunctionType(v.__code__, v.__globals__, v.__qualname__, v.__defaults__, v.__closure__) |
|
|
|
|
nf.__annotations__ = get_type_hints(v) |
|
|
|
|
nf.__annotations__ = v.__annotations__ |
|
|
|
|
self.attrs[k] = _wrap_func(nf, mapping) |
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
@ -218,28 +243,3 @@ def generic(*args: str):
@@ -218,28 +243,3 @@ def generic(*args: str):
|
|
|
|
|
def generic_inner(cls): |
|
|
|
|
return GenericWrapper(cls, args) |
|
|
|
|
return generic_inner |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
@generic("V") |
|
|
|
|
class TypeVar: |
|
|
|
|
prop: "V" |
|
|
|
|
|
|
|
|
|
def get(self) -> "V": |
|
|
|
|
return self.prop |
|
|
|
|
|
|
|
|
|
@generic("T") |
|
|
|
|
class Iterable: |
|
|
|
|
prop2: TypeVar["T"] |
|
|
|
|
|
|
|
|
|
@generic("U", "W") |
|
|
|
|
class List(TypeVar["W"]): |
|
|
|
|
prop3: Iterable[TypeVar["U"]] |
|
|
|
|
|
|
|
|
|
obj = List[int, str]() |
|
|
|
|
obj.prop = '' |
|
|
|
|
obj.get() |
|
|
|
|
|
|
|
|
|
obj2 = List[str, list]() |
|
|
|
|
obj2.prop = [] |
|
|
|
|
obj2.get() |
|
|
|
|