Metadata-Version: 2.3
Name: overloaded
Version: 0.1.0
Summary: Dynamically dispatched overloaded functions
Author: abkwreu
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# Overloaded

Dynamic runtime dispatch for overloaded functions using `@overload`: no need to declare your overloads then implement all the dispatch logic separately!

## Features

This package provides features to dynamically dispatch functions and methods to overloads declared with the `typing.overload` decorator, based on their signatures.

* Uses the `typing.overload` decorator directly

* Supports multiple dispatch

* Supports many kinds of type annotations, including:
  
  * `Any`/`Never`
  
  * Numeric-tower matching (`int` matches `float`)
  
  * Unions
  
  * `Literal`
  
  * Recognized genericized collections
  
  * `TypedDict`

* Fully compatible with Pyright
  
  * Pyright will check the return types of overloads
* Supports Python 3.12+, and tested on CPython, PyPy, and GraalPy.

## Installation

Install using `pip`:

```bash
pip install overloaded
```

## Usage

A function or method can be decorated with the `@overloaded` decorator to mark the function as overloaded. When called, it will dynamically dispatch to registered overloads. The decorator can also be applied to a class, which automatically dispatches all overloaded methods defined in the class.

```py
@overloaded
def overloaded_function(...): ...

class MyClass:
    @overloaded
    def overloaded_method(...): ...

@overloaded
class MyClass:
    def overloaded_method(...): ...
```

_Overloads not shown. See "Example" section for full setup._

Alternatively, for a class, it is possible to inherit from the `Overloaded` class or the `OverloadedMeta` metaclass, providing identical functionality, though these will also carry over to subclasses.

```py
class MyClass(Overloaded): ...

class MyClass(metaclass=OverloadedMeta): ...
```

## Example

```py
from typing import Any, overload
from collections.abc import Iterable, Mapping
from overloaded import overloaded, NoMatchingOverloadError

@overload
def deepmin(item: Mapping) -> Any:
    return min(deepmin(val) for val in item.values())
@overload
def deepmin(item: str | bytes) -> Any:
    return item
@overload
def deepmin(item: Iterable) -> Any:
    return min(deepmin(val) for val in item)
@overload
def deepmin(item: Any) -> Any:
    return item

@overloaded
def deepmin(*args, **kwargs) -> Any:
    raise NoMatchingOverloadError(deepmin)
```

## Dispatch notes

* Overload functions are expected to have executable code.

* The first matching overload will be selected.
  
  * In order for an overload to "match", all required arguments must be passed in the required forms (e.g., positional-only arguments must be passed positionally), and all arguments must be of the types indicated in the function signature.
  
  * If no overloads match, the "implementation" function will be called. It is recommended for this function to raise `NoMatchingOverloadError`.

* Generics
  
  * For heterogeneous tuples, every element of the input tuple will be checked.
  
  * For homogeneous tuples and all other `Collection`s, only the first element of the collection will be checked (for `Mapping`s, this is the first key-value pair).
  
  * Generic `Iterable` and `Callable` type annotations are supported, but these match against _any_ iterable/callable object, respectively.
