Skip to content

Instantly share code, notes, and snippets.

@cainmagi
Created February 8, 2024 16:51
Show Gist options
  • Select an option

  • Save cainmagi/1c294f1203ee61528b9b10e838f786aa to your computer and use it in GitHub Desktop.

Select an option

Save cainmagi/1c294f1203ee61528b9b10e838f786aa to your computer and use it in GitHub Desktop.
Solve overloads

Solve overloads

Turn the @overload decorator from typehint-purposed to run-time effective.

The module provides a solve_overloads decorator which is specifically designed for decorating function with overloads. The decorated function will be able to dispatch the input arguments according to the overload versions.

The decorated function will try to bind the input arguments with each overload until a binding is successful. The trial order of the overloads follows the same order of the definition.

Example usage:

from typing_extensions import overload
from solve_overloads import solve_overloads


@overload
def func(val: int): ...


@overload
def func(*vals: int, **kwvals: str): ...


@solve_overloads
def func(*args, **kwargs):
    print(args)
    print(kwargs)

The final printed results:

()
{
    "ver": 1,
    "arguments": {
        "vals": (1, 2, 3),
        "kwvals": {"val": "a", "val2": "b"}
    }
}

MIT License

Copyright (c) 2024 Yuchen Jin

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# -*- coding: UTF-8 -*-
"""
solve_overloads
===============
Author
------
Yuchen Jin
[email protected]
Description
-----------
Turn the `@overload` decorator from typehint-purposed to run-time effective.
The module provides a `solve_overloads` decorator which is specifically designed for
decorating function with overloads. The decorated function will be able to dispatch
the input arguments according to the overload versions.
The decorated function will try to bind the input arguments with each overload until
a binding is successful. The trial order of the overloads follows the same order of
the definition.
"""
import inspect
import functools
from typing import Any, TypeVar, cast
from typing_extensions import Protocol, get_overloads
class ArgsProtocol(Protocol):
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
_ArgsProtocol = TypeVar("_ArgsProtocol", bound=ArgsProtocol)
__all__ = ("solve_overloads",)
def solve_overloads(func: _ArgsProtocol) -> _ArgsProtocol:
"""Decorator for branching overloads.
This method can turn the typehint-purposed `@overload` decorator to an effective
argument validator during the run time.
This decorator is used for decorating any function with overloads. The decorator
will find a wrapped function that tries to bind the provided arguments to each
signature of the overloads until a succesful binding is done. If the arguments
cannot be bounded to any overload, raise a `TypeError`.
Arguments
---------
func: `(*args: Any, **kwargs: Any) -> Any`
The function to be bounded. The prototype of this function needs to contain
`*args` and `**kwargs`. The function also needs to have at least one overload.
Returns
-------
#1: `(*args: Any, **kwargs: Any) -> Any`
The wrapped function. It has totally the same signature and the same
functionality of the original function. However. After the decoration,
the input arguments in the function implementation will be like this:
```python
{
"ver": int,
"arguments": {*args, **kwargs}
}
```
Note that if the overload contains var arguments like `*args` or `**kwargs`,
the values will be compressed as sequences or dictionaries, respectively.
The value `ver` is the version of the overload.
For example,
```python
@overload
def func(val: int): ...
@overload
def func(*vals: int, **kwvals: str): ...
@solve_overloads
def func(*args, **kwargs):
print(args)
print(kwargs)
func(1, 2, 3, val="a", val2="b")
```
The printed results will be
```python
()
{
"ver": 1,
"arguments": {
"vals": (1, 2, 3),
"kwvals": {"val": "a", "val2": "b"}
}
}
```
"""
@functools.wraps(func)
def wrapped(*args, **kwargs):
for idx_ver, func_ver in enumerate(get_overloads(func)):
try:
all_args = inspect.signature(func_ver).bind(*args, **kwargs)
except TypeError:
continue
all_args.apply_defaults()
return func(ver=idx_ver, arguments=all_args.arguments)
raise TypeError(
"The provided arguments do not match any overload of the function."
)
return cast(_ArgsProtocol, wrapped)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment