RussBaz / enforce

Python 3.5+ runtime type checking for integration testing and data validation
543 stars 21 forks source link

Support for `@overload`ed callables #80

Open CallumJHays opened 3 years ago

CallumJHays commented 3 years ago

This is a bit of a tricky one, but suppose you had something like the following:

@overload
def test(a: int, b: float) -> str: ...
@overload
def test(a: List[int], b: List[float]) -> List[str]: ...
@runtime_validation
def test(a, b):
    return ...

It would be cool if the correct overload could be narrowed down to and validated with the arguments at runtime.

There has been some discussion in the official typing repo on how to accomplish this: https://github.com/python/typing/issues/711

The gist is to inject a custom @overload implementation. I imagine there'd be work in tricking intellisense servers into thinking it's the original.

ken-morel commented 1 month ago

Sure the logic in my packege pyoload will help. We could:

pyoload uses the first a thus supports overloads with different parameter list length and further recursive argument casting... but I won't expose on that, imaging

Bit of clone of pyoload


def get_name(obj):
    return obj.__module__ + '.' + obj.__qualname__

overloads = {}

class Overload:
    @classmethod
    def overload(cls, func):
        name = get_name(func)
        if not name in overloads:
            overloads[name] = []
        overloads[name].append(runtime_check(func))

    def __init__(self):
        self.entries = []

    def __call__(self, *args, **kw):
        for func in self.entries:
            try:
                result = func(*args, **kw)
            except:
                pass
            else:
                return ret
        else:
            raise OverloadError(...)

@Overload.overload
def myfunc(d:dict[str, tuple[int]]): ...

@Overload.overload
def myfunc(c: int): ...

@Overload.overload
def myfunc(a: str): ...