diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5b8d81..ab6e90e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.10" cache: poetry - name: poetry check @@ -43,6 +43,12 @@ jobs: - name: basedpyright run: poetry run basedpyright + - name: markdownlint + uses: DavidAnson/markdownlint-cli2-action@v16 + with: + config: '.markdownlint.jsonc' + globs: '**/*.md' + test: timeout-minutes: 5 @@ -50,7 +56,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13-dev"] runs-on: ${{ matrix.os }} diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc index 97e188f..9cbb33a 100644 --- a/.markdownlint.jsonc +++ b/.markdownlint.jsonc @@ -3,6 +3,9 @@ "MD007": { "indent": 4 }, + "MD013": { + "line_length": 79 + }, "MD033": { "allowed_elements": [ "h1", diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 95c2de3..4f06da0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,13 +16,13 @@ repos: - id: codespell - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.5 + rev: v0.3.7 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/DetachHead/basedpyright - rev: b1ebff9e96a087851fc24efe1674660cfea59140 + rev: v1.10.1 hooks: - id: basedpyright diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..78b61f2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,70 @@ +# Code of Conduct - optype + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, +body size, disability, ethnicity, sex characteristics, gender identity and +expression, level of experience, education, socio-economic status, nationality, +personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying and enforcing our standards +of acceptable behavior and will take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban +temporarily or permanently any contributor for other behaviors that they deem +inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail +address, posting via an official social media account, or acting as an +appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +`jhammudoglugmailcom` +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant](https://contributor-covenant.org/) and was generated by +[contributing-gen](https://github.com/bttger/contributing-gen). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cc8c840 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,246 @@ + +# Contributing to optype + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. +See the [Table of Contents](#table-of-contents) for different ways to help and +details about how this project handles them. +Please make sure to read the relevant section before making your contribution. +It will make it a lot easier for us maintainers and smooth out the experience +for all involved. +The community looks forward to your contributions. 🎉 + +> [!NOTE] +> And if you like optype, but just don't have time to contribute, that's fine. +> There are other easy ways to support the project and show your appreciation, +> which we would also be very happy about: +> +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [I Have a Question](#i-have-a-question) +- [I Want To Contribute](#i-want-to-contribute) + - [Reporting Bugs](#reporting-bugs) + - [Suggesting Enhancements](#suggesting-enhancements) + - [Your First Code Contribution](#your-first-code-contribution) + - [Improving The Documentation](#improving-the-documentation) + +## Code of Conduct + +This project and everyone participating in it is governed by the +[optype Code of Conduct][COC]. +By participating, you are expected to uphold this code. +Please report unacceptable behavior to `jhammudoglugmailcom`. + +## I Have a Question + +> [!NOTE] +> If you want to ask a question, we assume that you have read the +> available [Documentation][DOC]. + +Before you ask a question, it is best to search for existing [Issues][BUG] +that might help you. +In case you have found a suitable issue and still need clarification, +you can write your question in this issue. +It is also advisable to search the internet for answers first. + +If you then still feel the need to ask a question and need clarification, we +recommend the following: + +- Open an [Issue][BUG]. +- Provide as much context as you can about what you're running into. +- Provide project and platform versions (Python, mypy, pyright, ruff, etc), +depending on what seems relevant. + +We will then take care of the issue as soon as possible. + +## I Want To Contribute + +> ### Legal Notice +> +> When contributing to this project, +> you must agree that you have authored 100% of the content, +> that you have the necessary rights to the content and that the content you +> contribute may be provided under the project license. + +### Reporting Bugs + + +#### Before Submitting a Bug Report + +A good bug report shouldn't leave others needing to chase you up for more +information. +Therefore, we ask you to investigate carefully, collect information and +describe the issue in detail in your report. +Please complete the following steps in advance to help us fix any potential +bug as fast as possible. + +- Make sure that you are using the latest version. +- Determine if your bug is really a bug and not an error on your side e.g. +using incompatible environment components/versions +(Make sure that you have read the [documentation][DOC]. +If you are looking for support, you might want to check +[this section](#i-have-a-question)). +- To see if other users have experienced (and potentially already solved) +the same issue you are having, check if there is not already a bug report +existing for your bug or error in the [bug tracker][BUG]. +- Also make sure to search the internet (including Stack Overflow) to see if +users outside of the GitHub community have discussed the issue. +- Collect information about the bug: + - Stack trace (Traceback) + - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) + - Version of the interpreter, compiler, SDK, runtime environment, + package manager, depending on what seems relevant. + - Possibly your input and the output + - Can you reliably reproduce the issue? + And can you also reproduce it with older versions? + + +#### How Do I Submit a Good Bug Report? + +> You must never report security related issues, vulnerabilities or bugs +including sensitive information to the issue tracker, or elsewhere in public. +Instead sensitive bugs must be sent by email to `jhammudoglugmailcom`. + +We use GitHub issues to track bugs and errors. +If you run into an issue with the project: + +- Open an [Issue][BUG]. +(Since we can't be sure at this point whether it is a bug or not, +we ask you not to talk about a bug yet and not to label the issue.) +- Explain the behavior you would expect and the actual behavior. +- Please provide as much context as possible and describe the +*reproduction steps* that someone else can follow to recreate the issue on +their own. +This usually includes your code. +For good bug reports you should isolate the problem and create a reduced test +case. +- Provide the information you collected in the previous section. + +Once it's filed: + +- The project team will label the issue accordingly. +- A team member will try to reproduce the issue with your provided steps. +If there are no reproduction steps or no obvious way to reproduce the issue, +the team will ask you for those steps and mark the issue as `needs-repro`. +Bugs with the `needs-repro` tag will not be addressed until they are +reproduced. +- If the team is able to reproduce the issue, it will be marked `needs-fix`, +as well as possibly other tags (such as `critical`), and the issue will be +left to be [implemented by someone](#your-first-code-contribution). + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for +optype, **including completely new features and minor improvements to existing +functionality**. +Following these guidelines will help maintainers and the community to +understand your suggestion and find related suggestions. + + +#### Before Submitting an Enhancement + +- Make sure that you are using the latest version. +- Read the [documentation][DOC] carefully and find out if the functionality is +already covered, maybe by an individual configuration. +- Perform a [search][BUG] to see if the enhancement has already been suggested. +If it has, add a comment to the existing issue instead of opening a new one. +- Find out whether your idea fits with the scope and aims of the project. +It's up to you to make a strong case to convince the project's developers of +the merits of this feature. Keep in mind that we want features that will be +useful to the majority of our users and not just a small subset. If you're +just targeting a minority of users, consider writing an add-on/plugin library. + + +#### How Do I Submit a Good Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues][BUG]. + +- Use a **clear and descriptive title** for the issue to identify the +suggestion. +- Provide a **step-by-step description of the suggested enhancement** in as +many details as possible. +- **Describe the current behavior** and **explain which behavior you expected +to see instead** and why. At this point you can also tell which alternatives +do not work for you. +- **Explain why this enhancement would be useful** to most optype users. +You may also want to point out the other projects that solved it better and +which could serve as inspiration. + +### Your First Code Contribution + +Ensure you have [poetry](https://python-poetry.org/docs/#installation) +installed. +It can help to use optype's lowest-supported Python version, so that you don't +accidentally use those bleeding-edge Python features that you shouldn't, e.g. + +```bash +poetry env use python3.11 +``` + +Now you can install the dev dependencies using + +```bash +poetry install --sync +``` + +### pre-commit + +Optype uses [pre-commit](https://pre-commit.com/) to ensure that the code is +formatted and typed correctly when committing the changes. + +```bash +poetry run pre-commit install +``` + +It can also be manually run: + +```bash +poetry run pre-commit run --all-files +``` + +This is roughly the same as manually running + +```bash +poetry check +poetry run codespell +poetry run ruff --fix +poetry run basedpyright +poetry run markdownlint +``` + +> [!NOTE] +> Pre-commit doesn't run the tests. This will be run by github actions when +> submitting a pull request. See the next section for more details. + +### Testing + +Optype uses [pytest](https://docs.pytest.org/en/stable/) for unit testing. +These tests can be run with + +```bash +poetry run pytest +``` + +### Improving The Documentation + +All [documentation] lives in the `README.md`. Please read it carefully before +proposing any changes. Ensure that the markdown is formatted correctly with +[markdownlint](https://github.com/DavidAnson/markdownlint/tree/main). + + +## Attribution + +This guide is based on the **contributing-gen**. +[Make your own](https://github.com/bttger/contributing-gen)! + +[BUG]: https://github.com/jorenham/optype/issues +[COC]: https://github.com/jorenham/optype/blob/master/CODE_OF_CONDUCT.md +[DOC]: https://github.com/jorenham/optype?tab=readme-ov-file#optype diff --git a/README.md b/README.md index d6ea940..9d41b98 100644 --- a/README.md +++ b/README.md @@ -11,38 +11,38 @@

Continuous Integration PyPI Python Versions License - + Ruff - + Checked with pyright

@@ -78,26 +78,77 @@ For `twice`, we can use `optype.CanRMul[X, Y]`, which, as the name suggests, is a protocol with (only) the `def __rmul__(self, x: X) -> Y: ...` method. With this, the `twice` function can written as: + + + + + + + + + +
Python 3.11Python 3.12
+ +```python +from typing import Literal, TypeAlias, TypeVar +from optype import CanRMul + +Y = TypeVar('Y') +Two: TypeAlias = Literal[2] + +def twice(x: CanRMul[Two, Y]) -> Y: + return 2 * x +``` + + + ```python -import typing -import optype +from typing import Literal +from optype import CanRMul -type Two = typing.Literal[2] -def twice[Y](x: optype.CanRMul[Two, Y], /) -> Y: +type Two = Literal[2] + +def twice[Y](x: CanRMul[Two, Y]) -> Y: return 2 * x ``` +
+ But what about types that implement `__add__` but not `__radd__`? -In this case, we could return `x * 2` as fallback. +In this case, we could return `x * 2` as fallback (assuming commutativity). Because the `optype.Can*` protocols are runtime-checkable, the revised `twice2` function can be compactly written as: + + + + + + + + + +
Python 3.11Python 3.12
+ ```python -def twice2[Y](x: optype.CanRMul[Two, Y] | optype.CanMul[Two, Y], /) -> Y: - return 2 * x if isinstance(x, optype.CanRMul) else x * 2 +from optype import CanMul + +def twice2(x: CanRMul[Two, Y] | CanMul[Two, Y]) -> Y: + return 2 * x if isinstance(x, CanRMul) else x * 2 ``` + + +```python +from optype import CanMul + +def twice2[Y](x: CanRMul[Two, Y] | CanMul[Two, Y]) -> Y: + return 2 * x if isinstance(x, CanRMul) else x * 2 +``` + +
+ See [`examples/twice.py`](examples/twice.py) for the full example. ## Overview @@ -1396,7 +1447,6 @@ dataclass, i.e. `isinstance(obj, optype.HasDataclassFields)`. ## Future plans -- Support for Python versions before 3.12 (#19). - [numpy][NP] interfaces for arrays-like types (no deps) (#24) - [array-api][API-ND] interfaces (no deps) (#25) - [dataframe-api][API-DF] interfaces (no deps) (#26) diff --git a/examples/functor.py b/examples/functor.py index 8881d15..02b2d52 100644 --- a/examples/functor.py +++ b/examples/functor.py @@ -1,30 +1,41 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, final, override +import sys +from typing import TYPE_CHECKING, Any, Generic, TypeVar, final import optype +if sys.version_info < (3, 12): + from typing_extensions import override +else: + from typing import override + if TYPE_CHECKING: from collections.abc import Callable from types import NotImplementedType +T_co = TypeVar('T_co', covariant=True) +X = TypeVar('X') +Y = TypeVar('Y') + + @final # noqa: PLR0904 -class Functor[T]: +class Functor(Generic[T_co]): __match_args__ = __slots__ = ('value',) - def __init__(self, value: T, /) -> None: + def __init__(self, value: T_co, /) -> None: self.value = value - def map1[Y](self, f: Callable[[T], Y]) -> Functor[Y]: + def map1(self, f: Callable[[T_co], Y]) -> Functor[Y]: """ Applies a unary operator `f` over the value of `self`, and return a new `Functor`. """ return Functor(f(self.value)) - def map2[X, Y]( + def map2( self, - f: Callable[[T, X], Y], + f: Callable[[T_co, X], Y], other: Functor[X] | Any, ) -> Functor[Y] | NotImplementedType: """ @@ -50,21 +61,21 @@ def __hash__(self: Functor[optype.CanHash]) -> int: # unary prefix ops - def __neg__[Y](self: Functor[optype.CanNeg[Y]]) -> Functor[Y]: + def __neg__(self: Functor[optype.CanNeg[Y]]) -> Functor[Y]: """ >>> -Functor(3.14) Functor(-3.14) """ return self.map1(optype.do_neg) - def __pos__[Y](self: Functor[optype.CanPos[Y]]) -> Functor[Y]: + def __pos__(self: Functor[optype.CanPos[Y]]) -> Functor[Y]: """ >>> +Functor(True) Functor(1) """ return self.map1(optype.do_pos) - def __invert__[Y](self: Functor[optype.CanInvert[Y]]) -> Functor[Y]: + def __invert__(self: Functor[optype.CanInvert[Y]]) -> Functor[Y]: """ >>> ~Functor(0) Functor(-1) @@ -73,7 +84,7 @@ def __invert__[Y](self: Functor[optype.CanInvert[Y]]) -> Functor[Y]: # rich comparison ops - def __lt__[X, Y]( + def __lt__( self: Functor[optype.CanLt[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -85,7 +96,7 @@ def __lt__[X, Y]( """ return self.map2(optype.do_lt, x) - def __le__[X, Y]( + def __le__( self: Functor[optype.CanLe[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -98,7 +109,7 @@ def __le__[X, Y]( return self.map2(optype.do_le, x) @override - def __eq__[X, Y]( # pyright: ignore[reportIncompatibleMethodOverride] + def __eq__( # pyright: ignore[reportIncompatibleMethodOverride] self: Functor[optype.CanEq[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -113,7 +124,7 @@ def __eq__[X, Y]( # pyright: ignore[reportIncompatibleMethodOverride] return self.map2(optype.do_eq, x) @override - def __ne__[X, Y]( # pyright: ignore[reportIncompatibleMethodOverride] + def __ne__( # pyright: ignore[reportIncompatibleMethodOverride] self: Functor[optype.CanNe[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -127,7 +138,7 @@ def __ne__[X, Y]( # pyright: ignore[reportIncompatibleMethodOverride] """ return self.map2(optype.do_ne, x) - def __gt__[X, Y]( + def __gt__( self: Functor[optype.CanGt[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -139,7 +150,7 @@ def __gt__[X, Y]( """ return self.map2(optype.do_gt, x) - def __ge__[X, Y]( + def __ge__( self: Functor[optype.CanGe[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -153,7 +164,7 @@ def __ge__[X, Y]( # binary infix ops - def __add__[X, Y]( + def __add__( self: Functor[optype.CanAdd[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -165,7 +176,7 @@ def __add__[X, Y]( """ return self.map2(optype.do_add, x) - def __sub__[X, Y]( + def __sub__( self: Functor[optype.CanSub[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -175,7 +186,7 @@ def __sub__[X, Y]( """ return self.map2(optype.do_sub, x) - def __mul__[X, Y]( + def __mul__( self: Functor[optype.CanMul[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -185,13 +196,13 @@ def __mul__[X, Y]( """ return self.map2(optype.do_mul, x) - def __matmul__[X, Y]( + def __matmul__( self: Functor[optype.CanMatmul[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_matmul, x) - def __truediv__[X, Y]( + def __truediv__( self: Functor[optype.CanTruediv[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -201,7 +212,7 @@ def __truediv__[X, Y]( """ return self.map2(optype.do_truediv, x) - def __floordiv__[X, Y]( + def __floordiv__( self: Functor[optype.CanFloordiv[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -211,7 +222,7 @@ def __floordiv__[X, Y]( """ return self.map2(optype.do_floordiv, x) - def __mod__[X, Y]( + def __mod__( self: Functor[optype.CanMod[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -221,7 +232,7 @@ def __mod__[X, Y]( """ return self.map2(optype.do_mod, x) - def __pow__[X, Y]( + def __pow__( self: Functor[optype.CanPow2[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -231,7 +242,7 @@ def __pow__[X, Y]( """ return self.map2(optype.do_pow, x) - def __lshift__[X, Y]( + def __lshift__( self: Functor[optype.CanLshift[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -241,7 +252,7 @@ def __lshift__[X, Y]( """ return self.map2(optype.do_lshift, x) - def __rshift__[X, Y]( + def __rshift__( self: Functor[optype.CanRshift[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -251,7 +262,7 @@ def __rshift__[X, Y]( """ return self.map2(optype.do_rshift, x) - def __and__[X, Y]( + def __and__( self: Functor[optype.CanAnd[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -263,7 +274,7 @@ def __and__[X, Y]( """ return self.map2(optype.do_and, x) - def __xor__[X, Y]( + def __xor__( self: Functor[optype.CanXor[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -275,7 +286,7 @@ def __xor__[X, Y]( """ return self.map2(optype.do_xor, x) - def __or__[X, Y]( + def __or__( self: Functor[optype.CanOr[X, Y]], x: Functor[X], ) -> Functor[Y]: @@ -289,79 +300,79 @@ def __or__[X, Y]( # binary reflected infix ops - def __radd__[X, Y]( + def __radd__( self: Functor[optype.CanRAdd[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_radd, x) - def __rsub__[X, Y]( + def __rsub__( self: Functor[optype.CanRSub[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rsub, x) - def __rmul__[X, Y]( + def __rmul__( self: Functor[optype.CanRMul[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rmul, x) - def __rmatmul__[X, Y]( + def __rmatmul__( self: Functor[optype.CanRMatmul[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rmatmul, x) - def __rtruediv__[X, Y]( + def __rtruediv__( self: Functor[optype.CanRTruediv[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rtruediv, x) - def __rfloordiv__[X, Y]( + def __rfloordiv__( self: Functor[optype.CanRFloordiv[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rfloordiv, x) - def __rmod__[X, Y]( + def __rmod__( self: Functor[optype.CanRMod[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rmod, x) - def __rpow__[X, Y]( + def __rpow__( self: Functor[optype.CanRPow[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rpow, x) - def __rlshift__[X, Y]( + def __rlshift__( self: Functor[optype.CanRLshift[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rlshift, x) - def __rrshift__[X, Y]( + def __rrshift__( self: Functor[optype.CanRRshift[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rrshift, x) - def __rand__[X, Y]( + def __rand__( self: Functor[optype.CanRAnd[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rand, x) - def __rxor__[X, Y]( + def __rxor__( self: Functor[optype.CanRXor[X, Y]], x: Functor[X], ) -> Functor[Y]: return self.map2(optype.do_rxor, x) - def __ror__[X, Y]( + def __ror__( self: Functor[optype.CanROr[X, Y]], x: Functor[X], ) -> Functor[Y]: diff --git a/examples/twice.py b/examples/twice.py index 7255f28..5ef56fd 100644 --- a/examples/twice.py +++ b/examples/twice.py @@ -1,51 +1,72 @@ -import typing -import optype - -type Two = typing.Literal[2] +# %% +import sys +from typing import ( + Generic, + Literal, + TypeAlias, + TypeVar, + final, +) +if sys.version_info < (3, 12): + from typing_extensions import TypeVarTuple, Unpack, assert_type +else: + from typing import TypeVarTuple, Unpack, assert_type + +from optype import CanMul, CanRMul + +Y = TypeVar('Y') +Two: TypeAlias = Literal[2] + + +def twice(x: CanRMul[Two, Y], /) -> Y: + return 2 * x -def twice[Y](x: optype.CanRMul[Two, Y], /) -> Y: - return 2 * x +# %% +assert_type(twice(True), int) +assert_type(twice(1 / 137), float) +assert_type(twice(str(-1 / 12)), str) +assert_type(twice([object()]), list[object]) -typing.assert_type(twice(True), int) -typing.assert_type(twice(1 / 137), float) -typing.assert_type(twice(str(-1 / 12)), str) -typing.assert_type(twice([object()]), list[object]) +# %% +Ts = TypeVarTuple('Ts') -@typing.final -class RMulArgs[*Ts]: - def __init__(self, *args: *Ts) -> None: +@final +class RMulArgs(Generic[Unpack[Ts]]): + def __init__(self, *args: Unpack[Ts]) -> None: self.args = args - def __rmul__[Y: int](self, y: Two, /) -> 'RMulArgs[*Ts, *Ts]': + def __rmul__(self, y: Two, /) -> 'RMulArgs[Unpack[Ts], Unpack[Ts]]': if y != 2: return NotImplemented return RMulArgs(*self.args, *self.args) -typing.assert_type(twice(RMulArgs(42, True)), RMulArgs[int, bool, int, bool]) +assert_type(twice(RMulArgs(42, True)), RMulArgs[int, bool, int, bool]) -### - -def twice2[Y](x: optype.CanRMul[Two, Y] | optype.CanMul[Two, Y], /) -> Y: - return 2 * x if isinstance(x, optype.CanRMul) else x * 2 +# %% +def twice2(x: CanRMul[Two, Y] | CanMul[Two, Y], /) -> Y: + return 2 * x if isinstance(x, CanRMul) else x * 2 +# %% class RMulThing: def __rmul__(self, y: Two, /) -> str: return f'{y} * _' +assert_type(twice2(RMulThing()), str) +assert twice2(RMulThing()) == '2 * _' + + +# %% class MulThing: def __mul__(self, y: Two, /) -> str: return f'_ * {y}' -typing.assert_type(twice2(RMulThing()), str) -assert twice2(RMulThing()) == '2 * _' - -typing.assert_type(twice2(MulThing()), str) +assert_type(twice2(MulThing()), str) assert twice2(MulThing()) == '_ * 2' diff --git a/optype/_can.py b/optype/_can.py index d65e209..46ab3c8 100644 --- a/optype/_can.py +++ b/optype/_can.py @@ -1,37 +1,76 @@ # ruff: noqa: PYI034 -from collections.abc import Generator # sadge :( +import sys +from collections.abc import Generator from types import TracebackType from typing import ( Any, + ParamSpec, Protocol, - Self, + TypeAlias, + TypeVar, overload, - override, runtime_checkable, ) +if sys.version_info < (3, 11): + from typing_extensions import Self, TypeVarTuple, Unpack +else: + from typing import Self, TypeVarTuple, Unpack + +if sys.version_info < (3, 12): + from typing_extensions import override +else: + from typing import override + + # Iterator types # https://docs.python.org/3/library/stdtypes.html#iterator-types +_T_co = TypeVar('_T_co', bound=object, covariant=True) +_T_contra = TypeVar('_T_contra', bound=object, contravariant=True) + +_K_contra = TypeVar('_K_contra', contravariant=True) +_K_str_contra = TypeVar('_K_str_contra', bound=str, contravariant=True) +_K_idx_contra = TypeVar('_K_idx_contra', contravariant=True, bound='CanIndex') + +_V = TypeVar('_V') +_V_exc = TypeVar('_V_exc', bound=BaseException) +_V_contra = TypeVar('_V_contra', contravariant=True) +_V_int_contra = TypeVar('_V_int_contra', bound=int, contravariant=True) +_V_co = TypeVar('_V_co', covariant=True) +_V_next_co = TypeVar('_V_next_co', bound='CanNext[Any]', covariant=True) +_V_iter_co = TypeVar('_V_iter_co', bound='CanIter[Any]', covariant=True) +_V_anext_co = TypeVar('_V_anext_co', bound='CanANext[Any]', covariant=True) + +_D_co = TypeVar('_D_co', covariant=True) + +_Y_co = TypeVar('_Y_co', covariant=True) +_Y_str_co = TypeVar('_Y_str_co', bound=str, covariant=True) +_Y_bytes_co = TypeVar('_Y_bytes_co', bound=bytes, covariant=True) + +_X_co = TypeVar('_X_co', covariant=True) +_Xs = TypeVarTuple('_Xs') +_Xss = ParamSpec('_Xss') + @runtime_checkable -class CanNext[V](Protocol): +class CanNext(Protocol[_V_co]): """ Similar to `collections.abc.Iterator`, but without the (often redundant) requirement to also have a `__iter__` method. """ - def __next__(self) -> V: ... + def __next__(self) -> _V_co: ... @runtime_checkable -class CanIter[Vs: CanNext[Any]](Protocol): +class CanIter(Protocol[_V_next_co]): """Similar to `collections.abc.Iterable`, but more flexible.""" - def __iter__(self) -> Vs: ... + def __iter__(self) -> _V_next_co: ... @runtime_checkable -class CanIterSelf[V](CanNext[V], CanIter[CanNext[V]], Protocol): +class CanIterSelf(CanNext[_V_co], CanIter[CanNext[_V_co]], Protocol[_V_co]): """ Equivalent to `collections.abc.Iterator[T]`, minus the `abc` nonsense. """ @@ -44,46 +83,49 @@ def __iter__(self) -> Self: ... @runtime_checkable -class CanRepr[Y: str](Protocol): +class CanRepr(Protocol[_Y_str_co]): """ Each `object` has a *co*variant `__repr__: (CanRepr[Y]) -> Y` method. That means that if `__repr__` returns an instance of a custom `str` subtype `Y <: str`, then `repr()` will also return `Y` (i.e. no upcasting). """ @override - def __repr__(self) -> Y: ... + def __repr__(self) -> _Y_str_co: ... @runtime_checkable -class CanStr[Y: str](Protocol): +class CanStr(Protocol[_Y_str_co]): """ Each `object` has a *co*variant `__str__: (CanStr[Y]) -> Y` method on `+Y`. That means that if `__str__()` returns an instance of a custom `str` subtype `Y <: str`, then `str()` will also return `Y` (i.e. no upcasting). """ @override - def __str__(self) -> Y: ... + def __str__(self) -> _Y_str_co: ... @runtime_checkable -class CanBytes[Y: bytes](Protocol): +class CanBytes(Protocol[_Y_bytes_co]): """ The `__bytes__: (CanBytes[Y]) -> Y` method is *co*variant on `+Y`. So if `__bytes__` returns an instance of a custom `bytes` subtype `Y <: bytes`, then `bytes()` will also return `Y` (i.e. no upcasting). """ - def __bytes__(self) -> Y: ... + def __bytes__(self) -> _Y_bytes_co: ... + + +_X_str_contra = TypeVar('_X_str_contra', contravariant=True, bound=str) @runtime_checkable -class CanFormat[X: str, Y: str](Protocol): +class CanFormat(Protocol[_X_str_contra, _Y_str_co]): """ Each `object` has a `__format__: (CanFormat[X, Y], X) -> Y` method, with `-X` *contra*variant, and `+Y` *co*variant. Both `X` and `Y` can be `str` or `str` subtypes. Note that `format()` *does not* upcast `Y` to `str`. """ @override - def __format__(self, __x: X) -> Y: ... # pyright:ignore[reportIncompatibleMethodOverride] + def __format__(self, __x: _X_str_contra) -> _Y_str_co: ... # pyright:ignore[reportIncompatibleMethodOverride] @runtime_checkable @@ -101,18 +143,22 @@ def __hash__(self) -> int: ... # https://docs.python.org/3/reference/datamodel.html#object.__lt__ +_X_contra = TypeVar('_X_contra', contravariant=True) +_Y_co = TypeVar('_Y_co', covariant=True) + + @runtime_checkable -class CanLt[X, Y](Protocol): - def __lt__(self, __x: X) -> Y: ... +class CanLt(Protocol[_X_contra, _Y_co]): + def __lt__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanLe[X, Y](Protocol): - def __le__(self, __x: X) -> Y: ... +class CanLe(Protocol[_X_contra, _Y_co]): + def __le__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanEq[X, Y](Protocol): # noqa: PLW1641 +class CanEq(Protocol[_X_contra, _Y_co]): # noqa: PLW1641 """ Unfortunately, `typeshed` incorrectly annotates `object.__eq__` as `(Self, object) -> bool`. @@ -127,98 +173,105 @@ class CanEq[X, Y](Protocol): # noqa: PLW1641 and *contra*variant, and `+Y` unbounded and *co*variant. """ @override - def __eq__(self, __x: X, /) -> Y: ... # pyright:ignore[reportIncompatibleMethodOverride] + def __eq__(self, __x: _X_contra, /) -> _Y_co: ... # pyright:ignore[reportIncompatibleMethodOverride] @runtime_checkable -class CanNe[X, Y](Protocol): +class CanNe(Protocol[_X_contra, _Y_co]): """ Just like `__eq__`, The `__ne__` method is incorrectly annotated in `typeshed`. See `CanEq` for why this is, and how `optype` fixes this. """ @override - def __ne__(self, __x: X) -> Y: ... # pyright:ignore[reportIncompatibleMethodOverride] + def __ne__(self, __x: _X_contra) -> _Y_co: ... # pyright:ignore[reportIncompatibleMethodOverride] @runtime_checkable -class CanGt[X, Y](Protocol): - def __gt__(self, __x: X) -> Y: ... +class CanGt(Protocol[_X_contra, _Y_co]): + def __gt__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanGe[X, Y](Protocol): - def __ge__(self, __x: X) -> Y: ... +class CanGe(Protocol[_X_contra, _Y_co]): + def __ge__(self, __x: _X_contra) -> _Y_co: ... # 3.3.2. Customizing attribute access # https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access + @runtime_checkable -class CanGetattr[K: str, V](Protocol): - def __getattr__(self, __k: K) -> V: ... +class CanGetattr(Protocol[_K_str_contra, _V_co]): + def __getattr__(self, __k: _K_str_contra) -> _V_co: ... @runtime_checkable -class CanGetattribute[K: str, V](Protocol): +class CanGetattribute(Protocol[_K_str_contra, _V_co]): """Note that `isinstance(x, CanGetattribute)` is always true.""" @override - def __getattribute__(self, __k: K) -> V: ... # pyright:ignore[reportIncompatibleMethodOverride] + def __getattribute__(self, __k: _K_str_contra) -> _V_co: ... # pyright:ignore[reportIncompatibleMethodOverride] @runtime_checkable -class CanSetattr[K: str, V](Protocol): +class CanSetattr(Protocol[_K_str_contra, _V_contra]): """Note that `isinstance(x, CanSetattr)` is always true.""" @override - def __setattr__(self, __k: K, __v: V) -> Any: ... # pyright:ignore[reportIncompatibleMethodOverride] + def __setattr__(self, __k: _K_str_contra, __v: _V_contra) -> Any: ... # pyright:ignore[reportIncompatibleMethodOverride] @runtime_checkable -class CanDelattr[K: str](Protocol): +class CanDelattr(Protocol[_K_str_contra]): @override - def __delattr__(self, __k: K) -> Any: ... # pyright:ignore[reportIncompatibleMethodOverride] + def __delattr__(self, __k: _K_str_contra) -> Any: ... # pyright:ignore[reportIncompatibleMethodOverride] @runtime_checkable -class CanDir[Vs: CanIter[Any]](Protocol): +class CanDir(Protocol[_V_iter_co]): @override - def __dir__(self) -> Vs: ... + def __dir__(self) -> _V_iter_co: ... # 3.3.2.2. Implementing Descriptors # https://docs.python.org/3/reference/datamodel.html#implementing-descriptors + @runtime_checkable -class CanGet[T, U, V](Protocol): +class CanGet(Protocol[_T_contra, _T_co, _V_co]): @overload - def __get__(self, __obj: None, __cls: type[T]) -> U: ... + def __get__(self, __obj: None, __cls: type[_T_contra]) -> _T_co: ... @overload - def __get__(self, __obj: T, __cls: type[T] | None = ...) -> V: ... + def __get__( + self, + __obj: _T_contra, + __cls: type[_T_contra] | None = ..., + ) -> _V_co: ... @runtime_checkable -class CanSet[T: object, V](Protocol): - def __set__(self, __obj: T, __v: V) -> Any: ... +class CanSet(Protocol[_T_contra, _V_contra]): + def __set__(self, __obj: _T_contra, __v: _V_contra) -> Any: ... @runtime_checkable -class CanDelete[T: object](Protocol): - def __delete__(self, __obj: T) -> Any: ... +class CanDelete(Protocol[_T_contra]): + def __delete__(self, __obj: _T_contra) -> Any: ... # 3.3.3. Customizing class creation # https://docs.python.org/3/reference/datamodel.html#customizing-class-creation @runtime_checkable -class CanSetName[T](Protocol): - def __set_name__(self, __cls: type[T], __name: str) -> Any: ... +class CanSetName(Protocol[_T_contra]): + def __set_name__(self, __cls: type[_T_contra], __name: str) -> Any: ... # 3.3.6. Emulating callable objects # https://docs.python.org/3/reference/datamodel.html#emulating-callable-objects + @runtime_checkable -class CanCall[**Xs, Y](Protocol): - def __call__(self, *__xs: Xs.args, **__kxs: Xs.kwargs) -> Y: ... +class CanCall(Protocol[_Xss, _Y_co]): + def __call__(self, *__xs: _Xss.args, **__kw: _Xss.kwargs) -> _Y_co: ... # 3.3.7. Emulating container types @@ -235,41 +288,49 @@ def __length_hint__(self) -> int: ... @runtime_checkable -class CanGetitem[K, V](Protocol): - def __getitem__(self, __k: K) -> V: ... +class CanGetitem(Protocol[_K_contra, _V_co]): + def __getitem__(self, __k: _K_contra) -> _V_co: ... @runtime_checkable -class CanSetitem[K, V](Protocol): - def __setitem__(self, __k: K, __v: V) -> None: ... +class CanSetitem(Protocol[_K_contra, _V_contra]): + def __setitem__(self, __k: _K_contra, __v: _V_contra) -> None: ... @runtime_checkable -class CanDelitem[K](Protocol): - def __delitem__(self, __k: K) -> None: ... +class CanDelitem(Protocol[_K_contra]): + def __delitem__(self, __k: _K_contra) -> None: ... @runtime_checkable -class CanReversed[Y](Protocol): - def __reversed__(self) -> Y: ... +class CanReversed(Protocol[_V_co]): + def __reversed__(self) -> _V_co: ... @runtime_checkable -class CanContains[K](Protocol): - def __contains__(self, __k: K) -> bool: ... +class CanContains(Protocol[_K_contra]): + def __contains__(self, __k: _K_contra) -> bool: ... @runtime_checkable -class CanMissing[K, V](Protocol): - def __missing__(self, __k: K) -> V: ... +class CanMissing(Protocol[_K_contra, _V_co]): + def __missing__(self, __k: _K_contra) -> _V_co: ... @runtime_checkable -class CanGetMissing[K, V, M](CanGetitem[K, V], CanMissing[K, M], Protocol): ... +class CanGetMissing( + CanGetitem[_K_contra, _V_co], + CanMissing[_K_contra, _D_co], + Protocol[_K_contra, _V_co, _D_co], +): ... @runtime_checkable -class CanSequence[I: 'CanIndex', V](CanLen, CanGetitem[I, V], Protocol): +class CanSequence( + CanLen, + CanGetitem[_K_idx_contra, _V_co], + Protocol[_K_idx_contra, _V_co], +): """ A sequence is an object with a __len__ method and a __getitem__ method that takes int(-like) argument as key (the index). @@ -283,248 +344,255 @@ class CanSequence[I: 'CanIndex', V](CanLen, CanGetitem[I, V], Protocol): # https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types @runtime_checkable -class CanAdd[X, Y](Protocol): - def __add__(self, __x: X, /) -> Y: ... +class CanAdd(Protocol[_X_contra, _Y_co]): + def __add__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanSub[X, Y](Protocol): - def __sub__(self, __x: X, /) -> Y: ... +class CanSub(Protocol[_X_contra, _Y_co]): + def __sub__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanMul[X, Y](Protocol): - def __mul__(self, __x: X) -> Y: ... +class CanMul(Protocol[_X_contra, _Y_co]): + def __mul__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanMatmul[X, Y](Protocol): - def __matmul__(self, __x: X) -> Y: ... +class CanMatmul(Protocol[_X_contra, _Y_co]): + def __matmul__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanTruediv[X, Y](Protocol): - def __truediv__(self, __x: X) -> Y: ... +class CanTruediv(Protocol[_X_contra, _Y_co]): + def __truediv__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanFloordiv[X, Y](Protocol): - def __floordiv__(self, __x: X) -> Y: ... +class CanFloordiv(Protocol[_X_contra, _Y_co]): + def __floordiv__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanMod[X, Y](Protocol): - def __mod__(self, __x: X) -> Y: ... +class CanMod(Protocol[_X_contra, _Y_co]): + def __mod__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanDivmod[X, Y](Protocol): - def __divmod__(self, __x: X) -> Y: ... +class CanDivmod(Protocol[_X_contra, _Y_co]): + def __divmod__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanPow2[X, Y2](Protocol): - def __pow__(self, __x: X) -> Y2: ... +class CanPow2(Protocol[_X_contra, _Y_co]): + def __pow__(self, __x: _X_contra) -> _Y_co: ... + + +_VY_co = TypeVar('_VY_co', covariant=True) @runtime_checkable -class CanPow3[X, M, Y3](Protocol): - def __pow__(self, __x: X, __m: M) -> Y3: ... +class CanPow3(Protocol[_X_contra, _V_contra, _VY_co]): + def __pow__(self, __x: _X_contra, __m: _V_contra) -> _VY_co: ... @runtime_checkable -class CanPow[X, M, Y2, Y3](CanPow2[X, Y2], CanPow3[X, M, Y3], Protocol): +class CanPow( + CanPow2[_X_contra, _Y_co], + CanPow3[_X_contra, _V_contra, _VY_co], + Protocol[_X_contra, _V_contra, _Y_co, _VY_co], +): @overload - def __pow__(self, __x: X) -> Y2: ... + def __pow__(self, __x: _X_contra) -> _Y_co: ... @overload - def __pow__(self, __x: X, __m: M) -> Y3: ... + def __pow__(self, __x: _X_contra, __m: _V_contra) -> _VY_co: ... @runtime_checkable -class CanLshift[X, Y](Protocol): - def __lshift__(self, __x: X, /) -> Y: ... +class CanLshift(Protocol[_X_contra, _Y_co]): + def __lshift__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanRshift[X, Y](Protocol): - def __rshift__(self, __x: X, /) -> Y: ... +class CanRshift(Protocol[_X_contra, _Y_co]): + def __rshift__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanAnd[X, Y](Protocol): - def __and__(self, __x: X, /) -> Y: ... +class CanAnd(Protocol[_X_contra, _Y_co]): + def __and__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanXor[X, Y](Protocol): - def __xor__(self, __x: X, /) -> Y: ... +class CanXor(Protocol[_X_contra, _Y_co]): + def __xor__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanOr[X, Y](Protocol): - def __or__(self, __x: X, /) -> Y: ... +class CanOr(Protocol[_X_contra, _Y_co]): + def __or__(self, __x: _X_contra, /) -> _Y_co: ... # reflected @runtime_checkable -class CanRAdd[X, Y](Protocol): - def __radd__(self, __x: X, /) -> Y: ... +class CanRAdd(Protocol[_X_contra, _Y_co]): + def __radd__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanRSub[X, Y](Protocol): - def __rsub__(self, __x: X, /) -> Y: ... +class CanRSub(Protocol[_X_contra, _Y_co]): + def __rsub__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanRMul[X, Y](Protocol): - def __rmul__(self, __x: X) -> Y: ... +class CanRMul(Protocol[_X_contra, _Y_co]): + def __rmul__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanRMatmul[X, Y](Protocol): - def __rmatmul__(self, __x: X) -> Y: ... +class CanRMatmul(Protocol[_X_contra, _Y_co]): + def __rmatmul__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanRTruediv[X, Y](Protocol): - def __rtruediv__(self, __x: X) -> Y: ... +class CanRTruediv(Protocol[_X_contra, _Y_co]): + def __rtruediv__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanRFloordiv[X, Y](Protocol): - def __rfloordiv__(self, __x: X) -> Y: ... +class CanRFloordiv(Protocol[_X_contra, _Y_co]): + def __rfloordiv__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanRMod[X, Y](Protocol): - def __rmod__(self, __x: X) -> Y: ... +class CanRMod(Protocol[_X_contra, _Y_co]): + def __rmod__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanRDivmod[X, Y](Protocol): - def __rdivmod__(self, __x: X) -> Y: ... +class CanRDivmod(Protocol[_X_contra, _Y_co]): + def __rdivmod__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanRPow[X, Y](Protocol): - def __rpow__(self, __x: X) -> Y: ... +class CanRPow(Protocol[_X_contra, _Y_co]): + def __rpow__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanRLshift[X, Y](Protocol): - def __rlshift__(self, __x: X, /) -> Y: ... +class CanRLshift(Protocol[_X_contra, _Y_co]): + def __rlshift__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanRRshift[X, Y](Protocol): - def __rrshift__(self, __x: X, /) -> Y: ... +class CanRRshift(Protocol[_X_contra, _Y_co]): + def __rrshift__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanRAnd[X, Y](Protocol): - def __rand__(self, __x: X, /) -> Y: ... +class CanRAnd(Protocol[_X_contra, _Y_co]): + def __rand__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanRXor[X, Y](Protocol): - def __rxor__(self, __x: X, /) -> Y: ... +class CanRXor(Protocol[_X_contra, _Y_co]): + def __rxor__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanROr[X, Y](Protocol): - def __ror__(self, __x: X, /) -> Y: ... +class CanROr(Protocol[_X_contra, _Y_co]): + def __ror__(self, __x: _X_contra, /) -> _Y_co: ... # augmented / in-place @runtime_checkable -class CanIAdd[X, Y](Protocol): - def __iadd__(self, __x: X, /) -> Y: ... +class CanIAdd(Protocol[_X_contra, _Y_co]): + def __iadd__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanISub[X, Y](Protocol): - def __isub__(self, __x: X, /) -> Y: ... +class CanISub(Protocol[_X_contra, _Y_co]): + def __isub__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanIMul[X, Y](Protocol): - def __imul__(self, __x: X) -> Y: ... +class CanIMul(Protocol[_X_contra, _Y_co]): + def __imul__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanIMatmul[X, Y](Protocol): - def __imatmul__(self, __x: X) -> Y: ... +class CanIMatmul(Protocol[_X_contra, _Y_co]): + def __imatmul__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanITruediv[X, Y](Protocol): - def __itruediv__(self, __x: X) -> Y: ... +class CanITruediv(Protocol[_X_contra, _Y_co]): + def __itruediv__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanIFloordiv[X, Y](Protocol): - def __ifloordiv__(self, __x: X) -> Y: ... +class CanIFloordiv(Protocol[_X_contra, _Y_co]): + def __ifloordiv__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanIMod[X, Y](Protocol): - def __imod__(self, __x: X) -> Y: ... +class CanIMod(Protocol[_X_contra, _Y_co]): + def __imod__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanIPow[X, Y](Protocol): +class CanIPow(Protocol[_X_contra, _Y_co]): # no augmented pow/3 exists - def __ipow__(self, __x: X) -> Y: ... + def __ipow__(self, __x: _X_contra) -> _Y_co: ... @runtime_checkable -class CanILshift[X, Y](Protocol): - def __ilshift__(self, __x: X, /) -> Y: ... +class CanILshift(Protocol[_X_contra, _Y_co]): + def __ilshift__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanIRshift[X, Y](Protocol): - def __irshift__(self, __x: X, /) -> Y: ... +class CanIRshift(Protocol[_X_contra, _Y_co]): + def __irshift__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanIAnd[X, Y](Protocol): - def __iand__(self, __x: X, /) -> Y: ... +class CanIAnd(Protocol[_X_contra, _Y_co]): + def __iand__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanIXor[X, Y](Protocol): - def __ixor__(self, __x: X, /) -> Y: ... +class CanIXor(Protocol[_X_contra, _Y_co]): + def __ixor__(self, __x: _X_contra, /) -> _Y_co: ... @runtime_checkable -class CanIOr[X, Y](Protocol): - def __ior__(self, __x: X, /) -> Y: ... +class CanIOr(Protocol[_X_contra, _Y_co]): + def __ior__(self, __x: _X_contra, /) -> _Y_co: ... # unary arithmetic @runtime_checkable -class CanNeg[Y](Protocol): - def __neg__(self) -> Y: ... +class CanNeg(Protocol[_Y_co]): + def __neg__(self) -> _Y_co: ... @runtime_checkable -class CanPos[Y](Protocol): - def __pos__(self) -> Y: ... +class CanPos(Protocol[_Y_co]): + def __pos__(self) -> _Y_co: ... @runtime_checkable -class CanAbs[Y](Protocol): - def __abs__(self) -> Y: ... +class CanAbs(Protocol[_Y_co]): + def __abs__(self) -> _Y_co: ... @runtime_checkable -class CanInvert[Y](Protocol): - def __invert__(self) -> Y: ... +class CanInvert(Protocol[_Y_co]): + def __invert__(self) -> _Y_co: ... # numeric conversion @@ -552,74 +620,80 @@ def __index__(self) -> int: ... @runtime_checkable -class CanRound1[Y](Protocol): +class CanRound1(Protocol[_Y_co]): @overload - def __round__(self) -> Y: ... + def __round__(self) -> _Y_co: ... @overload - def __round__(self, __n: None = ...) -> Y: ... + def __round__(self, __n: None = ...) -> _Y_co: ... @runtime_checkable -class CanRound2[N, Y](Protocol): - def __round__(self, __n: N) -> Y: ... +class CanRound2(Protocol[_V_contra, _VY_co]): + def __round__(self, __n: _V_contra) -> _VY_co: ... @runtime_checkable -class CanRound[N, Y1, Y2](CanRound1[Y1], CanRound2[N, Y2], Protocol): +class CanRound( + CanRound1[_Y_co], + CanRound2[_V_contra, _VY_co], + Protocol[_V_contra, _Y_co, _VY_co], +): @overload - def __round__(self) -> Y1: ... + def __round__(self) -> _Y_co: ... @overload - def __round__(self, __n: None = ...) -> Y1: ... + def __round__(self, __n: None = ...) -> _Y_co: ... @overload - def __round__(self, __n: N) -> Y2: ... + def __round__(self, __n: _V_contra) -> _VY_co: ... @runtime_checkable -class CanTrunc[Y](Protocol): - def __trunc__(self) -> Y: ... +class CanTrunc(Protocol[_Y_co]): + def __trunc__(self) -> _Y_co: ... @runtime_checkable -class CanFloor[Y](Protocol): - def __floor__(self) -> Y: ... +class CanFloor(Protocol[_Y_co]): + def __floor__(self) -> _Y_co: ... @runtime_checkable -class CanCeil[Y](Protocol): - def __ceil__(self) -> Y: ... +class CanCeil(Protocol[_Y_co]): + def __ceil__(self) -> _Y_co: ... # 3.3.9. With Statement Context Managers # https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers + @runtime_checkable -class CanEnter[V](Protocol): - def __enter__(self) -> V: ... +class CanEnter(Protocol[_X_co]): + def __enter__(self) -> _X_co: ... @runtime_checkable -class CanExit[R](Protocol): +class CanExit(Protocol[_Y_co]): @overload def __exit__(self, __tp: None, __ex: None, __tb: None) -> None: ... @overload - def __exit__[E: BaseException]( + def __exit__( self, - __tp: type[E], - __ex: E, + __tp: type[_V_exc], + __ex: _V_exc, __tb: TracebackType, - ) -> R: ... + ) -> _Y_co: ... @runtime_checkable -class CanWith[V, R](CanEnter[V], CanExit[R], Protocol): ... +class CanWith(CanEnter[_X_co], CanExit[_Y_co], Protocol[_X_co, _Y_co]): ... # 3.3.11. Emulating buffer types # https://docs.python.org/3/reference/datamodel.html#emulating-buffer-types + @runtime_checkable -class CanBuffer[B: int](Protocol): - def __buffer__(self, __b: B) -> memoryview: ... +class CanBuffer(Protocol[_V_int_contra]): + def __buffer__(self, __b: _V_int_contra) -> memoryview: ... @runtime_checkable @@ -634,11 +708,11 @@ def __release_buffer__(self, __v: memoryview) -> None: ... # This should be `None | asyncio.Future[Any]`. But that would make this # incompatible with `collections.abc.Awaitable`, because it (annoyingly) # uses `Any`... -type _MaybeFuture = Any +_MaybeFuture: TypeAlias = Any @runtime_checkable -class CanAwait[V](Protocol): +class CanAwait(Protocol[_V_co]): # Technically speaking, this can return any # `CanNext[None | asyncio.Future[Any]]`. But in theory, the return value # of generators are currently impossible to type, because the return value @@ -649,39 +723,45 @@ class CanAwait[V](Protocol): @overload def __await__(self: 'CanAwait[None]') -> CanNext[_MaybeFuture]: ... @overload - def __await__(self: 'CanAwait[V]') -> Generator[_MaybeFuture, None, V]: ... + def __await__( + self: 'CanAwait[_V_co]', + ) -> Generator[_MaybeFuture, None, _V_co]: ... # 3.4.3. Asynchronous Iterators # https://docs.python.org/3/reference/datamodel.html#asynchronous-iterators @runtime_checkable -class CanANext[V](Protocol): - def __anext__(self) -> V: ... +class CanANext(Protocol[_V_co]): + def __anext__(self) -> _V_co: ... @runtime_checkable -class CanAIter[Y: CanANext[Any]](Protocol): - def __aiter__(self) -> Y: ... +class CanAIter(Protocol[_V_anext_co]): + def __aiter__(self) -> _V_anext_co: ... @runtime_checkable -class CanAIterSelf[V](CanANext[V], CanAIter[CanANext[V]], Protocol): +class CanAIterSelf( + CanANext[_V_co], + CanAIter[CanANext[_V_co]], + Protocol[_V_co], +): """A less inflexible variant of `collections.abc.AsyncIterator[T]`.""" @override - def __aiter__(self) -> 'CanAIterSelf[V]': ... # `Self` doesn't work here? + def __aiter__(self) -> Self: ... # 3.4.4. Asynchronous Context Managers # https://docs.python.org/3/reference/datamodel.html#asynchronous-context-managers @runtime_checkable -class CanAEnter[V](Protocol): - def __aenter__(self) -> CanAwait[V]: ... +class CanAEnter(Protocol[_X_co]): + def __aenter__(self) -> CanAwait[_X_co]: ... @runtime_checkable -class CanAExit[R](Protocol): +class CanAExit(Protocol[_Y_co]): @overload def __aexit__( self, @@ -690,16 +770,17 @@ def __aexit__( __tb: None, ) -> CanAwait[None]: ... @overload - def __aexit__[E: BaseException]( + def __aexit__( self, - __tp: type[E], - __ex: E, + __tp: type[_V_exc], + __ex: _V_exc, __tb: TracebackType, - ) -> CanAwait[R]: ... + ) -> CanAwait[_Y_co]: ... @runtime_checkable -class CanAsyncWith[V, R](CanAEnter[V], CanAExit[R], Protocol): ... +class CanAsyncWith(CanAEnter[_X_co], CanAExit[_Y_co], Protocol[_X_co, _Y_co]): + ... # `copy` stdlib @@ -707,21 +788,21 @@ class CanAsyncWith[V, R](CanAEnter[V], CanAExit[R], Protocol): ... @runtime_checkable -class CanCopy[T](Protocol): +class CanCopy(Protocol[_T_co]): """Support for creating shallow copies through `copy.copy`.""" - def __copy__(self) -> T: ... + def __copy__(self) -> _T_co: ... @runtime_checkable -class CanDeepcopy[T](Protocol): +class CanDeepcopy(Protocol[_T_co]): """Support for creating deep copies through `copy.deepcopy`.""" - def __deepcopy__(self, memo: dict[int, Any], /) -> T: ... + def __deepcopy__(self, memo: dict[int, Any], /) -> _T_co: ... @runtime_checkable -class CanReplace[T, V](Protocol): +class CanReplace(Protocol[_V_contra, _T_co]): """Support for `copy.replace` in Python 3.13+.""" - def __replace__(self, /, **changes: V) -> T: ... + def __replace__(self, /, **changes: _V_contra) -> _T_co: ... @runtime_checkable @@ -738,46 +819,55 @@ def __deepcopy__(self, memo: dict[int, Any], /) -> Self: ... @runtime_checkable -class CanReplaceSelf[V](CanReplace['CanReplaceSelf[Any]', V], Protocol): +class CanReplaceSelf(CanReplace[_V_contra, 'CanReplaceSelf[Any]'], Protocol): """Variant of `CanReplace` that returns `Self`.""" @override - def __replace__(self, /, **changes: V) -> Self: ... + def __replace__(self, /, **changes: _V_contra) -> Self: ... # `pickle` stdlib # https://docs.python.org/3.13/library/pickle.html +_Y_str_tuple_co = TypeVar( + '_Y_str_tuple_co', + covariant=True, + bound=str | tuple[Any, ...], +) + + @runtime_checkable -class CanReduce[R: str | tuple[Any, ...]](Protocol): +class CanReduce(Protocol[_Y_str_tuple_co]): @override - def __reduce__(self) -> R: ... + def __reduce__(self) -> _Y_str_tuple_co: ... @runtime_checkable -class CanReduceEx[R: str | tuple[Any, ...]](Protocol): +class CanReduceEx(Protocol[_Y_str_tuple_co]): @override - def __reduce_ex__(self, protocol: CanIndex, /) -> R: ... + def __reduce_ex__(self, protocol: CanIndex, /) -> _Y_str_tuple_co: ... @runtime_checkable -class CanGetstate[S: object](Protocol): - @override - def __getstate__(self) -> S: ... +class CanGetstate(Protocol[_T_co]): + def __getstate__(self) -> _T_co: ... @runtime_checkable -class CanSetstate[S: object](Protocol): - def __setstate__(self, state: S, /) -> None: ... +class CanSetstate(Protocol[_T_contra]): + def __setstate__(self, state: _T_contra, /) -> None: ... @runtime_checkable -class CanGetnewargs[*Args](Protocol): - def __new__(cls, *__args: *Args) -> Self: ... - def __getnewargs__(self) -> tuple[*Args]: ... +class CanGetnewargs(Protocol[Unpack[_Xs]]): + def __new__(cls, *__args: Unpack[_Xs]) -> Self: ... + def __getnewargs__(self) -> tuple[Unpack[_Xs]]: ... @runtime_checkable -class CanGetnewargsEx[*Args, Kw](Protocol): - def __new__(cls, *__args: *Args, **__kwargs: Kw) -> Self: ... - def __getnewargs_ex__(self) -> tuple[tuple[*Args], dict[str, Kw]]: ... +class CanGetnewargsEx(Protocol[Unpack[_Xs], _V]): + def __new__(cls, *__args: Unpack[_Xs], **__kwargs: _V) -> Self: ... + def __getnewargs_ex__(self) -> tuple[ + tuple[Unpack[_Xs]], + dict[str, _V], + ]: ... diff --git a/optype/_do.py b/optype/_do.py index 390f405..8a46844 100644 --- a/optype/_do.py +++ b/optype/_do.py @@ -1,11 +1,20 @@ import math as _math import operator as _o -from typing import TYPE_CHECKING +import sys as _sys +from typing import TYPE_CHECKING, ParamSpec, TypeVar import optype._can as _c import optype._does as _d +_K = TypeVar('_K') +_V = TypeVar('_V') +_D = TypeVar('_D') +_X = TypeVar('_X') +_Y = TypeVar('_Y') +_Xss = ParamSpec('_Xss') + + # type conversion do_bool: _d.DoesBool = bool do_int: _d.DoesInt = int @@ -49,7 +58,16 @@ # callables -do_call: _d.DoesCall = _o.call +if _sys.version_info < (3, 11): + def do_call( + f: _c.CanCall[_Xss, _Y], + /, + *args: _Xss.args, + **kwargs: _Xss.kwargs, + ) -> _Y: + return f(*args, **kwargs) +else: + do_call: _d.DoesCall = _o.call # containers and sequences @@ -61,32 +79,32 @@ # `operator.getitem` isn't used, because it has an (unreasonably loose, and # redundant) overload for `(Sequence[T], slice) -> Sequence[T]` # https://github.com/python/typeshed/blob/main/stdlib/_operator.pyi#L84-L86 -def do_getitem[K, V, M]( - obj: _c.CanGetitem[K, V] | _c.CanGetMissing[K, V, M], - key: K, +def do_getitem( + obj: _c.CanGetitem[_K, _V] | _c.CanGetMissing[_K, _V, _D], + key: _K, /, -) -> V | M: +) -> _V | _D: """Same as `value = obj[key]`.""" return obj[key] -def do_setitem[K, V](obj: _c.CanSetitem[K, V], key: K, value: V, /) -> None: +def do_setitem(obj: _c.CanSetitem[_K, _V], key: _K, value: _V, /) -> None: """Same as `obj[key] = value`.""" obj[key] = value -def do_delitem[K](obj: _c.CanDelitem[K], key: K, /) -> None: +def do_delitem(obj: _c.CanDelitem[_K], key: _K, /) -> None: """Same as `del obj[key]`.""" del obj[key] -def do_missing[K, V](obj: _c.CanMissing[K, V], key: K, /) -> V: +def do_missing(obj: _c.CanMissing[_K, _V], key: _K, /) -> _V: return obj.__missing__(key) # `operator.contains` cannot be used, as it incorrectly requires `key` # to be an **invariant** `object` instance... -def do_contains[K](obj: _c.CanContains[K], key: K, /) -> bool: +def do_contains(obj: _c.CanContains[_K], key: _K, /) -> bool: """Same as `key in obj`.""" return key in obj @@ -117,72 +135,73 @@ def do_contains[K](obj: _c.CanContains[K], key: K, /) -> bool: # (a DRY `do_r*` decorator won't work; the overloads get lost during casting to # `CanCall` or `Callable`, within the decorator function signature). -def do_radd[X, Y](a: _c.CanRAdd[X, Y], b: X) -> Y: + +def do_radd(a: _c.CanRAdd[_X, _Y], b: _X) -> _Y: """Same as `b + a`.""" return b + a -def do_rsub[X, Y](a: _c.CanRSub[X, Y], b: X) -> Y: +def do_rsub(a: _c.CanRSub[_X, _Y], b: _X) -> _Y: """Same as `b - a`.""" return b - a -def do_rmul[X, Y](a: _c.CanRMul[X, Y], b: X) -> Y: +def do_rmul(a: _c.CanRMul[_X, _Y], b: _X) -> _Y: """Same as `b * a`.""" return b * a -def do_rmatmul[X, Y](a: _c.CanRMatmul[X, Y], b: X) -> Y: +def do_rmatmul(a: _c.CanRMatmul[_X, _Y], b: _X) -> _Y: """Same as `b @ a`.""" return b @ a -def do_rtruediv[X, Y](a: _c.CanRTruediv[X, Y], b: X) -> Y: +def do_rtruediv(a: _c.CanRTruediv[_X, _Y], b: _X) -> _Y: """Same as `b / a`.""" return b / a -def do_rfloordiv[X, Y](a: _c.CanRFloordiv[X, Y], b: X) -> Y: +def do_rfloordiv(a: _c.CanRFloordiv[_X, _Y], b: _X) -> _Y: """Same as `b // a`.""" return b // a -def do_rmod[X, Y](a: _c.CanRMod[X, Y], b: X) -> Y: +def do_rmod(a: _c.CanRMod[_X, _Y], b: _X) -> _Y: """Same as `b % a`.""" return b % a -def do_rdivmod[X, Y](a: _c.CanRDivmod[X, Y], b: X) -> Y: +def do_rdivmod(a: _c.CanRDivmod[_X, _Y], b: _X) -> _Y: """Same as `divmod(b, a)`.""" return divmod(b, a) -def do_rpow[X, Y](a: _c.CanRPow[X, Y], b: X) -> Y: +def do_rpow(a: _c.CanRPow[_X, _Y], b: _X) -> _Y: """Same as `b ** a`.""" return b ** a -def do_rlshift[X, Y](a: _c.CanRLshift[X, Y], b: X) -> Y: +def do_rlshift(a: _c.CanRLshift[_X, _Y], b: _X) -> _Y: """Same as `b << a`.""" return b << a -def do_rrshift[X, Y](a: _c.CanRRshift[X, Y], b: X) -> Y: +def do_rrshift(a: _c.CanRRshift[_X, _Y], b: _X) -> _Y: """Same as `b >> a`.""" return b >> a -def do_rand[X, Y](a: _c.CanRAnd[X, Y], b: X) -> Y: +def do_rand(a: _c.CanRAnd[_X, _Y], b: _X) -> _Y: """Same as `b & a`.""" return b & a -def do_rxor[X, Y](a: _c.CanRXor[X, Y], b: X) -> Y: +def do_rxor(a: _c.CanRXor[_X, _Y], b: _X) -> _Y: """Same as `b ^ a`.""" return b ^ a -def do_ror[X, Y](a: _c.CanROr[X, Y], b: X) -> Y: +def do_ror(a: _c.CanROr[_X, _Y], b: _X) -> _Y: """Same as `b | a`.""" return b | a diff --git a/optype/_does.py b/optype/_does.py index 6bea5ff..f55083e 100644 --- a/optype/_does.py +++ b/optype/_does.py @@ -1,41 +1,62 @@ -from typing import Any, Protocol, Self, final, overload, override +from typing import ( + Any, + ParamSpec, + Protocol, + TypeAlias, + TypeVar, + final, + overload, +) import optype._can as _c -# iteration +_V = TypeVar('_V') +_D = TypeVar('_D') +_V_next = TypeVar('_V_next', bound=_c.CanNext[Any]) +_V_anext = TypeVar('_V_anext', bound=_c.CanANext[Any]) -class _CanIterNext[V]( - _c.CanIter['_CanIterNext[Any]'], - _c.CanNext[V], - Protocol, -): - @override - def __iter__(self) -> Self: ... +_X = TypeVar('_X') +_X_str = TypeVar('_X_str', bound=str) +_X_optional = TypeVar('_X_optional', bound=object | None) + +_Y = TypeVar('_Y') +_Y_str = TypeVar('_Y_str', bound=str) +_Y_bytes = TypeVar('_Y_bytes', bound=bytes) + +_Xss = ParamSpec('_Xss') + + +# iteration @final class DoesNext(Protocol): # https://docs.python.org/3/library/functions.html#next @overload - def __call__[V](self, __vs: _c.CanNext[V], /) -> V: ... + def __call__(self, __vs: _c.CanNext[_V], /) -> _V: ... @overload - def __call__[V, V0](self, __vs: _c.CanNext[V], __v0: V0, /) -> V | V0: ... + def __call__(self, __vs: _c.CanNext[_V], __v0: _D, /) -> _V | _D: ... @final class DoesIter(Protocol): # https://docs.python.org/3/library/functions.html#iter @overload - def __call__[Vs: _c.CanNext[Any]](self, __vs: _c.CanIter[Vs], /) -> Vs: ... + def __call__(self, __vs: _c.CanIter[_V_next], /) -> _V_next: ... @overload - def __call__[V]( - self, __vs: _c.CanGetitem[_c.CanIndex, V], /, - ) -> _CanIterNext[V]: ... + def __call__( + self, + __vs: _c.CanGetitem[_c.CanIndex, _V], + /, + ) -> _c.CanIterSelf[_V]: ... @overload - def __call__[V, S: object | None]( - self, __f: _c.CanCall[[], V | S], __s: S, /, - ) -> _CanIterNext[V]: ... + def __call__( + self, + __f: _c.CanCall[[], _V | _X_optional], + __s: _X_optional, + /, + ) -> _c.CanIterSelf[_V]: ... # type conversion @@ -67,13 +88,13 @@ def __call__(self, __o: _c.CanComplex, /) -> complex: ... @final class DoesStr(Protocol): # https://docs.python.org/3/library/functions.html#func-str - def __call__[Y: str](self, __o: _c.CanStr[Y], /) -> Y: ... + def __call__(self, __o: _c.CanStr[_Y_str], /) -> _Y_str: ... @final class DoesBytes(Protocol): # https://docs.python.org/3/library/functions.html#func-bytes - def __call__[Y: bytes](self, __o: _c.CanBytes[Y], /) -> Y: ... + def __call__(self, __o: _c.CanBytes[_Y_bytes], /) -> _Y_bytes: ... # formatting @@ -81,15 +102,18 @@ def __call__[Y: bytes](self, __o: _c.CanBytes[Y], /) -> Y: ... @final class DoesRepr(Protocol): # https://docs.python.org/3/library/functions.html#repr - def __call__[Y: str](self, __o: _c.CanRepr[Y], /) -> Y: ... + def __call__(self, __o: _c.CanRepr[_Y_str], /) -> _Y_str: ... @final class DoesFormat(Protocol): # https://docs.python.org/3/library/functions.html#format - def __call__[X: str, Y: str]( - self, __o: _c.CanFormat[X, Y], __x: X = ..., /, - ) -> Y: ... + def __call__( + self, + __o: _c.CanFormat[_X_str, _Y_str], + __x: _X_str = ..., + /, + ) -> _Y_str: ... # rich comparison @@ -97,79 +121,92 @@ def __call__[X: str, Y: str]( @final class DoesLt(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanLt[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanLt[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanGt[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanGt[_X, _Y], /) -> _Y: ... @final class DoesLe(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanLe[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanLe[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanGe[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanGe[_X, _Y], /) -> _Y: ... @final class DoesEq(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanEq[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanEq[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanEq[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanEq[_X, _Y], /) -> _Y: ... @final class DoesNe(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanNe[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanNe[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanNe[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanNe[_X, _Y], /) -> _Y: ... @final class DoesGt(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanGt[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanGt[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanLt[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanLt[_X, _Y], /) -> _Y: ... @final class DoesGe(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanGe[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanGe[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanLe[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanLe[_X, _Y], /) -> _Y: ... # attributes - -type _CanGetAttr[K: str, V] = _c.CanGetattr[K, V] | _c.CanGetattribute[K, V] +_CanGetAttr: TypeAlias = ( + _c.CanGetattr[_X_str, _V] + | _c.CanGetattribute[_X_str, _V] +) @final class DoesGetattr(Protocol): # https://docs.python.org/3/library/functions.html#getattr @overload - def __call__[K: str, V](self, __o: _CanGetAttr[K, V], __k: K, /) -> V: ... + def __call__(self, __o: _CanGetAttr[_X_str, _V], __k: _X_str, /) -> _V: ... @overload - def __call__[K: str, V, V0]( - self, __o: _CanGetAttr[K, V], __k: K, __v0: V0, /, - ) -> V | V0: ... + def __call__( + self, + __o: _CanGetAttr[_X_str, _V], + __k: _X_str, + __v0: _D, + /, + ) -> _V | _D: ... @final class DoesSetattr(Protocol): # https://docs.python.org/3/library/functions.html#setattr - def __call__[K: str, V]( - self, __o: _c.CanSetattr[K, V], __k: K, __v: V, /, + def __call__( + self, + __o: _c.CanSetattr[_X_str, _V], + __k: _X_str, + __v: _V, + /, ) -> None: ... @final class DoesDelattr(Protocol): # https://docs.python.org/3/library/functions.html#delattr - def __call__[K: str](self, __o: _c.CanDelattr[K], __k: K, /) -> None: ... + def __call__(self, __o: _c.CanDelattr[_X_str], __k: _X_str, /) -> None: ... + + +_Vss = TypeVar('_Vss', bound=_c.CanIter[Any]) @final @@ -178,19 +215,19 @@ class DoesDir(Protocol): @overload def __call__(self, /) -> list[str]: ... @overload - def __call__[Vs: _c.CanIter[Any]](self, __o: _c.CanDir[Vs], /) -> Vs: ... + def __call__(self, __o: _c.CanDir[_Vss], /) -> _Vss: ... # callables @final class DoesCall(Protocol): - def __call__[**Xs, Y]( + def __call__( self, - __f: _c.CanCall[Xs, Y], - *__xs: Xs.args, - **__kxs: Xs.kwargs, - ) -> Y: ... + __f: _c.CanCall[_Xss, _Y], + *__xs: _Xss.args, + **__kxs: _Xss.kwargs, + ) -> _Y: ... # containers and subscritable types @@ -206,36 +243,43 @@ class DoesLengthHint(Protocol): def __call__(self, __o: _c.CanLengthHint, /) -> int: ... -type _CanSubscript[K, V, M] = _c.CanGetitem[K, V] | _c.CanGetMissing[K, V, M] +_CanSubscript: TypeAlias = _c.CanGetitem[_X, _V] | _c.CanGetMissing[_X, _V, _D] @final class DoesGetitem(Protocol): - def __call__[K, V, M]( - self, __o: _CanSubscript[K, V, M], __k: K, /, - ) -> V | M: ... + def __call__( + self, + __o: _CanSubscript[_X, _V, _D], + __k: _X, + /, + ) -> _V | _D: ... @final class DoesSetitem(Protocol): - def __call__[K, V]( - self, __o: _c.CanSetitem[K, V], __k: K, __v: V, /, + def __call__( + self, + __o: _c.CanSetitem[_X, _V], + __k: _X, + __v: _V, + /, ) -> None: ... @final class DoesDelitem(Protocol): - def __call__[K](self, __o: _c.CanDelitem[K], __k: K, /) -> None: ... + def __call__(self, __o: _c.CanDelitem[_X], __k: _X, /) -> None: ... @final class DoesMissing(Protocol): - def __call__[K, V](self, __o: _c.CanMissing[K, V], __k: K, /) -> V: ... + def __call__(self, __o: _c.CanMissing[_X, _V], __k: _X, /) -> _V: ... @final class DoesContains(Protocol): - def __call__[K](self, __o: _c.CanContains[K], __k: K, /) -> bool: ... + def __call__(self, __o: _c.CanContains[_X], __k: _X, /) -> bool: ... @final @@ -247,9 +291,9 @@ class DoesReversed(Protocol): https://github.com/python/typeshed/issues/11645 """ @overload - def __call__[Vs](self, __o: _c.CanReversed[Vs], /) -> Vs: ... + def __call__(self, __o: _c.CanReversed[_V], /) -> _V: ... @overload - def __call__[V](self, __o: _c.CanSequence[Any, V], /) -> 'reversed[V]': ... + def __call__(self, __o: _c.CanSequence[Any, _V], /) -> 'reversed[_V]': ... # binary infix operators @@ -257,290 +301,294 @@ def __call__[V](self, __o: _c.CanSequence[Any, V], /) -> 'reversed[V]': ... @final class DoesAdd(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanAdd[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanAdd[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRAdd[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRAdd[_X, _Y], /) -> _Y: ... @final class DoesSub(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanSub[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanSub[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRSub[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRSub[_X, _Y], /) -> _Y: ... @final class DoesMul(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanMul[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanMul[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRMul[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRMul[_X, _Y], /) -> _Y: ... @final class DoesMatmul(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanMatmul[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanMatmul[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRMatmul[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRMatmul[_X, _Y], /) -> _Y: ... @final class DoesTruediv(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanTruediv[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanTruediv[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRTruediv[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRTruediv[_X, _Y], /) -> _Y: ... @final class DoesFloordiv(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanFloordiv[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanFloordiv[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRFloordiv[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRFloordiv[_X, _Y], /) -> _Y: ... @final class DoesMod(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanMod[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanMod[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRMod[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRMod[_X, _Y], /) -> _Y: ... @final class DoesDivmod(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanDivmod[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanDivmod[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRDivmod[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRDivmod[_X, _Y], /) -> _Y: ... @final class DoesPow(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanPow2[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanPow2[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, M, Y]( - self, __o: _c.CanPow3[X, M, Y], __x: X, __mod: M, /, - ) -> Y: ... + def __call__( + self, + __o: _c.CanPow3[_X, _D, _Y], + __x: _X, + __mod: _D, + /, + ) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRPow[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRPow[_X, _Y], /) -> _Y: ... @final class DoesLshift(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanLshift[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanLshift[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRLshift[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRLshift[_X, _Y], /) -> _Y: ... @final class DoesRshift(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanRshift[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRshift[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRRshift[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRRshift[_X, _Y], /) -> _Y: ... @final class DoesAnd(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanAnd[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanAnd[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRAnd[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRAnd[_X, _Y], /) -> _Y: ... @final class DoesXor(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanXor[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanXor[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanRXor[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanRXor[_X, _Y], /) -> _Y: ... @final class DoesOr(Protocol): @overload - def __call__[X, Y](self, __o: _c.CanOr[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanOr[_X, _Y], __x: _X, /) -> _Y: ... @overload - def __call__[X, Y](self, __x: X, __o: _c.CanROr[X, Y], /) -> Y: ... + def __call__(self, __x: _X, __o: _c.CanROr[_X, _Y], /) -> _Y: ... # binary reflected operators @final class DoesRAdd(Protocol): - def __call__[X, Y](self, __o: _c.CanRAdd[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRAdd[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRSub(Protocol): - def __call__[X, Y](self, __o: _c.CanRSub[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRSub[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRMul(Protocol): - def __call__[X, Y](self, __o: _c.CanRMul[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRMul[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRMatmul(Protocol): - def __call__[X, Y](self, __o: _c.CanRMatmul[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRMatmul[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRTruediv(Protocol): - def __call__[X, Y](self, __o: _c.CanRTruediv[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRTruediv[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRFloordiv(Protocol): - def __call__[X, Y](self, __o: _c.CanRFloordiv[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRFloordiv[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRMod(Protocol): - def __call__[X, Y](self, __o: _c.CanRMod[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRMod[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRDivmod(Protocol): - def __call__[X, Y](self, __o: _c.CanRDivmod[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRDivmod[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRPow(Protocol): - def __call__[X, Y](self, __o: _c.CanRPow[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRPow[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRLshift(Protocol): - def __call__[X, Y](self, __o: _c.CanRLshift[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRLshift[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRRshift(Protocol): - def __call__[X, Y](self, __o: _c.CanRRshift[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRRshift[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRAnd(Protocol): - def __call__[X, Y](self, __o: _c.CanRAnd[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRAnd[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesRXor(Protocol): - def __call__[X, Y](self, __o: _c.CanRXor[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanRXor[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesROr(Protocol): - def __call__[X, Y](self, __o: _c.CanROr[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanROr[_X, _Y], __x: _X, /) -> _Y: ... # augmented / in-place operators @final class DoesIAdd(Protocol): - def __call__[X, Y](self, __o: _c.CanIAdd[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIAdd[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesISub(Protocol): - def __call__[X, Y](self, __o: _c.CanISub[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanISub[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesIMul(Protocol): - def __call__[X, Y](self, __o: _c.CanIMul[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIMul[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesIMatmul(Protocol): - def __call__[X, Y](self, __o: _c.CanIMatmul[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIMatmul[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesITruediv(Protocol): - def __call__[X, Y](self, __o: _c.CanITruediv[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanITruediv[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesIFloordiv(Protocol): - def __call__[X, Y](self, __o: _c.CanIFloordiv[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIFloordiv[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesIMod(Protocol): - def __call__[X, Y](self, __o: _c.CanIMod[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIMod[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesIPow(Protocol): - def __call__[X, Y](self, __o: _c.CanIPow[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIPow[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesILshift(Protocol): - def __call__[X, Y](self, __o: _c.CanILshift[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanILshift[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesIRshift(Protocol): - def __call__[X, Y](self, __o: _c.CanIRshift[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIRshift[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesIAnd(Protocol): - def __call__[X, Y](self, __o: _c.CanIAnd[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIAnd[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesIXor(Protocol): - def __call__[X, Y](self, __o: _c.CanIXor[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIXor[_X, _Y], __x: _X, /) -> _Y: ... @final class DoesIOr(Protocol): - def __call__[X, Y](self, __o: _c.CanIOr[X, Y], __x: X, /) -> Y: ... + def __call__(self, __o: _c.CanIOr[_X, _Y], __x: _X, /) -> _Y: ... # unary arithmetic @final class DoesNeg(Protocol): - def __call__[Y](self, __o: _c.CanNeg[Y], /) -> Y: ... + def __call__(self, __o: _c.CanNeg[_Y], /) -> _Y: ... @final class DoesPos(Protocol): - def __call__[Y](self, __o: _c.CanPos[Y], /) -> Y: ... + def __call__(self, __o: _c.CanPos[_Y], /) -> _Y: ... @final class DoesAbs(Protocol): - def __call__[Y](self, __o: _c.CanAbs[Y], /) -> Y: ... + def __call__(self, __o: _c.CanAbs[_Y], /) -> _Y: ... @final class DoesInvert(Protocol): - def __call__[Y](self, __o: _c.CanInvert[Y], /) -> Y: ... + def __call__(self, __o: _c.CanInvert[_Y], /) -> _Y: ... # fingerprinting @final class DoesHash(Protocol): - def __call__[Y](self, __o: _c.CanHash, /) -> int: ... + def __call__(self, __o: _c.CanHash, /) -> int: ... @final class DoesIndex(Protocol): - def __call__[Y](self, __o: _c.CanIndex, /) -> int: ... + def __call__(self, __o: _c.CanIndex, /) -> int: ... # rounding @@ -548,26 +596,26 @@ def __call__[Y](self, __o: _c.CanIndex, /) -> int: ... @final class DoesRound(Protocol): @overload - def __call__[Y](self, __o: _c.CanRound1[Y], /) -> Y: ... + def __call__(self, __o: _c.CanRound1[_Y], /) -> _Y: ... @overload - def __call__[Y](self, __o: _c.CanRound1[Y], __n: None = ..., /) -> Y: ... + def __call__(self, __o: _c.CanRound1[_Y], __n: None = ..., /) -> _Y: ... @overload - def __call__[N, Y](self, __o: _c.CanRound2[N, Y], __n: N, /) -> Y: ... + def __call__(self, __o: _c.CanRound2[_X, _Y], __n: _X, /) -> _Y: ... @final class DoesTrunc(Protocol): - def __call__[Y](self, __o: _c.CanTrunc[Y], /) -> Y: ... + def __call__(self, __o: _c.CanTrunc[_Y], /) -> _Y: ... @final class DoesFloor(Protocol): - def __call__[Y](self, __o: _c.CanFloor[Y], /) -> Y: ... + def __call__(self, __o: _c.CanFloor[_Y], /) -> _Y: ... @final class DoesCeil(Protocol): - def __call__[Y](self, __o: _c.CanCeil[Y], /) -> Y: ... + def __call__(self, __o: _c.CanCeil[_Y], /) -> _Y: ... # async iteration @@ -575,13 +623,16 @@ def __call__[Y](self, __o: _c.CanCeil[Y], /) -> Y: ... @final class DoesANext(Protocol): @overload - def __call__[V](self, __o: _c.CanANext[V], /) -> V: ... + def __call__(self, __o: _c.CanANext[_V], /) -> _V: ... @overload - async def __call__[V, V0]( - self, __o: _c.CanANext[_c.CanAwait[V]], __v0: V0, /, - ) -> V | V0: ... + async def __call__( + self, + __o: _c.CanANext[_c.CanAwait[_V]], + __v0: _D, + /, + ) -> _V | _D: ... @final class DoesAIter(Protocol): - def __call__[Y: _c.CanANext[Any]](self, __o: _c.CanAIter[Y], /) -> Y: ... + def __call__(self, __o: _c.CanAIter[_V_anext], /) -> _V_anext: ... diff --git a/optype/_has.py b/optype/_has.py index 24c4671..3d2c96c 100644 --- a/optype/_has.py +++ b/optype/_has.py @@ -2,29 +2,57 @@ """ Elementary interfaces for special "dunder" attributes. """ +import sys from collections.abc import Callable from dataclasses import Field as _Field from types import CodeType, ModuleType -from typing import Any, ClassVar, Protocol, Self, override, runtime_checkable +from typing import ( + Any, + ClassVar, + ParamSpec, + Protocol, + TypeVar, + runtime_checkable, +) + + +if sys.version_info < (3, 11): + from typing_extensions import Self, TypeVarTuple, Unpack +else: + from typing import Self, TypeVarTuple, Unpack + +if sys.version_info < (3, 12): + from typing_extensions import override +else: + from typing import override from ._can import CanIter as _CanIter +_V = TypeVar('_V') +_V_match_args = TypeVar('_V_match_args', bound=tuple[str, ...] | list[str]) +_V_slots = TypeVar('_V_slots', bound=str | _CanIter[Any]) + +_Xs = TypeVarTuple('_Xs') +_Xss = ParamSpec('_Xss') +_Y = TypeVar('_Y') + + # Instances @runtime_checkable -class HasMatchArgs[Ks: tuple[str, ...] | list[str]](Protocol): - __match_args__: Ks +class HasMatchArgs(Protocol[_V_match_args]): + __match_args__: _V_match_args @runtime_checkable -class HasSlots[S: str | _CanIter[Any]](Protocol): - __slots__: S +class HasSlots(Protocol[_V_slots]): + __slots__: _V_slots @runtime_checkable -class HasDict[V](Protocol): - __dict__: dict[str, V] +class HasDict(Protocol[_V]): + __dict__: dict[str, _V] @runtime_checkable @@ -66,32 +94,35 @@ class HasDoc(Protocol): @runtime_checkable -class HasAnnotations[V](Protocol): - __annotations__: dict[str, V] +class HasAnnotations(Protocol[_V]): + __annotations__: dict[str, _V] @runtime_checkable -class HasTypeParams[*Ps](Protocol): +class HasTypeParams(Protocol[Unpack[_Xs]]): # Note that `*Ps: (TypeVar, ParamSpec, TypeVarTuple)` should hold - __type_params__: tuple[*Ps] + __type_params__: tuple[Unpack[_Xs]] # functions and methods @runtime_checkable -class HasFunc[**Xs, Y](Protocol): - __func__: Callable[Xs, Y] +class HasFunc(Protocol[_Xss, _Y]): + __func__: Callable[_Xss, _Y] @runtime_checkable -class HasWrapped[**Xs, Y](Protocol): - __wrapped__: Callable[Xs, Y] +class HasWrapped(Protocol[_Xss, _Y]): + __wrapped__: Callable[_Xss, _Y] + + +_T_self_co = TypeVar('_T_self_co', bound=object | ModuleType, covariant=True) @runtime_checkable -class HasSelf[T: object | ModuleType](Protocol): +class HasSelf(Protocol[_T_self_co]): @property - def __self__(self) -> T: ... + def __self__(self) -> _T_self_co: ... @runtime_checkable diff --git a/poetry.lock b/poetry.lock index d20abc9..9fc089b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "basedpyright" -version = "1.8.0" +version = "1.10.1" description = "static type checking for Python (but based)" optional = false python-versions = ">=3.8" files = [ - {file = "basedpyright-1.8.0-py3-none-any.whl", hash = "sha256:8f4c2ca09b615840a57ee0e6a440fc0bd3ed5e4b7524d2bcc11bcf4362cc23fa"}, - {file = "basedpyright-1.8.0.tar.gz", hash = "sha256:7c3177f74003e0b5938a243f416ffa5e4cfb75d7d55201078d9dfc4ce0c59cf3"}, + {file = "basedpyright-1.10.1-py3-none-any.whl", hash = "sha256:dc05a093d1f31ccef42c39c2a01efddff31c2b63f71ab06599ccf280011d45d7"}, + {file = "basedpyright-1.10.1.tar.gz", hash = "sha256:c588721755ddfc6400b8e171d4c14eaa0eb05fd9ab4499507278c0d9994b71ca"}, ] [package.dependencies] @@ -64,15 +64,29 @@ files = [ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "filelock" -version = "3.13.3" +version = "3.13.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.3-py3-none-any.whl", hash = "sha256:5ffa845303983e7a0b7ae17636509bc97997d58afeafa72fb141a17b152284cb"}, - {file = "filelock-3.13.3.tar.gz", hash = "sha256:a79895a25bbefdf55d1a2a0a80968f7dbb28edcd6d4234a0afb3f37ecde4b546"}, + {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, + {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, ] [package.extras] @@ -210,9 +224,11 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.4,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] @@ -292,46 +308,68 @@ files = [ [[package]] name = "ruff" -version = "0.3.5" +version = "0.3.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aef5bd3b89e657007e1be6b16553c8813b221ff6d92c7526b7e0227450981eac"}, - {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89b1e92b3bd9fca249153a97d23f29bed3992cff414b222fcd361d763fc53f12"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e55771559c89272c3ebab23326dc23e7f813e492052391fe7950c1a5a139d89"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabc62195bf54b8a7876add6e789caae0268f34582333cda340497c886111c39"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a05f3793ba25f194f395578579c546ca5d83e0195f992edc32e5907d142bfa3"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dfd3504e881082959b4160ab02f7a205f0fadc0a9619cc481982b6837b2fd4c0"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87258e0d4b04046cf1d6cc1c56fadbf7a880cc3de1f7294938e923234cf9e498"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:712e71283fc7d9f95047ed5f793bc019b0b0a29849b14664a60fd66c23b96da1"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532a90b4a18d3f722c124c513ffb5e5eaff0cc4f6d3aa4bda38e691b8600c9f"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:122de171a147c76ada00f76df533b54676f6e321e61bd8656ae54be326c10296"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d80a6b18a6c3b6ed25b71b05eba183f37d9bc8b16ace9e3d700997f00b74660b"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7b6e63194c68bca8e71f81de30cfa6f58ff70393cf45aab4c20f158227d5936"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a759d33a20c72f2dfa54dae6e85e1225b8e302e8ac655773aff22e542a300985"}, - {file = "ruff-0.3.5-py3-none-win32.whl", hash = "sha256:9d8605aa990045517c911726d21293ef4baa64f87265896e491a05461cae078d"}, - {file = "ruff-0.3.5-py3-none-win_amd64.whl", hash = "sha256:dc56bb16a63c1303bd47563c60482a1512721053d93231cf7e9e1c6954395a0e"}, - {file = "ruff-0.3.5-py3-none-win_arm64.whl", hash = "sha256:faeeae9905446b975dcf6d4499dc93439b131f1443ee264055c5716dd947af55"}, - {file = "ruff-0.3.5.tar.gz", hash = "sha256:a067daaeb1dc2baf9b82a32dae67d154d95212080c80435eb052d95da647763d"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, + {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, + {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, + {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, + {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, ] [[package]] name = "setuptools" -version = "69.2.0" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, +] + [[package]] name = "virtualenv" version = "20.25.1" @@ -354,5 +392,5 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" -python-versions = "^3.12" -content-hash = "063b874693be70ee83c46c80b32a7e120e228a3fb5d07954301e27bf7e6ee740" +python-versions = "^3.10" +content-hash = "7d34bbb4c99377fb82bb807e73208e09a3ead2712bf59c3fe1d7b7406cac396d" diff --git a/pyproject.toml b/pyproject.toml index 2d61811..bab716a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,25 +5,42 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "optype" -version = "0.3.1" -description = "Building blocks for precise type hints in Python 3.12+" +version = "0.4.0" +description = "Building blocks for precise & flexible type hints" authors = ["Joren Hammudoglu "] license = "BSD-3-Clause" readme = "README.md" +classifiers = [ + "Development Status :: 4 - Beta", + "Operating System :: OS Independent", + "Typing :: Typed", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +repository = "https://github.com/jorenham/optype/" +documentation = "https://github.com/jorenham/optype?tab=readme-ov-file#optype" +[tool.poetry.urls] +"Bug Tracker" = "https://github.com/jorenham/optype/issues" +"Changelog" = "https://github.com/jorenham/optype/releases" [tool.poetry.dependencies] -python = "^3.12" +python = "^3.10" +typing-extensions = {version = ">=4.5", python = "<3.12"} [tool.poetry.group.dev.dependencies] pre-commit = '^3.7.0' [tool.poetry.group.lint.dependencies] +basedpyright = "^1.10.1" codespell = "^2.2.6" -basedpyright = "^1.8.0" -ruff = "^0.3.5" +ruff = "^0.3.7" +typing-extensions = "*" [tool.poetry.group.test.dependencies] pytest = "^8.1.1" +typing-extensions = "*" [tool.poetry.group.test-github] optional = true @@ -55,12 +72,13 @@ exclude = [ stubPath = "." venvPath = "." venv = ".venv" -pythonVersion = "3.12" +pythonVersion = "3.10" pythonPlatform = "All" typeCheckingMode = "all" -useLibraryCodeForTypes = false reportAny = false reportUnusedCallResult = false +# because of `sys.version_info()` conditionals +reportUnreachable = false [tool.pytest.ini_options] @@ -80,8 +98,8 @@ xfail_strict = true [tool.ruff] -src = ["optype", "tests"] -target-version = "py312" +src = ["optype", "examples", "tests"] +target-version = "py310" line-length = 79 indent-width = 4 show-fixes = true @@ -150,30 +168,27 @@ extend-ignore = [ # flake8-annotations "ANN001", # missing-type-function-argument (deprecated) "ANN002", # missing-type-args (deprecated) - "ANN401", # any-type (unreasonable) + "ANN401", # any-type # flake8-pyi - "PYI036", # bad-exit-annotation (unreasonable) + "PYI036", # bad-exit-annotation # refurb - "FURB118", # reimplemented-operator (unreasonable) + "FURB118", # reimplemented-operator ] [tool.ruff.lint.per-file-ignores] "examples/*" = [ # isort "I001", # unsorted-imports - # flake8-no-pep420 "INP001", # implicit-namespace-package - # pylint "PLR2004", # magic-value-comparison ] "tests/*" = [ # flake8-annotations "ANN201", # missing-return-type - # flake8-self "SLF001", # private-member-access ] @@ -182,8 +197,8 @@ extend-ignore = [ case-sensitive = true combine-as-imports = true known-first-party = ["optype"] -lines-between-types = 0 lines-after-imports = 2 +lines-between-types = 0 [tool.ruff.lint.flake8-quotes] inline-quotes = "single" @@ -193,7 +208,7 @@ strict = true [tool.ruff.lint.pylint] allow-dunder-method-names = [ - "__replace__", # used by `copy.replace` in Python 3.13+ + "__replace__" # used by `copy.replace` in Python 3.13+ ] [tool.ruff.format] diff --git a/tests/helpers.py b/tests/helpers.py index c8fd56b..a24c9c1 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -108,6 +108,7 @@ def get_callable_members(module: ModuleType) -> frozenset[str]: if not name.startswith('_') and callable(cls := getattr(module, name)) and not is_protocol(cls) + and getattr(module, name).__module__ != 'typing' })