-
Notifications
You must be signed in to change notification settings - Fork 182
refactor: Replace ad-hoc dispatch with custom @singledispatch
#3410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+235
−124
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
b0df54e
refactor: Replace `_same_supertype` with a custom `@singledispatch`
dangotbanned c544b10
refactor: Just use a real class
dangotbanned 02b7811
fix(typing): Satisfy `mypy`
dangotbanned 56d3459
fix: Oops forgot the first element
dangotbanned ab71793
refactor(typing): Use slightly better names
dangotbanned a58d070
chore: Rename `default` -> `upper_bound`
dangotbanned c4c21f8
docs: Replace debugging doc
dangotbanned e4f7bf1
docs: More cleanup
dangotbanned 01fbf85
refactor: Use `__slots__`, remove a field
dangotbanned d88d50b
docs: More, more cleanup
dangotbanned 659f5c7
docs: lil bit of `.register` progress
dangotbanned 2b01b2b
cov
dangotbanned 5450652
test: Get full coverage for `@just_dispatch`
dangotbanned 238f069
chore: Give it a simple repr
dangotbanned 301f537
test: Oops, forgot that was an override
dangotbanned 22c8029
Merge remote-tracking branch 'upstream/dtypes/supertyping' into dtype…
dangotbanned bc39c72
Merge branch 'dtypes/supertyping' into dtypes/supertyping-dispatch
dangotbanned 3fc33c6
Merge remote-tracking branch 'upstream/dtypes/supertyping' into dtype…
dangotbanned 14f81fc
revert: Keep only what is required
dangotbanned e4c6657
Merge remote-tracking branch 'upstream/dtypes/supertyping' into dtype…
dangotbanned 802d939
refactor: Simplify `@just_dispatch` signature
dangotbanned 308f389
fix(typing): Satisfy mypy
dangotbanned 08f762a
test: Gotta get that coverage
dangotbanned ce3e730
Merge branch 'dtypes/supertyping' into dtypes/supertyping-dispatch
dangotbanned 18d6273
docs: Restore a minimal version of `@just_dispatch` doc
dangotbanned ddd5d98
revert: Remove `Impl` alias
dangotbanned 55c393e
refactor: Rename `Passthrough` -> `PassthroughFn`
dangotbanned 2757f40
docs: Add note to use only on internal
dangotbanned File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Callable | ||
| from functools import partial | ||
| from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar, overload | ||
|
|
||
| if TYPE_CHECKING: | ||
|
|
||
| class Deferred(Protocol): | ||
| def __call__(self, f: Callable[..., R], /) -> JustDispatch[R]: ... | ||
|
|
||
|
|
||
| __all__ = ["just_dispatch"] | ||
|
|
||
| R = TypeVar("R") | ||
| R_co = TypeVar("R_co", covariant=True) | ||
| PassthroughFn = TypeVar("PassthroughFn", bound=Callable[..., Any]) | ||
| """Original function is passed-through unchanged.""" | ||
|
|
||
|
|
||
| class JustDispatch(Generic[R_co]): | ||
| """Single-dispatch wrapper produced by decorating a function with `@just_dispatch`.""" | ||
|
|
||
| __slots__ = ("_registry", "_upper_bound") | ||
|
|
||
| def __init__(self, function: Callable[..., R_co], /, upper_bound: type[Any]) -> None: | ||
| self._upper_bound: type[Any] = upper_bound | ||
| self._registry: dict[type[Any], Callable[..., R_co]] = {upper_bound: function} | ||
|
|
||
| def dispatch(self, tp: type[Any], /) -> Callable[..., R_co]: | ||
| """Get the implementation for a given type.""" | ||
| if f := self._registry.get(tp): | ||
| return f | ||
| if issubclass(tp, self._upper_bound): | ||
| f = self._registry[tp] = self._registry[self._upper_bound] | ||
| return f | ||
| msg = f"{self._registry[self._upper_bound].__name__!r} does not support {tp.__name__!r}" | ||
| raise TypeError(msg) | ||
|
|
||
| def register( | ||
| self, tp: type[Any], *tps: type[Any] | ||
| ) -> Callable[[PassthroughFn], PassthroughFn]: | ||
| """Register types to dispatch via the decorated function.""" | ||
|
|
||
| def decorate(f: PassthroughFn, /) -> PassthroughFn: | ||
| self._registry.update((tp_, f) for tp_ in (tp, *tps)) | ||
| return f | ||
|
|
||
| return decorate | ||
|
|
||
| def __call__(self, arg: object, *args: Any, **kwds: Any) -> R_co: | ||
| """Dispatch on the type of the first argument, passing through all arguments.""" | ||
| return self.dispatch(arg.__class__)(arg, *args, **kwds) | ||
|
|
||
|
|
||
| @overload | ||
| def just_dispatch(function: Callable[..., R], /) -> JustDispatch[R]: ... | ||
| @overload | ||
| def just_dispatch(*, upper_bound: type[Any] = object) -> Deferred: ... | ||
| def just_dispatch( | ||
| function: Callable[..., R] | None = None, /, *, upper_bound: type[Any] = object | ||
| ) -> JustDispatch[R] | Deferred: | ||
| """Transform a function into a single-dispatch generic function. | ||
|
|
||
| An alternative take on [`@functools.singledispatch`]: | ||
| - without [MRO] fallback | ||
| - allows [*just*] the types registered and optionally an `upper_bound` | ||
|
|
||
| Arguments: | ||
| function: Function to decorate, where the body serves as the default implementation. | ||
| upper_bound: When there is no registered implementation for a specific type, it must | ||
| be a subclass of `upper_bound` to use the default implementation. | ||
|
|
||
| Tip: | ||
| `@just_dispatch` should only be used to decorate **internal functions** as we lose the docstring. | ||
|
|
||
| [`@functools.singledispatch`]: https://docs.python.org/3/library/functools.html#functools.singledispatch | ||
| [MRO]: https://docs.python.org/3/howto/mro.html#python-2-3-mro | ||
| [*just*]: https://github.com/jorenham/optype/blob/e7221ed1d3d02989d5d01873323bac9f88459f26/README.md#just | ||
| """ | ||
| if function is not None: | ||
| return JustDispatch(function, upper_bound) | ||
| return partial(JustDispatch[Any], upper_bound=upper_bound) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.