Skip to content
Merged

Dev #48

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/workflows/python-tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ jobs:
with:
python-version: '3.13'

- name: Set up Python 3.12
uses: actions/setup-python@v6
with:
python-version: '3.12'

- name: Install dependencies
run: |
pip install --upgrade pip
Expand Down
122 changes: 39 additions & 83 deletions HowTo.md
Original file line number Diff line number Diff line change
@@ -1,106 +1,62 @@
# How to write cleaner Pythoncode with `strongtyping-pyoverload`
# How to write cleaner Python code with `strongtyping-pyoverload`

- With starting of Type-Hints in Python we can now better define what input we expect
we can say we only want to get a specific kind of Type, or we allow multiple once
```python
def func_a(a: int):
...
Python's flexibility is one of its greatest strengths, but as your codebase grows, managing complex function logic based on varying input types can quickly turn into a messy web of `if isinstance(...)` checks.

# in python 3.10 we can also write `str | int` instead of Union
def func(a: Union[str, int]):
...
```
- the same works on class level too
```python
class Foo:
def func_a(self, a: list):
...

# in python 3.10 we can also write `str | int` instead of Union
def func(self, a: Union[list, tuple]):
if isinstance(a, list):
...
if isinstance(a, tuple):
...
```
- wouldn't it not be nice to have a dedicated method for each parameter without renaming it
```python
class Foo:
def func(self, a: str):
print("Called with `str`")
What if you could write cleaner, more expressive code by defining multiple versions of the same function, each tailored to specific types?

def func(self, a: list):
print("Called with `list`")
### ✨ The Solution: Elegant Overloading
With `strongtyping-pyoverload`, you can separate these concerns into distinct, beautifully typed methods. The decorator handles the dispatching logic at runtime, ensuring the right code runs for the right data.

def func(self, a: tuple):
print("Called with `tuple`")
```
- Python will raise no error if you do this but if you call the function
```python
>>> foo = Foo()
>>> foo.func([1, 2, 3])
"Called with `tuple`"
>>> foo.func((1, 2, 3))
"Called with `tuple`"
```
- Python will always use the latest definition for both cases
- This is where the `overload` decorator from `strongtyping-pyoverload` comes into play
```python
from strongtyping_pyoverload import overload


class Foo:

class DataProcessor:
@overload
def func(self, a: str):
print("Called with `str`")
def process(self, data: str):
return data.upper()

@overload
def func(self, a: list):
print("Called with `list`")
def process(self, data: list):
return [item * 2 for item in data]

@overload
def func(self, a: tuple):
print("Called with `tuple`")
def process(self, data: MyPydanticModel):
return data.model_dump()
```

### 🛡️ Native Pydantic Integration
Stop manually validating dictionaries. You can define overloads that take Pydantic models. If you pass a dictionary that matches a model's schema, `strongtyping-pyoverload` can automatically validate it and dispatch to the correct handler.

>>> foo = Foo()
>>> foo.func("hello")
"Called with `str`"
>>> foo.func(list("hello"))
"Called with `list`"
>>> foo.func(tuple("hello"))
"Called with `tuple`"
```
- The same works on module level too
```python
# module_a.py
from pydantic import BaseModel
from strongtyping_pyoverload import overload

class UserCreate(BaseModel):
name: str
age: int

class UserHandler:
@overload
def process(self, data: UserCreate):
return f"Creating user {data.name}"

@overload
def module_func():
return 0
@overload
def process(self, data: dict):
return "Processing raw dictionary"

handler = UserHandler()
# Automatically validates dict against UserCreate schema
print(handler.process({"name": "Alice", "age": 30})) # Output: Creating user Alice
```

@overload
def module_func(a: int, b: int):
return a * b
### 🧬 Deep Inheritance & Mixin Support
It plays well with others. Whether you're using deep class hierarchies or mixing in functionality from multiple sources, the `overload` decorator respects the Method Resolution Order (MRO), ensuring that the most specific implementation is always found.

### 🤖 AI-Ready Code
By utilizing `__signature__` and `__annotations__` metadata, this library makes your code more "readable" for AI coding assistants and modern IDEs. Your tools will understand exactly which version of a function is being called, providing better autocompletion and insights.

@overload
def module_func(a: str, b: str):
return a + b
### ⚡ High Performance
The library uses a structured registry and optimized lookup logic to ensure that the overhead of runtime dispatching is kept to an absolute minimum.

...
# module_b.py
from module_a import module_func
>>> module_func()
0
>>> module_func(2, 2)
4
>>> module_func("foo", "bar")
"foobar"
```
- I think this will help to write cleaner code as a function now will only be called if the parameters are matching
- Otherwise you will get an `AttributeError`
### 🐍 Support for Modern Python Features
Full support for `typing.Annotated`, `Keyword-Only` parameters, and Python 3.13+. It’s built for the modern Python ecosystem.
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# strongtyping-pyoverload
[![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)](https://www.python.org/downloads/release/python-3110/)
[![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3120/)
[![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/release/python-3130/)
[![Python 3.14](https://img.shields.io/badge/python-3.14-blue.svg)](https://www.python.org/downloads/release/python-3140/)
![Python application](https://github.com/FelixTheC/py-overload/workflows/Python%20application/badge.svg)
Expand All @@ -10,10 +8,30 @@
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![AI Agents](https://img.shields.io/badge/AI_Agents-SKILL.md-blue?logo=robotframework&logoColor=white)](SKILL.md)

## A Runtime method overload decorator which add overloading capacity similar to C++
- there is a `override` decorator from `typing` which works only for static type checking
- this decorator works on `runtime`
## Runtime method overloading for Python

`strongtyping-pyoverload` provides a powerful `overload` decorator that brings true runtime method overloading to Python, similar to C++.

## Documentation can be found here
### [readthedocs](https://strongtyping-pyoverload.readthedocs.io/en/latest/)
### Key Features
- **Native Pydantic Integration**: Automatically validate and dispatch based on Pydantic models.
- **Deep Inheritance & Mixin Support**: Respects Method Resolution Order (MRO) for complex class hierarchies.
- **AI-Ready Metadata**: Sets `__signature__` and `__annotations__` for better IDE and AI assistant support.
- **High Performance**: Optimized lookup logic with caching for minimal overhead.
- **Modern Python**: Full support for `typing.Annotated`, keyword-only parameters, and Python 3.13+.

### Quick Start
```python
from strongtyping_pyoverload import overload

class DataProcessor:
@overload
def process(self, data: str):
return data.upper()

@overload
def process(self, data: list):
return [item * 2 for item in data]
```

## Documentation
Full documentation can be found at [readthedocs](https://strongtyping-pyoverload.readthedocs.io/en/latest/).
8 changes: 8 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.4.4
- Native Pydantic Integration: Automatically validate and dispatch based on Pydantic models.
- Performance Optimization: Implemented optimized registry and lookup logic with caching.
- AI-Ready Metadata: Added `__signature__` and `__annotations__` to overloaded functions for better IDE/AI support.
- Deep Inheritance & Mixin Support: Properly respect MRO during dispatching.
- Modern Python: Added support for `typing.Annotated` and keyword-only parameters.
- Support for Python 3.13 and 3.14.

## 0.3.0
- support *args and **kwargs
- extend Documentation
Expand Down
63 changes: 37 additions & 26 deletions docs/class_level.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,43 +30,54 @@ Called with `tuple`
```

### Subclasses/Inheritance
can overwrite an existing method __but__ these __must match the exact type definition__ of the __original method__
The `overload` decorator respects the Method Resolution Order (MRO), allowing you to extend or override functionality in subclasses.

```python
from strongtyping_pyoverload import overload

class Base:
@overload
def process(self, x: int):
return f"Base int: {x}"

class Example:
class Derived(Base):
@overload
def other_func(self):
return 0
def process(self, x: str):
return f"Derived str: {x}"

class SubDerived(Derived):
@overload
def other_func(self, a: int, b: int):
return (a * a) / b
def process(self, x: float):
return f"SubDerived float: {x}"

obj = SubDerived()
print(obj.process(1)) # Base int: 1
print(obj.process("hi")) # Derived str: hi
print(obj.process(1.5)) # SubDerived float: 1.5
```

class Other(Example):
### Mixins
You can also combine functionality from multiple mixin classes.

```python
class Mixin1:
@overload
def other_func(self, a):
return a ** a + a
def handle(self, x: int):
return f"Mixin1: {x}"

class Mixin2:
@overload
def other_func(self, a: int, b: int): # the parameters and everything are exact the same
return ((a * a) / b) + a
```
```pycon
>>> example = Example()
>>> example.other_func(2, 3)
1.333333333333333
>>>
>>> other = Other()
>>> other.other_func()
0
>>> other.other_func(2)
6
>>> other.other_func(2, 3)
3.333333333333333
def handle(self, x: str):
return f"Mixin2: {x}"

class Combined(Mixin1, Mixin2):
@overload
def handle(self, x: float):
return f"Combined: {x}"

obj = Combined()
print(obj.handle(1)) # Mixin1: 1
print(obj.handle("hi")) # Mixin2: hi
```

### A type hint for each parameter??
Expand Down Expand Up @@ -130,7 +141,7 @@ class Other:
```

### No function matches
when no function matches an `AttributError` will be raised
When no function matches an `AttributeError` will be raised.
```python
from strongtyping_pyoverload import overload

Expand All @@ -145,5 +156,5 @@ class Example:
>>> example.other_func("Not", "Supported")
Traceback (most recent call last):
...
AttributeError: `Example` has no function which matches with your parameters `('Not', 'Supported')`
AttributeError: `Example` has no function which matches with your parameters `('Not', 'Supported'), {}`
```
Loading
Loading