Skip to content

Commit

Permalink
Merge branch 'release/0.2.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
jorenham committed Mar 16, 2024
2 parents d625ab2 + 308a910 commit 225d32e
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ repos:
- id: codespell

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.2
rev: v0.3.3
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,12 @@ from the abracadabra collections. This is how they are defined:
</tr>
</table>

For the sake of compatibility with `collections.abc`, there is
`optype.CanIterSelf[T]`, which is a protocol whose `__iter__` returns
`typing.Self`, as well as a `__next__` method that returns `T`.
I.e. it is equivalent to `collections.abc.Iterator[T]`, but without the `abc`
nonsense.

### Async Iteration

Yes, you guessed it right; the abracadabra collections made the exact same
Expand Down Expand Up @@ -918,7 +924,7 @@ But fret not; the `optype` alternatives are right here:
<td><code>do_aiter</code></td>
<td><code>DoesAIter</code></td>
<td><code>__aiter__</code></td>
<td><code>CanANext[Vs: CanAnext]</code></td>
<td><code>CanAIter[Vs: CanAnext]</code></td>
</tr>
</table>

Expand All @@ -930,6 +936,9 @@ liberal). For details, see the discussion at [python/typeshed#7491][AN].
Just because something is legal, doesn't mean it's a good idea (don't eat the
yellow snow).

Additionally, there is `optype.CanAIterSelf[V]`, with both the
`__aiter__() -> Self` and the `__anext__() -> V` methods.

[AN]: https://github.com/python/typeshed/pull/7491

### Containers
Expand All @@ -948,8 +957,8 @@ yellow snow).
</tr>
<tr>
<td><code>len(_)</code></td>
<td></td>
<td></td>
<td><code>do_len</code></td>
<td><code>DoesLen</code></td>
<td><code>__len__</code></td>
<td><code>CanLen</code></td>
</tr>
Expand All @@ -958,8 +967,8 @@ yellow snow).
<code>_.__length_hint__()</code>
(<a href="https://docs.python.org/3/reference/datamodel.html#object.__length_hint__">docs</a>)
</td>
<td></td>
<td></td>
<td><code>do_length_hint</code></td>
<td><code>DoesLengthHint</code></td>
<td><code>__length_hint__</code></td>
<td><code>CanLengthHint</code></td>
</tr>
Expand Down
12 changes: 12 additions & 0 deletions optype/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
'CanAEnter',
'CanAExit',
'CanAIter',
'CanAIterSelf',
'CanANext',
'CanAbs',
'CanAdd',
Expand Down Expand Up @@ -52,6 +53,7 @@
'CanInt',
'CanInvert',
'CanIter',
'CanIterSelf',
'CanLe',
'CanLen',
'CanLengthHint',
Expand Down Expand Up @@ -143,6 +145,8 @@
'DoesInvert',
'DoesIter',
'DoesLe',
'DoesLen',
'DoesLengthHint',
'DoesLshift',
'DoesLt',
'DoesMatmul',
Expand Down Expand Up @@ -237,6 +241,8 @@
'do_itruediv',
'do_ixor',
'do_le',
'do_len',
'do_length_hint',
'do_lshift',
'do_lt',
'do_matmul',
Expand Down Expand Up @@ -280,6 +286,7 @@
CanAEnter,
CanAExit,
CanAIter,
CanAIterSelf,
CanANext,
CanAbs,
CanAdd,
Expand Down Expand Up @@ -330,6 +337,7 @@
CanInt,
CanInvert,
CanIter,
CanIterSelf,
CanLe,
CanLen,
CanLengthHint,
Expand Down Expand Up @@ -423,6 +431,8 @@
do_itruediv,
do_ixor,
do_le,
do_len,
do_length_hint,
do_lshift,
do_lt,
do_matmul,
Expand Down Expand Up @@ -503,6 +513,8 @@
DoesInvert,
DoesIter,
DoesLe,
DoesLen,
DoesLengthHint,
DoesLshift,
DoesLt,
DoesMatmul,
Expand Down
84 changes: 72 additions & 12 deletions optype/_can.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import (
Any,
Protocol,
Self,
overload,
override,
runtime_checkable,
Expand All @@ -13,43 +14,89 @@
# Iterator types
# https://docs.python.org/3/library/stdtypes.html#iterator-types


@runtime_checkable
class CanNext[V](Protocol):
"""
Similar to `collections.abc.Iterator`, but without the (often redundant)
requirement to also have a `__iter__` method.
"""
def __next__(self) -> V: ...


@runtime_checkable
class CanIter[Vs: CanNext[Any]](Protocol):
"""Similar to `collections.abc.Iterable`, but more flexible."""
def __iter__(self) -> Vs: ...


@runtime_checkable
class CanIterSelf[V](CanNext[V], CanIter[CanNext[V]], Protocol):
"""
Equivalent to `collections.abc.Iterator[T]`, minus the `abc` nonsense.
"""
@override
def __iter__(self) -> Self: ...


# 3.3.1. Basic customization
# https://docs.python.org/3/reference/datamodel.html#basic-customization


@runtime_checkable
class CanRepr[Y: str](Protocol):
"""
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: ...


@runtime_checkable
class CanStr[Y: str](Protocol):
"""By default, each `object` has a `__str__` method."""
"""
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: ...


@runtime_checkable
class CanBytes[Y: bytes](Protocol):
"""
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: ...


@runtime_checkable
class CanFormat[X: str, Y: str](Protocol):
"""
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]


@runtime_checkable
class CanBool(Protocol):
def __bool__(self) -> bool: ...


@runtime_checkable
class CanHash(Protocol):
@override
def __hash__(self) -> int: ...


# 3.3.1. Basic customization - Rich comparison method
# https://docs.python.org/3/reference/datamodel.html#object.__lt__

Expand All @@ -66,12 +113,29 @@ def __le__(self, __x: X) -> Y: ...

@runtime_checkable
class CanEq[X, Y](Protocol):
"""
Unfortunately, `typeshed` incorrectly annotates `object.__eq__` as
`(Self, object) -> bool`.
As a counter-example, consider `numpy.ndarray`. It's `__eq__` method
returns a boolean (mask) array of the same shape as the input array.
Moreover, `numpy.ndarray` doesn't even implement `CanBool` (`bool()`
raises a `TypeError` for shapes of size > 1).
There is nothing wrong with this implementation, even though `typeshed`
(incorrectly) won't allow it (because `numpy.ndarray <: object`).
So in reality, it should be `__eq__: (Self, X) -> Y`, with `-X` unbounded
and *contra*variant, and `+Y` unbounded and *co*variant.
"""
@override
def __eq__(self, __x: X, /) -> Y: ... # pyright:ignore[reportIncompatibleMethodOverride]


@runtime_checkable
class CanNe[X, Y](Protocol):
"""
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]

Expand All @@ -86,17 +150,6 @@ class CanGe[X, Y](Protocol):
def __ge__(self, __x: X) -> Y: ...


@runtime_checkable
class CanHash(Protocol):
@override
def __hash__(self) -> int: ...


@runtime_checkable
class CanBool(Protocol):
def __bool__(self) -> bool: ...


# 3.3.2. Customizing attribute access
# https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access

Expand Down Expand Up @@ -601,6 +654,13 @@ class CanAIter[Y: CanANext[Any]](Protocol):
def __aiter__(self) -> Y: ...


@runtime_checkable
class CanAIterSelf[V](CanANext[V], CanAIter[CanANext[V]], Protocol):
"""A less inflexible variant of `collections.abc.AsyncIterator[T]`."""
@override
def __aiter__(self) -> Self: ...


# 3.4.4. Asynchronous Context Managers
# https://docs.python.org/3/reference/datamodel.html#asynchronous-context-managers

Expand Down
22 changes: 14 additions & 8 deletions optype/_do.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
do_lt: _d.DoesLt = _o.lt
do_le: _d.DoesLe = _o.le
do_eq: _d.DoesEq = _o.eq
do_ne: _d.DoesEq = _o.ne
do_ne: _d.DoesNe = _o.ne
do_gt: _d.DoesGt = _o.gt
do_ge: _d.DoesGe = _o.ge

Expand All @@ -49,15 +49,21 @@

# callables

def do_call[**Xs, Y](
func: _c.CanCall[Xs, Y],
*args: Xs.args,
**kwargs: Xs.kwargs,
) -> Y:
return func(*args, **kwargs)
# def do_call[**Xs, Y](
# func: _c.CanCall[Xs, Y],
# *args: Xs.args,
# **kwargs: Xs.kwargs,
# ) -> Y:
# return func(*args, **kwargs)

do_call: _d.DoesCall = _o.call


# containers and sequences

do_len: _d.DoesLen = len
do_length_hint: _d.DoesLengthHint = _o.length_hint

# containers

def do_getitem[K, V, M](
obj: _c.CanGetitem[K, V] | _c.CanGetMissing[K, V, M], key: K, /,
Expand Down
13 changes: 12 additions & 1 deletion optype/_does.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,17 @@ def __call__[**Xs, Y](

# containers and subscritable types


@final
class DoesLen(Protocol):
def __call__(self, __o: _c.CanLen, /) -> int: ...


@final
class DoesLengthHint(Protocol):
def __call__(self, __o: _c.CanLengthHint, /) -> int: ...


type _CanSubscript[K, V, M] = _c.CanGetitem[K, V] | _c.CanGetMissing[K, V, M]


Expand Down Expand Up @@ -446,7 +457,7 @@ def __call__[X, Y](self, __o: _c.CanIFloordiv[X, Y], __x: X, /) -> Y: ...

@final
class DoesIMod(Protocol):
def __call__[X, Y](self, __o: _c.CanMod[X, Y], __x: X, /) -> Y: ...
def __call__[X, Y](self, __o: _c.CanIMod[X, Y], __x: X, /) -> Y: ...


@final
Expand Down
Loading

0 comments on commit 225d32e

Please sign in to comment.