From 7082bc479e69683088517e9d6545567c0407aca7 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 12:39:07 +0100 Subject: [PATCH 01/24] obscure workaround for `CanAIterSelf` not being an `AsyncGenerator` subtype --- optype/_can.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optype/_can.py b/optype/_can.py index debdfee..3d79748 100644 --- a/optype/_can.py +++ b/optype/_can.py @@ -658,7 +658,7 @@ def __aiter__(self) -> Y: ... class CanAIterSelf[V](CanANext[V], CanAIter[CanANext[V]], Protocol): """A less inflexible variant of `collections.abc.AsyncIterator[T]`.""" @override - def __aiter__(self) -> Self: ... + def __aiter__(self) -> 'CanAIterSelf[V]': ... # `Self` doesn't work here? # 3.4.4. Asynchronous Context Managers From 981b990024c5fd61c8385ceb0893428d76be5a1a Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 12:43:35 +0100 Subject: [PATCH 02/24] upgrade pyright to 1.1.355 --- .pre-commit-config.yaml | 2 +- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a5b995d..b6660b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/RobertCraigie/pyright-python - rev: v1.1.354 + rev: v1.1.355 hooks: - id: pyright diff --git a/poetry.lock b/poetry.lock index 22fdddc..ee89d5c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -166,13 +166,13 @@ virtualenv = ">=20.10.0" [[package]] name = "pyright" -version = "1.1.354" +version = "1.1.355" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.354-py3-none-any.whl", hash = "sha256:f28d61ae8ae035fc52ded1070e8d9e786051a26a4127bbd7a4ba0399b81b37b5"}, - {file = "pyright-1.1.354.tar.gz", hash = "sha256:b1070dc774ff2e79eb0523fe87f4ba9a90550de7e4b030a2bc9e031864029a1f"}, + {file = "pyright-1.1.355-py3-none-any.whl", hash = "sha256:bf30b6728fd68ae7d09c98292b67152858dd89738569836896df786e52b5fe48"}, + {file = "pyright-1.1.355.tar.gz", hash = "sha256:dca4104cd53d6484e6b1b50b7a239ad2d16d2ffd20030bcf3111b56f44c263bf"}, ] [package.dependencies] @@ -340,4 +340,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "f593bd9370abebdcaaa13ba3f6726024664d3f34432cc8272ae0567f960e172c" +content-hash = "851ccca3976c8b66016eb3f4184f02d06edff9531fa492bba11517d66eb63a69" diff --git a/pyproject.toml b/pyproject.toml index ec76760..fa20291 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ pre-commit = '^3.6.2' [tool.poetry.group.lint.dependencies] codespell = "^2.2.6" -pyright = "^1.1.354" +pyright = "^1.1.355" ruff = "^0.3.3" [tool.poetry.group.test.dependencies] From a6fadcad39418d8857b9f2bc837090d4c2da98c8 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 14:21:20 +0100 Subject: [PATCH 03/24] clean `TODO`'s --- optype/_can.py | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) diff --git a/optype/_can.py b/optype/_can.py index 3d79748..a488df4 100644 --- a/optype/_can.py +++ b/optype/_can.py @@ -689,52 +689,3 @@ def __aexit__[E: BaseException]( @runtime_checkable class CanAsyncWith[V, R](CanAEnter[V], CanAExit[R], Protocol): ... - - -# path-like -# TODO: CanFspath[P: str | bytes] - - -# standard library `copy` -# https://docs.python.org/3/library/copy.html -# TODO: CanCopy -# TODO: CanDeepCopy -# TODO: CanReplace (py313+) - - -# standard library `pickle` -# https://docs.python.org/3/library/pickle.html#pickling-class-instances -# TODO: CanGetnewargsEx -# TODO: CanGetnewargs -# TODO: CanGetstate -# TODO: CanSetstate -# TODO: CanReduce -# TODO: CanReduceEx - - -# 3rd-party library `numpy` -# https://numpy.org/devdocs/reference/arrays.classes.html -# https://numpy.org/devdocs/user/basics.subclassing.html -# TODO: __array__ -# TODO: __array_ufunc__ (this one is pretty awesome) -# TODO: __array_function__ -# TODO: __array_finalize__ -# TODO (maybe): __array_prepare__ -# TODO (maybe): __array_priority__ -# TODO (maybe): __array_wrap__ -# https://numpy.org/doc/stable/reference/arrays.interface.html -# TODO: __array_interface__ -# TODO (maybe): __array_struct__ - - -# Array API -# https://data-apis.org/array-api/latest/API_specification/array_object.html -# TODO: __array_namespace__ -# TODO: __dlpack__ -# TODO: __dlpack_device__ - - -# Dataframe API -# https://data-apis.org/dataframe-api/draft/API_specification/index.html -# TODO: __dataframe_namespace__ -# TODO: __column_namespace__ From 67947c2d9ec7c5e246165adf434e6ccb9ab7702a Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 14:23:58 +0100 Subject: [PATCH 04/24] Cleanup of the "future plans" section --- README.md | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 80186cf..6ee97dc 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ if your type hints are spot-on; `optype` is you friend. format(_, x) - do_format(_, x) + do_format DoesFormat __format__ CanFormat[X: str, Y: str] @@ -727,7 +727,7 @@ Additionally, `optype` also provides their (overloaded) intersection type: round(_) - do_round + do_round/1 DoesRound __round__/1 @@ -737,7 +737,7 @@ Additionally, `optype` also provides their (overloaded) intersection type: round(_, n) - do_round + do_round/2 DoesRound __round__/2 @@ -746,8 +746,11 @@ Additionally, `optype` also provides their (overloaded) intersection type: - round(_, n=None) - do_round(_, n=None) + round(_, n=...) + + do_round/1
+ do_round/2 + DoesRound __round__ CanRound[N, Y1, Y2] @@ -1239,11 +1242,16 @@ Interfaces for emulating buffer types using the [buffer protocol][BP]. ## Future plans -- Support for Python versions before 3.12. -- More standard library protocols, e.g. `copy`, `dataclasses`, `pickle`. -- Typed mixins for DRY implementation of operator, e.g. for comparison ops - `GeFromLt`, `GtFromLe`, etc as a typed alternative for - `functools.total_ordering`. Similarly for numeric types, with e.g. `__add__` - and `__neg__` a mixin could generate `__pos__` and `__sub__`. -- Dependency-free third-party type support, e.g. protocols for `numpy`'s array - interface. +- Support for Python versions before 3.12 (#19). +- More standard library protocols, e.g. + - `copy` (#20) + - `dataclasses` (#21) + - `pickle` (#22). + - `typing.NamedTuple` (#23) +- [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) + +[NP]: https://github.com/numpy/numpy +[API-ND]: https://data-apis.org/array-api/latest/ +[API-DF]: https://data-apis.org/dataframe-api/draft/index.html From 9360ea5612625371fd396de43b591b5610afcaa4 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 14:42:00 +0100 Subject: [PATCH 05/24] denote function arity when relevant --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6ee97dc..4cb4a32 100644 --- a/README.md +++ b/README.md @@ -371,7 +371,7 @@ info. _ ** x
pow(_, x) - do_pow + do_pow/2 DoesPow __pow__ @@ -381,7 +381,7 @@ info. pow(_, x, m) - do_pow + do_pow/3 DoesPow __pow__ @@ -876,14 +876,14 @@ from the abracadabra collections. This is how they are defined: next(_) - do_next + do_next/1 DoesNext __next__ CanNext[V] iter(_) - do_iter + do_iter/1 DoesIter __iter__ CanIter[Vs: CanNext] From 7353246ea4a5dfac9804fba9d9621d30d6928c04 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 16:04:45 +0100 Subject: [PATCH 06/24] add note to self on why not to use `operator.*item` --- optype/_do.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/optype/_do.py b/optype/_do.py index 534f2d9..64b1dd6 100644 --- a/optype/_do.py +++ b/optype/_do.py @@ -65,8 +65,12 @@ do_length_hint: _d.DoesLengthHint = _o.length_hint +# `operator.getitem` isn't used, because it has an (unreasonably loose, and +# redundant) overload for `(Sequence[T], slice) -> Sequence[T]` def do_getitem[K, V, M]( - obj: _c.CanGetitem[K, V] | _c.CanGetMissing[K, V, M], key: K, /, + obj: _c.CanGetitem[K, V] | _c.CanGetMissing[K, V, M], + key: K, + /, ) -> V | M: """Same as `value = obj[key]`.""" return obj[key] From ca0391cdf43f7be17adf9fc18097c09527b39c00 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 16:07:54 +0100 Subject: [PATCH 07/24] add note to self on why not to use `operator.contains` --- optype/_do.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/optype/_do.py b/optype/_do.py index 64b1dd6..b058ac7 100644 --- a/optype/_do.py +++ b/optype/_do.py @@ -86,6 +86,8 @@ def do_delitem[K](obj: _c.CanDelitem[K], key: K, /) -> None: del obj[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: """Same as `key in obj`.""" return key in obj From 129667bad41cfdb84031bacf7e1fc2d25943b8cc Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 16:27:52 +0100 Subject: [PATCH 08/24] add `CanSequence` as the intersection of `CanLen` and `CanGetitem[K: CanIndex, V]` --- optype/_can.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/optype/_can.py b/optype/_can.py index a488df4..b33f876 100644 --- a/optype/_can.py +++ b/optype/_can.py @@ -268,6 +268,17 @@ def __missing__(self, __k: K) -> V: ... class CanGetMissing[K, V, M](CanGetitem[K, V], CanMissing[K, M], Protocol): ... +@runtime_checkable +class CanSequence[I: 'CanIndex', V](CanLen, CanGetitem[I, V], Protocol): + """ + A sequence is an object with a __len__ method and a + __getitem__ method that takes int(-like) argument as key (the index). + Additionally, it is expected to be 0-indexed (the first element is at + index 0) and "dense" (i.e. the indices are consecutive integers, and are + obtainable with e.g. `range(len(_))`). + """ + + # 3.3.8. Emulating numeric types # https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types @@ -425,8 +436,8 @@ def __rxor__(self, __x: X, /) -> Y: ... class CanROr[X, Y](Protocol): def __ror__(self, __x: X, /) -> Y: ... -# augmented / in-place +# augmented / in-place @runtime_checkable class CanIAdd[X, Y](Protocol): @@ -493,8 +504,8 @@ def __ixor__(self, __x: X, /) -> Y: ... class CanIOr[X, Y](Protocol): def __ior__(self, __x: X, /) -> Y: ... -# unary arithmetic +# unary arithmetic @runtime_checkable class CanNeg[Y](Protocol): @@ -515,8 +526,8 @@ def __abs__(self) -> Y: ... class CanInvert[Y](Protocol): def __invert__(self) -> Y: ... -# numeric conversion +# numeric conversion @runtime_checkable class CanComplex(Protocol): From e762c2a8d4ce5090973518a0525be07937b9186e Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 17:41:12 +0100 Subject: [PATCH 09/24] add `do_reversed` and `DoesReversed` --- README.md | 10 +++++++--- optype/__init__.py | 6 ++++++ optype/_do.py | 6 ++++++ optype/_does.py | 14 ++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4cb4a32..2b2a542 100644 --- a/README.md +++ b/README.md @@ -1015,10 +1015,10 @@ Additionally, there is `optype.CanAIterSelf[V]`, with both the reversed(_) - - + do_reversed + DoesReversed __reversed__ - CanReversed[Y] + CanReversed[Vs] | CanSequence[V] @@ -1026,6 +1026,10 @@ Because `CanMissing[K, M]` generally doesn't show itself without `CanGetitem[K, V]` there to hold its hand, `optype` conveniently stitched them together as `optype.CanGetMissing[K, V, M]`. +Similarly, there is `optype.CanSequence[I: CanIndex, V]`, which is the +combination of both `CanLen` and `CanItem[I, V]`, and serves as a more +specific and flexible `collections.abc.Sequence[V]`. + Additionally, `optype` provides protocols for types with (custom) *hash* or *index* methods: diff --git a/optype/__init__.py b/optype/__init__.py index 48637af..95db81d 100644 --- a/optype/__init__.py +++ b/optype/__init__.py @@ -92,6 +92,7 @@ 'CanRound1', 'CanRound2', 'CanRshift', + 'CanSequence', 'CanSet', 'CanSetName', 'CanSetattr', @@ -173,6 +174,7 @@ 'DoesRTruediv', 'DoesRXor', 'DoesRepr', + 'DoesReversed', 'DoesRound', 'DoesRshift', 'DoesSetattr', @@ -258,6 +260,7 @@ 'do_rand', 'do_rdivmod', 'do_repr', + 'do_reversed', 'do_rfloordiv', 'do_rlshift', 'do_rmatmul', @@ -376,6 +379,7 @@ CanRound1, CanRound2, CanRshift, + CanSequence, CanSet, CanSetName, CanSetattr, @@ -448,6 +452,7 @@ do_rand, do_rdivmod, do_repr, + do_reversed, do_rfloordiv, do_rlshift, do_rmatmul, @@ -541,6 +546,7 @@ DoesRTruediv, DoesRXor, DoesRepr, + DoesReversed, DoesRound, DoesRshift, DoesSetattr, diff --git a/optype/_do.py b/optype/_do.py index b058ac7..c0e2b99 100644 --- a/optype/_do.py +++ b/optype/_do.py @@ -67,6 +67,7 @@ # `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, @@ -93,6 +94,11 @@ def do_contains[K](obj: _c.CanContains[K], key: K, /) -> bool: return key in obj +# `builtins.reversed` is annotated incorrectly within typeshed: +# https://github.com/python/typeshed/issues/11645 +do_reversed: _d.DoesReversed = reversed # pyright: ignore[reportAssignmentType] + + # infix ops do_add: _d.DoesAdd = _o.add do_sub: _d.DoesSub = _o.sub diff --git a/optype/_does.py b/optype/_does.py index fa85b20..e9fae2f 100644 --- a/optype/_does.py +++ b/optype/_does.py @@ -233,6 +233,20 @@ class DoesContains(Protocol): def __call__[K](self, __o: _c.CanContains[K], __k: K, /) -> bool: ... +@final +class DoesReversed(Protocol): + """ + This is correct type of `builtins.reversed`. + + Note that typeshed's annotations for `reversed` are completely wrong: + https://github.com/python/typeshed/issues/11645 + """ + @overload + def __call__[Vs](self, __o: _c.CanReversed[Vs], /) -> Vs: ... + @overload + def __call__[V](self, __o: _c.CanSequence[Any, V], /) -> reversed[V]: ... + + # binary infix operators @final From 12e92ed1a9ed0e848b78433355880890aab66295 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 17:48:36 +0100 Subject: [PATCH 10/24] add `do_missing` and `DoesMissing` --- README.md | 4 ++-- optype/__init__.py | 4 ++++ optype/_do.py | 5 +++++ optype/_does.py | 5 +++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b2a542..8b5bbb8 100644 --- a/README.md +++ b/README.md @@ -987,8 +987,8 @@ Additionally, there is `optype.CanAIterSelf[V]`, with both the _.__missing__() (docs) - - + do_missing + DoesMissing __missing__ CanMissing[K, V] diff --git a/optype/__init__.py b/optype/__init__.py index 95db81d..273c175 100644 --- a/optype/__init__.py +++ b/optype/__init__.py @@ -151,6 +151,7 @@ 'DoesLshift', 'DoesLt', 'DoesMatmul', + 'DoesMissing', 'DoesMod', 'DoesMul', 'DoesNe', @@ -248,6 +249,7 @@ 'do_lshift', 'do_lt', 'do_matmul', + 'do_missing', 'do_mod', 'do_mul', 'do_ne', @@ -440,6 +442,7 @@ do_lshift, do_lt, do_matmul, + do_missing, do_mod, do_mul, do_ne, @@ -523,6 +526,7 @@ DoesLshift, DoesLt, DoesMatmul, + DoesMissing, DoesMod, DoesMul, DoesNe, diff --git a/optype/_do.py b/optype/_do.py index c0e2b99..19767b7 100644 --- a/optype/_do.py +++ b/optype/_do.py @@ -87,6 +87,10 @@ def do_delitem[K](obj: _c.CanDelitem[K], key: K, /) -> None: del obj[key] +def do_missing[K, V](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: @@ -233,6 +237,7 @@ def do_ror[X, Y](a: _c.CanROr[X, Y], b: X) -> Y: _do_getitem: _d.DoesGetitem = do_getitem _do_setitem: _d.DoesSetitem = do_setitem _do_delitem: _d.DoesDelitem = do_delitem + _do_missing: _d.DoesMissing = do_missing _do_contains: _d.DoesContains = do_contains _do_radd: _d.DoesRAdd = do_radd diff --git a/optype/_does.py b/optype/_does.py index e9fae2f..b7a2aba 100644 --- a/optype/_does.py +++ b/optype/_does.py @@ -228,6 +228,11 @@ class DoesDelitem(Protocol): def __call__[K](self, __o: _c.CanDelitem[K], __k: K, /) -> None: ... +@final +class DoesMissing(Protocol): + def __call__[K, V](self, __o: _c.CanMissing[K, V], __k: K, /) -> V: ... + + @final class DoesContains(Protocol): def __call__[K](self, __o: _c.CanContains[K], __k: K, /) -> bool: ... From 00b1a22e49d516d38c990754560a0ab6ede2c4ba Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 17:52:15 +0100 Subject: [PATCH 11/24] workaround for unsubscriptable `reversed` at runtime --- optype/_does.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optype/_does.py b/optype/_does.py index b7a2aba..6bea5ff 100644 --- a/optype/_does.py +++ b/optype/_does.py @@ -249,7 +249,7 @@ class DoesReversed(Protocol): @overload def __call__[Vs](self, __o: _c.CanReversed[Vs], /) -> Vs: ... @overload - def __call__[V](self, __o: _c.CanSequence[Any, V], /) -> reversed[V]: ... + def __call__[V](self, __o: _c.CanSequence[Any, V], /) -> 'reversed[V]': ... # binary infix operators From a243b75b11acd712551c47e6cb5e4542d32d4cc6 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 18:22:06 +0100 Subject: [PATCH 12/24] add `CanCopy(Self)`, `CanDeepcopy(Self)` and `CanReplace(Self)` --- optype/__init__.py | 12 ++++++++++++ optype/_can.py | 42 ++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 17 +++++++++++------ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/optype/__init__.py b/optype/__init__.py index 273c175..c145f26 100644 --- a/optype/__init__.py +++ b/optype/__init__.py @@ -16,6 +16,10 @@ 'CanCeil', 'CanComplex', 'CanContains', + 'CanCopy', + 'CanCopySelf', + 'CanDeepcopy', + 'CanDeepcopySelf', 'CanDelattr', 'CanDelete', 'CanDelitem', @@ -86,6 +90,8 @@ 'CanRTruediv', 'CanRXor', 'CanReleaseBuffer', + 'CanReplace', + 'CanReplaceSelf', 'CanRepr', 'CanReversed', 'CanRound', @@ -305,6 +311,10 @@ CanCeil, CanComplex, CanContains, + CanCopy, + CanCopySelf, + CanDeepcopy, + CanDeepcopySelf, CanDelattr, CanDelete, CanDelitem, @@ -375,6 +385,8 @@ CanRTruediv, CanRXor, CanReleaseBuffer, + CanReplace, + CanReplaceSelf, CanRepr, CanReversed, CanRound, diff --git a/optype/_can.py b/optype/_can.py index b33f876..98d6b4a 100644 --- a/optype/_can.py +++ b/optype/_can.py @@ -700,3 +700,45 @@ def __aexit__[E: BaseException]( @runtime_checkable class CanAsyncWith[V, R](CanAEnter[V], CanAExit[R], Protocol): ... + + +# `copy` stdlib +# https://docs.python.org/3.13/library/copy.html + + +@runtime_checkable +class CanCopy[T](Protocol): + """Support for creating shallow copies through `copy.copy`.""" + def __copy__(self) -> T: ... + + +@runtime_checkable +class CanDeepcopy[T](Protocol): + """Support for creating deep copies through `copy.deepcopy`.""" + def __deepcopy__(self, memo: dict[int, Any], /) -> T: ... + + +@runtime_checkable +class CanReplace[T, V](Protocol): + """Support for `copy.replace` in Python 3.13+.""" + def __replace__(self, /, **changes: V) -> T: ... + + +@runtime_checkable +class CanCopySelf(CanCopy['CanCopySelf'], Protocol): + """Variant of `CanCopy` that returns `Self` (as it should).""" + @override + def __copy__(self) -> Self: ... + + +class CanDeepcopySelf(CanDeepcopy['CanDeepcopySelf'], Protocol): + """Variant of `CanDeepcopy` that returns `Self` (as it should).""" + @override + def __deepcopy__(self, memo: dict[int, Any], /) -> Self: ... + + +@runtime_checkable +class CanReplaceSelf[V](CanReplace['CanReplaceSelf[Any]', V], Protocol): + """Variant of `CanReplace` that returns `Self`.""" + @override + def __replace__(self, /, **changes: V) -> Self: ... diff --git a/pyproject.toml b/pyproject.toml index fa20291..05adc53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -198,12 +198,6 @@ extend-ignore = [ "SLF001", # private-member-access ] -[tool.ruff.lint.flake8-quotes] -inline-quotes = "single" - -[tool.ruff.lint.flake8-type-checking] -strict = true - [tool.ruff.lint.isort] case-sensitive = true combine-as-imports = true @@ -212,6 +206,17 @@ known-first-party = ["optype"] lines-between-types = 0 lines-after-imports = 2 +[tool.ruff.lint.flake8-quotes] +inline-quotes = "single" + +[tool.ruff.lint.flake8-type-checking] +strict = true + +[tool.ruff.lint.pylint] +allow-dunder-method-names = [ + "__replace__", # used by `copy.replace` in Python 3.13+ +] + [tool.ruff.format] # keep in sync with .editorconfig line-ending = "lf" From f280155007d18d91efdae592e1b4fd7fad8046ed Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 18:34:41 +0100 Subject: [PATCH 13/24] document the `copy` interfaces --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b5bbb8..458affa 100644 --- a/README.md +++ b/README.md @@ -1244,11 +1244,53 @@ Interfaces for emulating buffer types using the [buffer protocol][BP]. [BP]: https://docs.python.org/3/reference/datamodel.html#python-buffer-protocol +### `copy` + +For the [`copy`][CP] standard library, `optype` provides the following +interfaces: + + + + + + + + + + + + + + + + + + + + + + + + + + +
operatoroperand
expressionmethod(s)type
copy.copy(_)__copy__CanCopy[T]
copy.deepcopy(_, memo={})__deepcopy__CanDeepcopy[T]
copy.replace(_, **changes: V) (Python 3.13+)__replace__CanReplace[T, V]
+ +And for convenience, there are the runtime-checkable aliases for all three +interfaces, with `T` bound to `Self`. These are roughly equivalent to: + +```python +type CanCopySelf = CanCopy[CanCopySelf] +type CanDeepcopySelf = CanDeepcopy[CanDeepcopySelf] +type CanReplaceSelf[V] = CanReplace[CanReplaceSelf[V], V] +``` + +[CP]: https://docs.python.org/3.13/library/copy.html + ## Future plans - Support for Python versions before 3.12 (#19). - More standard library protocols, e.g. - - `copy` (#20) - `dataclasses` (#21) - `pickle` (#22). - `typing.NamedTuple` (#23) From f3cdb769861951463699be35748db3be865718dc Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 18:58:44 +0100 Subject: [PATCH 14/24] take overridden methods into account during testing --- tests/test_protocols.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_protocols.py b/tests/test_protocols.py index 0116625..796158e 100644 --- a/tests/test_protocols.py +++ b/tests/test_protocols.py @@ -86,12 +86,25 @@ def test_name_matches_dunder(cls: type): members = get_protocol_members(cls) assert members - member_count = len(members) + own_members: frozenset[str] parents = list(filter(is_protocol, cls.mro()[1:])) + # parents = [is_protocol(parent) for parent in cls.mro()[1:]] + if parents: + overridden = { + member for member in members + if callable(f := getattr(cls, member)) + and getattr(f, '__override__', False) + } + own_members = members - overridden + else: + own_members = members + + member_count = len(members) + own_member_count = len(own_members) # this test should probably be split up... - if member_count > 1: + if member_count > min(1, own_member_count): # ensure #parent protocols == #members (including inherited) assert len(parents) == member_count From 384bca31923f2e01ccc144e2da921b939963f62c Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 19:42:48 +0100 Subject: [PATCH 15/24] interfaces for the 6 special pickle method --- optype/__init__.py | 12 ++++++++++++ optype/_can.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/optype/__init__.py b/optype/__init__.py index c145f26..2f27120 100644 --- a/optype/__init__.py +++ b/optype/__init__.py @@ -38,6 +38,9 @@ 'CanGetattr', 'CanGetattribute', 'CanGetitem', + 'CanGetnewargs', + 'CanGetnewargsEx', + 'CanGetstate', 'CanGt', 'CanHash', 'CanIAdd', @@ -89,6 +92,8 @@ 'CanRSub', 'CanRTruediv', 'CanRXor', + 'CanReduce', + 'CanReduceEx', 'CanReleaseBuffer', 'CanReplace', 'CanReplaceSelf', @@ -103,6 +108,7 @@ 'CanSetName', 'CanSetattr', 'CanSetitem', + 'CanSetstate', 'CanStr', 'CanSub', 'CanTruediv', @@ -333,6 +339,9 @@ CanGetattr, CanGetattribute, CanGetitem, + CanGetnewargs, + CanGetnewargsEx, + CanGetstate, CanGt, CanHash, CanIAdd, @@ -384,6 +393,8 @@ CanRSub, CanRTruediv, CanRXor, + CanReduce, + CanReduceEx, CanReleaseBuffer, CanReplace, CanReplaceSelf, @@ -398,6 +409,7 @@ CanSetName, CanSetattr, CanSetitem, + CanSetstate, CanStr, CanSub, CanTruediv, diff --git a/optype/_can.py b/optype/_can.py index 98d6b4a..49a04f3 100644 --- a/optype/_can.py +++ b/optype/_can.py @@ -742,3 +742,42 @@ class CanReplaceSelf[V](CanReplace['CanReplaceSelf[Any]', V], Protocol): """Variant of `CanReplace` that returns `Self`.""" @override def __replace__(self, /, **changes: V) -> Self: ... + + +# `pickle` stdlib +# https://docs.python.org/3.13/library/pickle.html + + +@runtime_checkable +class CanReduce[R: str | tuple[Any, ...]](Protocol): + @override + def __reduce__(self) -> R: ... + + +@runtime_checkable +class CanReduceEx[R: str | tuple[Any, ...]](Protocol): + @override + def __reduce_ex__(self, protocol: CanIndex) -> R: ... + + +@runtime_checkable +class CanGetstate[S: object](Protocol): + @override + def __getstate__(self) -> S: ... + + +@runtime_checkable +class CanSetstate[S: object](Protocol): + def __setstate__(self, state: S, /) -> S: ... + + +@runtime_checkable +class CanGetnewargs[*Args](Protocol): + def __new__(cls, *__args: *Args) -> Self: ... + def __getnewargs_ex__(self) -> tuple[*Args]: ... + + +@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]]: ... From 529e25bb8d2099f9dd695829306270e5ad2418d5 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 20:06:19 +0100 Subject: [PATCH 16/24] several signature fixes in the pickle interfaces --- optype/_can.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/optype/_can.py b/optype/_can.py index 49a04f3..97fb542 100644 --- a/optype/_can.py +++ b/optype/_can.py @@ -757,7 +757,7 @@ def __reduce__(self) -> R: ... @runtime_checkable class CanReduceEx[R: str | tuple[Any, ...]](Protocol): @override - def __reduce_ex__(self, protocol: CanIndex) -> R: ... + def __reduce_ex__(self, protocol: CanIndex, /) -> R: ... @runtime_checkable @@ -768,13 +768,13 @@ def __getstate__(self) -> S: ... @runtime_checkable class CanSetstate[S: object](Protocol): - def __setstate__(self, state: S, /) -> S: ... + def __setstate__(self, state: S, /) -> None: ... @runtime_checkable class CanGetnewargs[*Args](Protocol): def __new__(cls, *__args: *Args) -> Self: ... - def __getnewargs_ex__(self) -> tuple[*Args]: ... + def __getnewargs__(self) -> tuple[*Args]: ... @runtime_checkable From 826cc1bddac82cb481dee91b5730013d525035e9 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 20:09:29 +0100 Subject: [PATCH 17/24] documented the pickle interfaces --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 458affa..726a739 100644 --- a/README.md +++ b/README.md @@ -1256,7 +1256,7 @@ interfaces: expression - method(s) + method type @@ -1285,14 +1285,70 @@ type CanDeepcopySelf = CanDeepcopy[CanDeepcopySelf] type CanReplaceSelf[V] = CanReplace[CanReplaceSelf[V], V] ``` -[CP]: https://docs.python.org/3.13/library/copy.html +[CP]: https://docs.python.org/3/library/copy.html + +### `pickle` + +For the [`pickle`][PK] standard library, `optype` provides the following +interfaces: + +[PK]: https://docs.python.org/3/library/pickle.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
method(s)signature (bound)type
__reduce__() -> RCanReduce[R: str | tuple]
__reduce_ex__(CanIndex) -> RCanReduceEx[R: str | tuple]
__getstate__() -> StateCanGetstate[State: object]
__setstate__(State) -> NoneCanSetstate[State: object]
+ __getnewargs__
+ __new__ +
+ () -> tuple[*Args]
+ (*Args) -> Self
+
CanGetnewargs[*Args]
+ __getnewargs_ex__
+ __new__ +
+ () -> tuple[tuple[*Args], dict[str, Kw]]
+ (*Args, **dict[str, Kw]) -> Self
+
CanGetnewargsEx[*Args, Kw]
## Future plans - Support for Python versions before 3.12 (#19). - More standard library protocols, e.g. - `dataclasses` (#21) - - `pickle` (#22). - `typing.NamedTuple` (#23) - [numpy][NP] interfaces for arrays-like types (no deps) (#24) - [array-api][API-ND] interfaces (no deps) (#25) From 06adf08c4a4416910e0d4aeb6ab78321d7a238a4 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 20:17:21 +0100 Subject: [PATCH 18/24] fix tests by excluding `__new__`, narrowly escaping from its black magic --- tests/helpers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/helpers.py b/tests/helpers.py index c6776d4..c8fd56b 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -35,8 +35,11 @@ def get_protocol_members(cls: type) -> frozenset[str]: members = annotations.keys() | { name for name, v in vars(cls).items() if ( - callable(v) and ( - v.__module__ == module or ( + name != '__new__' + and callable(v) + and ( + v.__module__ == module + or ( # Fun fact: Each `@overload` returns the same dummy # function; so there's no reference your wrapped method :). # Oh and BTW; `typing.get_overloads` only works on the From a8d19d8893e32804f46835952a9eb555d694b609 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 20:31:06 +0100 Subject: [PATCH 19/24] upgrade to ruff 0.3.4 --- poetry.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index ee89d5c..93491d5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -277,28 +277,28 @@ files = [ [[package]] name = "ruff" -version = "0.3.3" +version = "0.3.4" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:973a0e388b7bc2e9148c7f9be8b8c6ae7471b9be37e1cc732f8f44a6f6d7720d"}, - {file = "ruff-0.3.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfa60d23269d6e2031129b053fdb4e5a7b0637fc6c9c0586737b962b2f834493"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eca7ff7a47043cf6ce5c7f45f603b09121a7cc047447744b029d1b719278eb5"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7d3f6762217c1da954de24b4a1a70515630d29f71e268ec5000afe81377642d"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b24c19e8598916d9c6f5a5437671f55ee93c212a2c4c569605dc3842b6820386"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5a6cbf216b69c7090f0fe4669501a27326c34e119068c1494f35aaf4cc683778"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352e95ead6964974b234e16ba8a66dad102ec7bf8ac064a23f95371d8b198aab"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d6ab88c81c4040a817aa432484e838aaddf8bfd7ca70e4e615482757acb64f8"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79bca3a03a759cc773fca69e0bdeac8abd1c13c31b798d5bb3c9da4a03144a9f"}, - {file = "ruff-0.3.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2700a804d5336bcffe063fd789ca2c7b02b552d2e323a336700abb8ae9e6a3f8"}, - {file = "ruff-0.3.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd66469f1a18fdb9d32e22b79f486223052ddf057dc56dea0caaf1a47bdfaf4e"}, - {file = "ruff-0.3.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45817af234605525cdf6317005923bf532514e1ea3d9270acf61ca2440691376"}, - {file = "ruff-0.3.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0da458989ce0159555ef224d5b7c24d3d2e4bf4c300b85467b08c3261c6bc6a8"}, - {file = "ruff-0.3.3-py3-none-win32.whl", hash = "sha256:f2831ec6a580a97f1ea82ea1eda0401c3cdf512cf2045fa3c85e8ef109e87de0"}, - {file = "ruff-0.3.3-py3-none-win_amd64.whl", hash = "sha256:be90bcae57c24d9f9d023b12d627e958eb55f595428bafcb7fec0791ad25ddfc"}, - {file = "ruff-0.3.3-py3-none-win_arm64.whl", hash = "sha256:0171aab5fecdc54383993389710a3d1227f2da124d76a2784a7098e818f92d61"}, - {file = "ruff-0.3.3.tar.gz", hash = "sha256:38671be06f57a2f8aba957d9f701ea889aa5736be806f18c0cd03d6ff0cbca8d"}, + {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"}, + {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"}, + {file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"}, + {file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"}, + {file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"}, + {file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"}, + {file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"}, + {file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"}, + {file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"}, + {file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"}, ] [[package]] From 986fcde822931dd2095e5c156e350c3d66b683b7 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 20:32:05 +0100 Subject: [PATCH 20/24] pin ruff to 0.3.4 --- .pre-commit-config.yaml | 2 +- poetry.lock | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b6660b3..1b01609 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: codespell - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.3 + rev: v0.3.4 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/poetry.lock b/poetry.lock index 93491d5..2d4628a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -340,4 +340,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "851ccca3976c8b66016eb3f4184f02d06edff9531fa492bba11517d66eb63a69" +content-hash = "8edd41441e33d08bc99baf4e572c8a12b2dd3598532bb0a079c67787660e4981" diff --git a/pyproject.toml b/pyproject.toml index 05adc53..33099e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ pre-commit = '^3.6.2' [tool.poetry.group.lint.dependencies] codespell = "^2.2.6" pyright = "^1.1.355" -ruff = "^0.3.3" +ruff = "^0.3.4" [tool.poetry.group.test.dependencies] pytest = "^8.1.1" From 61aa2f5c1e9cd94f9e24aa1288f0668a2d3510a4 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 20:53:40 +0100 Subject: [PATCH 21/24] add `HasDataclassFields` --- optype/__init__.py | 2 ++ optype/_has.py | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/optype/__init__.py b/optype/__init__.py index 2f27120..0cf7148 100644 --- a/optype/__init__.py +++ b/optype/__init__.py @@ -200,6 +200,7 @@ 'HasAnnotations', 'HasClass', 'HasCode', + 'HasDataclassFields', 'HasDict', 'HasDoc', 'HasFunc', @@ -589,6 +590,7 @@ HasAnnotations, HasClass, HasCode, + HasDataclassFields, HasDict, HasDoc, HasFunc, diff --git a/optype/_has.py b/optype/_has.py index 92684b0..24c4671 100644 --- a/optype/_has.py +++ b/optype/_has.py @@ -2,13 +2,12 @@ """ Elementary interfaces for special "dunder" attributes. """ +from collections.abc import Callable +from dataclasses import Field as _Field from types import CodeType, ModuleType -from typing import Any, Protocol, Self, override, runtime_checkable +from typing import Any, ClassVar, Protocol, Self, override, runtime_checkable -from ._can import ( - CanCall as _CanCall, - CanIter as _CanIter, -) +from ._can import CanIter as _CanIter # Instances @@ -81,12 +80,12 @@ class HasTypeParams[*Ps](Protocol): @runtime_checkable class HasFunc[**Xs, Y](Protocol): - __func__: _CanCall[Xs, Y] + __func__: Callable[Xs, Y] @runtime_checkable class HasWrapped[**Xs, Y](Protocol): - __wrapped__: _CanCall[Xs, Y] + __wrapped__: Callable[Xs, Y] @runtime_checkable @@ -103,4 +102,7 @@ class HasCode(Protocol): # Module `dataclasses` # https://docs.python.org/3/library/dataclasses.html -# TODO: HasDataclassFields +@runtime_checkable +class HasDataclassFields(Protocol): + """Can be used to check whether a type or instance is a dataclass.""" + __dataclass_fields__: ClassVar[dict[str, _Field[Any]]] From 196d80863aba9f77b6b9356d7f2cffaea1ee4a86 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 20:57:29 +0100 Subject: [PATCH 22/24] document `HasDataclassFields` --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 726a739..e1d573d 100644 --- a/README.md +++ b/README.md @@ -1344,6 +1344,15 @@ interfaces: +### `dataclasses` + +For the [`dataclasses`][DC] standard library, `optype` provides the +`optype.HasDataclassFields` interface +It can conveniently be used to check whether a type or instance is a +dataclass, i.e. `isinstance(obj, optype.HasDataclassFields)`. + +[DC]: https://docs.python.org/3/library/dataclasses.html + ## Future plans - Support for Python versions before 3.12 (#19). From ff7c677a8238fcc47deaa6e5c55b8ed7e5edbe9a Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 21:05:09 +0100 Subject: [PATCH 23/24] remove implemented future plans --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index e1d573d..3eda07b 100644 --- a/README.md +++ b/README.md @@ -1356,9 +1356,6 @@ dataclass, i.e. `isinstance(obj, optype.HasDataclassFields)`. ## Future plans - Support for Python versions before 3.12 (#19). -- More standard library protocols, e.g. - - `dataclasses` (#21) - - `typing.NamedTuple` (#23) - [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) From 3bfe22802e4763a5e2825a007613f2c072e42c41 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 21 Mar 2024 21:05:37 +0100 Subject: [PATCH 24/24] bump to 0.3.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 33099e7..93b1d63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "optype" -version = "0.2.2" +version = "0.3.0" description = "Building blocks for precise & flexible Python type hints" authors = ["Joren Hammudoglu "] license = "BSD-3-Clause"