Browse Source

IT FUCKING WORKS

master
Martmists 2 years ago
parent
commit
e902dcc87e
  1. 25
      generics/generic_types.py
  2. 140
      generics/generics.py
  3. 7
      generics/test.py
  4. 4
      requirements.txt

25
generics/generic_types.py

@ -1,4 +1,7 @@ @@ -1,4 +1,7 @@
#from __future__ import annotations
from __future__ import annotations
from typing import Union
from generics import generic
@ -16,12 +19,15 @@ class Iterable: @@ -16,12 +19,15 @@ class Iterable:
@generic("T")
class ListIterator(Iterator["T"]):
def __init__(self, lst: list):
def __init__(self, lst):
self._itr = iter(lst)
def __next__(self) -> "T":
return next(self._itr)
def __iter__(self):
return self._itr
@generic("T")
class List(Iterable["T"]):
@ -32,11 +38,14 @@ class List(Iterable["T"]): @@ -32,11 +38,14 @@ class List(Iterable["T"]):
return ListIterator[self._("T")](self._lst)
# TODO: Union support
def __setitem__(self, key: int, value: "T"):
def __setitem__(self, key: Union[int, slice], value: Union["T", List["T"]]):
self._lst.__setitem__(key, value)
def __getitem__(self, key: int) -> "T":
return self._lst.__getitem__(key)
def __getitem__(self, key: Union[int, slice]) -> Union["T", List["T"]]:
res = self._lst.__getitem__(key)
if isinstance(key, int):
return res
return List[self._("T")](res)
def append(self, obj: "T"):
self._lst.append(obj)
@ -44,13 +53,13 @@ class List(Iterable["T"]): @@ -44,13 +53,13 @@ class List(Iterable["T"]):
def clear(self):
self._lst.clear()
def copy(self) -> 'List["T"]':
def copy(self) -> List["T"]:
return List[self._("T")](self._lst.copy())
def count(self, value: "T") -> int:
return self._lst.count(value)
def extend(self, iterable):
def extend(self, iterable: Iterable["T"]):
self._lst.extend(iterable)
def index(self, val: "T", start: int = 0, stop: int = 9223372036854775807) -> int:
@ -68,7 +77,7 @@ class List(Iterable["T"]): @@ -68,7 +77,7 @@ class List(Iterable["T"]):
def __repr__(self):
return repr(self._lst)
def __add__(self, other: 'List["T"]') -> 'List["T"]':
def __add__(self, other: List["T"]) -> List["T"]:
return List[self._("T")](self._lst + other._lst)

140
generics/generics.py

@ -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()

7
generics/test.py

@ -5,9 +5,14 @@ def main(): @@ -5,9 +5,14 @@ def main():
x = List[Pair[int, str]]()
x.append(Pair[int, str](1, "2"))
x.append(Pair[int, str](3, "4"))
x[1:] = List[Pair[int, str]]([
Pair[int, str](5, "6"),
Pair[int, str](7, "8")
])
print(x)
x.append(Pair[int, int](5, 2))
x.append(6) # <= error
if __name__ == "__main__":
main()

4
requirements.txt

@ -1 +1,3 @@ @@ -1 +1,3 @@
requests
requests~=2.25.0
Pillow~=8.0.1
pytypes~=1.0b5
Loading…
Cancel
Save