Repository: jorenham/optype (language: python)

Issue:
`optype.*.__module__`  exposes the private module name
To illustrate:

```pycon
>>> import optype as opt
>>> opt.HasMatchArgs.__module__
'optype._has'
```

NumPy solves this with a nice `set_module(module)` decorator, that can be used on functions and classes, e.g. as `@set_module("numpy.char")`.
Assuming that `module` is never `None`, the implementation is equivalent to:

```python
class _DoesDecorate(Protocol):
    def __call__[T: opt.HasModule](self, obj: T, /) -> T: ...

def set_module(module: LiteralString, /) -> _DoesDecorate:
    assert module
    assert all(map(str.isidentifier, module.split('.')))

    def do_decorate[F: opt.HasModule](obj: T, /) -> T:
        obj.__module__ = module
        return obj
    return do_decorate
```



Discussion / hints:

Fix patch:
diff --git a/optype/_can.py b/optype/_can.py
index a8c72f59..f9464dc2 100644
--- a/optype/_can.py
+++ b/optype/_can.py
@@ -25,11 +25,12 @@
         runtime_checkable,
     )
 
-
 if TYPE_CHECKING:
     from collections.abc import Generator
     from types import TracebackType
 
+from optype._utils import set_module
+
 
 _Ignored: TypeAlias = Any
 _JustFalse: TypeAlias = Literal[False]
@@ -50,6 +51,7 @@
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class CanBool(Protocol[_R_bool]):
     def __bool__(self, /) -> _R_bool: ...
@@ -58,16 +60,19 @@ def __bool__(self, /) -> _R_bool: ...
 _R_int = TypeVar('_R_int', infer_variance=True, bound=int, default=int)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanInt(Protocol[_R_int]):
     def __int__(self, /) -> _R_int: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanFloat(Protocol):
     def __float__(self, /) -> float: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanComplex(Protocol):
     def __complex__(self, /) -> complex: ...
@@ -76,6 +81,7 @@ def __complex__(self, /) -> complex: ...
 _R_bytes = TypeVar('_R_bytes', infer_variance=True, bound=bytes, default=bytes)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanBytes(Protocol[_R_bytes]):
     """
@@ -89,6 +95,7 @@ def __bytes__(self, /) -> _R_bytes: ...
 _R_str = TypeVar('_R_str', infer_variance=True, bound=str, default=str)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanStr(Protocol[_R_str]):
     """
@@ -104,6 +111,7 @@ def __str__(self, /) -> _R_str: ...
 # Representation
 #
 
+@set_module('optype')
 @runtime_checkable
 class CanHash(Protocol):
     @override
@@ -113,6 +121,7 @@ def __hash__(self, /) -> int: ...
 _R_index = TypeVar('_R_index', infer_variance=True, bound=int, default=int)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIndex(Protocol[_R_index]):
     def __index__(self, /) -> _R_index: ...
@@ -121,6 +130,7 @@ def __index__(self, /) -> _R_index: ...
 _R_repr = TypeVar('_R_repr', infer_variance=True, bound=str, default=str)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRepr(Protocol[_R_repr]):
     """
@@ -136,6 +146,7 @@ def __repr__(self, /) -> _R_repr: ...
 _R_format = TypeVar('_R_format', infer_variance=True, bound=str, default=str)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanFormat(Protocol[_T_format, _R_format]):
     """
@@ -155,6 +166,7 @@ def __format__(self, fmt: _T_format, /) -> _R_format: ...  # pyright:ignore[repo
 _V_next = TypeVar('_V_next', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanNext(Protocol[_V_next]):
     """
@@ -167,6 +179,7 @@ def __next__(self, /) -> _V_next: ...
 _R_iter = TypeVar('_R_iter', infer_variance=True, bound=CanNext[Any])
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIter(Protocol[_R_iter]):
     """
@@ -179,6 +192,7 @@ def __iter__(self, /) -> _R_iter: ...
 _V_iter_self = TypeVar('_V_iter_self', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIterSelf(
     CanNext[_V_iter_self],
@@ -199,6 +213,7 @@ def __iter__(self, /) -> Self: ...
 _V_anext = TypeVar('_V_anext', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanANext(Protocol[_V_anext]):
     def __anext__(self, /) -> _V_anext: ...
@@ -207,6 +222,7 @@ def __anext__(self, /) -> _V_anext: ...
 _R_aiter = TypeVar('_R_aiter', infer_variance=True, bound=CanANext[Any])
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAIter(Protocol[_R_aiter]):
     def __aiter__(self, /) -> _R_aiter: ...
@@ -215,6 +231,7 @@ def __aiter__(self, /) -> _R_aiter: ...
 _V_aiter_self = TypeVar('_V_aiter_self', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAIterSelf(
     CanANext[_V_aiter_self],
@@ -234,6 +251,7 @@ def __aiter__(self, /) -> Self: ...
 _R_eq = TypeVar('_R_eq', infer_variance=True, default=bool)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanEq(Protocol[_T_eq, _R_eq]):  # noqa: PLW1641
     """
@@ -257,6 +275,7 @@ def __eq__(self, rhs: _T_eq, /) -> _R_eq: ...  # pyright:ignore[reportIncompatib
 _R_ne = TypeVar('_R_ne', infer_variance=True, default=bool)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanNe(Protocol[_T_ne, _R_ne]):
     """
@@ -271,6 +290,7 @@ def __ne__(self, rhs: _T_ne, /) -> _R_ne: ...  # pyright:ignore[reportIncompatib
 _R_lt = TypeVar('_R_lt', infer_variance=True, default=bool)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanLt(Protocol[_T_lt, _R_lt]):
     def __lt__(self, rhs: _T_lt, /) -> _R_lt: ...
@@ -280,6 +300,7 @@ def __lt__(self, rhs: _T_lt, /) -> _R_lt: ...
 _R_le = TypeVar('_R_le', infer_variance=True, default=bool)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanLe(Protocol[_T_le, _R_le]):
     def __le__(self, rhs: _T_le, /) -> _R_le: ...
@@ -289,6 +310,7 @@ def __le__(self, rhs: _T_le, /) -> _R_le: ...
 _R_gt = TypeVar('_R_gt', infer_variance=True, default=bool)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanGt(Protocol[_T_gt, _R_gt]):
     def __gt__(self, rhs: _T_gt, /) -> _R_gt: ...
@@ -298,6 +320,7 @@ def __gt__(self, rhs: _T_gt, /) -> _R_gt: ...
 _R_ge = TypeVar('_R_ge', infer_variance=True, default=bool)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanGe(Protocol[_T_ge, _R_ge]):
     def __ge__(self, rhs: _T_ge, /) -> _R_ge: ...
@@ -311,6 +334,7 @@ def __ge__(self, rhs: _T_ge, /) -> _R_ge: ...
 _R_call = TypeVar('_R_call', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanCall(Protocol[_Pss_call, _R_call]):
     def __call__(
@@ -329,6 +353,7 @@ def __call__(
 _V_getattr = TypeVar('_V_getattr', infer_variance=True, default=Any)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanGetattr(Protocol[_N_getattr, _V_getattr]):
     def __getattr__(self, name: _N_getattr, /) -> _V_getattr: ...
@@ -343,6 +368,7 @@ def __getattr__(self, name: _N_getattr, /) -> _V_getattr: ...
 _V_getattribute = TypeVar('_V_getattribute', infer_variance=True, default=Any)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanGetattribute(Protocol[_N_getattribute, _V_getattribute]):
     """Note that `isinstance(x, CanGetattribute)` is always true."""
@@ -358,6 +384,7 @@ def __getattribute__(  # pyright:ignore[reportIncompatibleMethodOverride]
 _V_setattr = TypeVar('_V_setattr', infer_variance=True, default=Any)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanSetattr(Protocol[_N_setattr, _V_setattr]):
     """Note that `isinstance(x, CanSetattr)` is always true."""
@@ -373,6 +400,7 @@ def __setattr__(  # pyright:ignore[reportIncompatibleMethodOverride]
 _N_delattr = TypeVar('_N_delattr', infer_variance=True, bound=str, default=str)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanDelattr(Protocol[_N_delattr]):
     @override
@@ -387,6 +415,7 @@ def __delattr__(self, name: _N_delattr, /) -> Any: ...  # pyright:ignore[reportI
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class CanDir(Protocol[_R_dir]):
     @override
@@ -402,6 +431,7 @@ def __dir__(self, /) -> _R_dir: ...
 _VT_get = TypeVar('_VT_get', infer_variance=True, default=_V_get)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanGet(Protocol[_T_get, _V_get, _VT_get]):
     @overload
@@ -419,6 +449,7 @@ def __get__(self, owner: None, owner_type: type[_T_get], /) -> _VT_get: ...
 _V_set = TypeVar('_V_set', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanSet(Protocol[_T_set, _V_set]):
     def __set__(self, owner: _T_set, value: _V_set, /) -> _Ignored: ...
@@ -427,6 +458,7 @@ def __set__(self, owner: _T_set, value: _V_set, /) -> _Ignored: ...
 _T_delete = TypeVar('_T_delete', infer_variance=True, bound=object)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanDelete(Protocol[_T_delete]):
     def __delete__(self, owner: _T_delete, /) -> _Ignored: ...
@@ -441,6 +473,7 @@ def __delete__(self, owner: _T_delete, /) -> _Ignored: ...
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class CanSetName(Protocol[_T_set_name, _N_set_name]):
     def __set_name__(
@@ -458,6 +491,7 @@ def __set_name__(
 _R_len = TypeVar('_R_len', infer_variance=True, bound=int, default=int)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanLen(Protocol[_R_len]):
     def __len__(self, /) -> _R_len: ...
@@ -471,6 +505,7 @@ def __len__(self, /) -> _R_len: ...
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class CanLengthHint(Protocol[_R_length_hint]):
     def __length_hint__(self, /) -> _R_length_hint: ...
@@ -480,6 +515,7 @@ def __length_hint__(self, /) -> _R_length_hint: ...
 _V_getitem = TypeVar('_V_getitem', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanGetitem(Protocol[_K_getitem, _V_getitem]):
     def __getitem__(self, key: _K_getitem, /) -> _V_getitem: ...
@@ -489,6 +525,7 @@ def __getitem__(self, key: _K_getitem, /) -> _V_getitem: ...
 _V_setitem = TypeVar('_V_setitem', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanSetitem(Protocol[_K_setitem, _V_setitem]):
     def __setitem__(
@@ -502,6 +539,7 @@ def __setitem__(
 _K_delitem = TypeVar('_K_delitem', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanDelitem(Protocol[_K_delitem]):
     def __delitem__(self, key: _K_delitem, /) -> None: ...
@@ -511,6 +549,7 @@ def __delitem__(self, key: _K_delitem, /) -> None: ...
 _R_reversed = TypeVar('_R_reversed', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanReversed(Protocol[_R_reversed]):
     def __reversed__(self, /) -> _R_reversed: ...
@@ -533,6 +572,7 @@ def __reversed__(self, /) -> _R_reversed: ...
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class CanContains(Protocol[_K_contains, _R_contains]):
     def __contains__(self, key: _K_contains, /) -> _R_contains: ...
@@ -542,6 +582,7 @@ def __contains__(self, key: _K_contains, /) -> _R_contains: ...
 _D_missing = TypeVar('_D_missing', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanMissing(Protocol[_K_missing, _D_missing]):
     def __missing__(self, key: _K_missing, /) -> _D_missing: ...
@@ -556,6 +597,7 @@ def __missing__(self, key: _K_missing, /) -> _D_missing: ...
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class CanGetMissing(
     CanGetitem[_K_get_missing, _V_get_missing],
@@ -572,6 +614,7 @@ class CanGetMissing(
 _V_sequence = TypeVar('_V_sequence', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanSequence(
     CanLen,
@@ -595,6 +638,7 @@ class CanSequence(
 _R_add = TypeVar('_R_add', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAdd(Protocol[_T_add, _R_add]):
     def __add__(self, rhs: _T_add, /) -> _R_add: ...
@@ -604,6 +648,7 @@ def __add__(self, rhs: _T_add, /) -> _R_add: ...
 _R_sub = TypeVar('_R_sub', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanSub(Protocol[_T_sub, _R_sub]):
     def __sub__(self, rhs: _T_sub, /) -> _R_sub: ...
@@ -613,6 +658,7 @@ def __sub__(self, rhs: _T_sub, /) -> _R_sub: ...
 _R_mul = TypeVar('_R_mul', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanMul(Protocol[_T_mul, _R_mul]):
     def __mul__(self, rhs: _T_mul, /) -> _R_mul: ...
@@ -622,6 +668,7 @@ def __mul__(self, rhs: _T_mul, /) -> _R_mul: ...
 _R_matmul = TypeVar('_R_matmul', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanMatmul(Protocol[_T_matmul, _R_matmul]):
     def __matmul__(self, rhs: _T_matmul, /) -> _R_matmul: ...
@@ -631,6 +678,7 @@ def __matmul__(self, rhs: _T_matmul, /) -> _R_matmul: ...
 _R_truediv = TypeVar('_R_truediv', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanTruediv(Protocol[_T_truediv, _R_truediv]):
     def __truediv__(self, rhs: _T_truediv, /) -> _R_truediv: ...
@@ -640,6 +688,7 @@ def __truediv__(self, rhs: _T_truediv, /) -> _R_truediv: ...
 _R_floordiv = TypeVar('_R_floordiv', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanFloordiv(Protocol[_T_floordiv, _R_floordiv]):
     def __floordiv__(self, rhs: _T_floordiv, /) -> _R_floordiv: ...
@@ -649,6 +698,7 @@ def __floordiv__(self, rhs: _T_floordiv, /) -> _R_floordiv: ...
 _R_mod = TypeVar('_R_mod', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanMod(Protocol[_T_mod, _R_mod]):
     def __mod__(self, rhs: _T_mod, /) -> _R_mod: ...
@@ -658,6 +708,7 @@ def __mod__(self, rhs: _T_mod, /) -> _R_mod: ...
 _R_divmod = TypeVar('_R_divmod', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanDivmod(Protocol[_T_divmod, _R_divmod]):
     def __divmod__(self, rhs: _T_divmod, /) -> _R_divmod: ...
@@ -667,6 +718,7 @@ def __divmod__(self, rhs: _T_divmod, /) -> _R_divmod: ...
 _R_pow2 = TypeVar('_R_pow2', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanPow2(Protocol[_T_pow2, _R_pow2]):
     @overload
@@ -680,6 +732,7 @@ def __pow__(self, exp: _T_pow2, mod: None = ..., /) -> _R_pow2: ...
 _R_pow3 = TypeVar('_R_pow3', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanPow3(Protocol[_T_pow3_exp, _T_pow3_mod, _R_pow3]):
     def __pow__(self, exp: _T_pow3_exp, mod: _T_pow3_mod, /) -> _R_pow3: ...
@@ -691,6 +744,7 @@ def __pow__(self, exp: _T_pow3_exp, mod: _T_pow3_mod, /) -> _R_pow3: ...
 _R_pow_mod = TypeVar('_R_pow_mod', infer_variance=True, default=_R_pow)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanPow(
     CanPow2[_T_pow_exp, _R_pow],
@@ -709,6 +763,7 @@ def __pow__(self, exp: _T_pow_exp, mod: _T_pow_mod, /) -> _R_pow_mod: ...
 _R_lshift = TypeVar('_R_lshift', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanLshift(Protocol[_T_lshift, _R_lshift]):
     def __lshift__(self, rhs: _T_lshift, /) -> _R_lshift: ...
@@ -718,6 +773,7 @@ def __lshift__(self, rhs: _T_lshift, /) -> _R_lshift: ...
 _R_rshift = TypeVar('_R_rshift', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRshift(Protocol[_T_rshift, _R_rshift]):
     def __rshift__(self, rhs: _T_rshift, /) -> _R_rshift: ...
@@ -727,6 +783,7 @@ def __rshift__(self, rhs: _T_rshift, /) -> _R_rshift: ...
 _R_and = TypeVar('_R_and', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAnd(Protocol[_T_and, _R_and]):
     def __and__(self, rhs: _T_and, /) -> _R_and: ...
@@ -736,6 +793,7 @@ def __and__(self, rhs: _T_and, /) -> _R_and: ...
 _R_xor = TypeVar('_R_xor', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanXor(Protocol[_T_xor, _R_xor]):
     def __xor__(self, rhs: _T_xor, /) -> _R_xor: ...
@@ -745,6 +803,7 @@ def __xor__(self, rhs: _T_xor, /) -> _R_xor: ...
 _R_or = TypeVar('_R_or', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanOr(Protocol[_T_or, _R_or]):
     def __or__(self, rhs: _T_or, /) -> _R_or: ...
@@ -758,6 +817,7 @@ def __or__(self, rhs: _T_or, /) -> _R_or: ...
 _R_radd = TypeVar('_R_radd', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRAdd(Protocol[_T_radd, _R_radd]):
     def __radd__(self, rhs: _T_radd, /) -> _R_radd: ...
@@ -767,6 +827,7 @@ def __radd__(self, rhs: _T_radd, /) -> _R_radd: ...
 _R_rsub = TypeVar('_R_rsub', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRSub(Protocol[_T_rsub, _R_rsub]):
     def __rsub__(self, rhs: _T_rsub, /) -> _R_rsub: ...
@@ -776,6 +837,7 @@ def __rsub__(self, rhs: _T_rsub, /) -> _R_rsub: ...
 _R_rmul = TypeVar('_R_rmul', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRMul(Protocol[_T_rmul, _R_rmul]):
     def __rmul__(self, rhs: _T_rmul, /) -> _R_rmul: ...
@@ -785,6 +847,7 @@ def __rmul__(self, rhs: _T_rmul, /) -> _R_rmul: ...
 _R_rmatmul = TypeVar('_R_rmatmul', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRMatmul(Protocol[_T_rmatmul, _R_rmatmul]):
     def __rmatmul__(self, rhs: _T_rmatmul, /) -> _R_rmatmul: ...
@@ -794,6 +857,7 @@ def __rmatmul__(self, rhs: _T_rmatmul, /) -> _R_rmatmul: ...
 _R_rtruediv = TypeVar('_R_rtruediv', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRTruediv(Protocol[_T_rtruediv, _R_rtruediv]):
     def __rtruediv__(self, rhs: _T_rtruediv, /) -> _R_rtruediv: ...
@@ -803,6 +867,7 @@ def __rtruediv__(self, rhs: _T_rtruediv, /) -> _R_rtruediv: ...
 _R_rfloordiv = TypeVar('_R_rfloordiv', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRFloordiv(Protocol[_T_rfloordiv, _R_rfloordiv]):
     def __rfloordiv__(self, rhs: _T_rfloordiv, /) -> _R_rfloordiv: ...
@@ -812,6 +877,7 @@ def __rfloordiv__(self, rhs: _T_rfloordiv, /) -> _R_rfloordiv: ...
 _R_rmod = TypeVar('_R_rmod', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRMod(Protocol[_T_rmod, _R_rmod]):
     def __rmod__(self, rhs: _T_rmod, /) -> _R_rmod: ...
@@ -821,6 +887,7 @@ def __rmod__(self, rhs: _T_rmod, /) -> _R_rmod: ...
 _R_rdivmod = TypeVar('_R_rdivmod', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRDivmod(Protocol[_T_rdivmod, _R_rdivmod]):
     def __rdivmod__(self, rhs: _T_rdivmod, /) -> _R_rdivmod: ...
@@ -830,6 +897,7 @@ def __rdivmod__(self, rhs: _T_rdivmod, /) -> _R_rdivmod: ...
 _R_rpow = TypeVar('_R_rpow', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRPow(Protocol[_T_rpow, _R_rpow]):
     def __rpow__(self, x: _T_rpow, /) -> _R_rpow: ...
@@ -839,6 +907,7 @@ def __rpow__(self, x: _T_rpow, /) -> _R_rpow: ...
 _R_rlshift = TypeVar('_R_rlshift', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRLshift(Protocol[_T_rlshift, _R_rlshift]):
     def __rlshift__(self, rhs: _T_rlshift, /) -> _R_rlshift: ...
@@ -848,6 +917,7 @@ def __rlshift__(self, rhs: _T_rlshift, /) -> _R_rlshift: ...
 _R_rrshift = TypeVar('_R_rrshift', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRRshift(Protocol[_T_rrshift, _R_rrshift]):
     def __rrshift__(self, rhs: _T_rrshift, /) -> _R_rrshift: ...
@@ -857,6 +927,7 @@ def __rrshift__(self, rhs: _T_rrshift, /) -> _R_rrshift: ...
 _R_rand = TypeVar('_R_rand', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRAnd(Protocol[_T_rand, _R_rand]):
     def __rand__(self, rhs: _T_rand, /) -> _R_rand: ...
@@ -866,6 +937,7 @@ def __rand__(self, rhs: _T_rand, /) -> _R_rand: ...
 _R_rxor = TypeVar('_R_rxor', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRXor(Protocol[_T_rxor, _R_rxor]):
     def __rxor__(self, rhs: _T_rxor, /) -> _R_rxor: ...
@@ -875,6 +947,7 @@ def __rxor__(self, rhs: _T_rxor, /) -> _R_rxor: ...
 _R_ror = TypeVar('_R_ror', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanROr(Protocol[_T_ror, _R_ror]):
     def __ror__(self, rhs: _T_ror, /) -> _R_ror: ...
@@ -888,11 +961,13 @@ def __ror__(self, rhs: _T_ror, /) -> _R_ror: ...
 _R_iadd = TypeVar('_R_iadd', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIAdd(Protocol[_T_iadd, _R_iadd]):
     def __iadd__(self, rhs: _T_iadd, /) -> _R_iadd: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIAddSelf(CanIAdd[_T_iadd, 'CanIAddSelf[Any]'], Protocol[_T_iadd]):
     @override
@@ -903,11 +978,13 @@ def __iadd__(self, rhs: _T_iadd, /) -> Self: ...
 _R_isub = TypeVar('_R_isub', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanISub(Protocol[_T_isub, _R_isub]):
     def __isub__(self, rhs: _T_isub, /) -> _R_isub: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanISubSelf(CanISub[_T_isub, 'CanISubSelf[Any]'], Protocol[_T_isub]):
     @override
@@ -918,11 +995,13 @@ def __isub__(self, rhs: _T_isub, /) -> Self: ...
 _R_imul = TypeVar('_R_imul', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIMul(Protocol[_T_imul, _R_imul]):
     def __imul__(self, rhs: _T_imul, /) -> _R_imul: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIMulSelf(CanIMul[_T_imul, 'CanIMulSelf[Any]'], Protocol[_T_imul]):
     @override
@@ -933,11 +1012,13 @@ def __imul__(self, rhs: _T_imul, /) -> Self: ...
 _R_imatmul = TypeVar('_R_imatmul', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIMatmul(Protocol[_T_imatmul, _R_imatmul]):
     def __imatmul__(self, rhs: _T_imatmul, /) -> _R_imatmul: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIMatmulSelf(
     CanIMatmul[_T_imatmul, 'CanIMatmulSelf[Any]'],
@@ -951,11 +1032,13 @@ def __imatmul__(self, rhs: _T_imatmul, /) -> Self: ...
 _R_itruediv = TypeVar('_R_itruediv', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanITruediv(Protocol[_T_itruediv, _R_itruediv]):
     def __itruediv__(self, rhs: _T_itruediv, /) -> _R_itruediv: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanITruedivSelf(
     CanITruediv[_T_itruediv, 'CanITruedivSelf[Any]'],
@@ -969,11 +1052,13 @@ def __itruediv__(self, rhs: _T_itruediv, /) -> Self: ...
 _R_ifloordiv = TypeVar('_R_ifloordiv', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIFloordiv(Protocol[_T_ifloordiv, _R_ifloordiv]):
     def __ifloordiv__(self, rhs: _T_ifloordiv, /) -> _R_ifloordiv: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIFloordivSelf(
     CanIFloordiv[_T_ifloordiv, 'CanIFloordivSelf[Any]'],
@@ -987,11 +1072,13 @@ def __ifloordiv__(self, rhs: _T_ifloordiv, /) -> Self: ...
 _R_imod = TypeVar('_R_imod', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIMod(Protocol[_T_imod, _R_imod]):
     def __imod__(self, rhs: _T_imod, /) -> _R_imod: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIModSelf(CanIMod[_T_imod, 'CanIModSelf[Any]'], Protocol[_T_imod]):
     @override
@@ -1002,12 +1089,14 @@ def __imod__(self, rhs: _T_imod, /) -> Self: ...
 _R_ipow = TypeVar('_R_ipow', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIPow(Protocol[_T_ipow, _R_ipow]):
     # no augmented pow/3 exists
     def __ipow__(self, rhs: _T_ipow, /) -> _R_ipow: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIPowSelf(CanIPow[_T_ipow, 'CanIPowSelf[Any]'], Protocol[_T_ipow]):
     @override
@@ -1018,11 +1107,13 @@ def __ipow__(self, rhs: _T_ipow, /) -> Self: ...
 _R_ilshift = TypeVar('_R_ilshift', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanILshift(Protocol[_T_ilshift, _R_ilshift]):
     def __ilshift__(self, rhs: _T_ilshift, /) -> _R_ilshift: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanILshiftSelf(
     CanILshift[_T_ilshift, 'CanILshiftSelf[Any]'],
@@ -1036,11 +1127,13 @@ def __ilshift__(self, rhs: _T_ilshift, /) -> Self: ...
 _R_irshift = TypeVar('_R_irshift', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIRshift(Protocol[_T_irshift, _R_irshift]):
     def __irshift__(self, rhs: _T_irshift, /) -> _R_irshift: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIRshiftSelf(
     CanIRshift[_T_irshift, 'CanIRshiftSelf[Any]'],
@@ -1054,11 +1147,13 @@ def __irshift__(self, rhs: _T_irshift, /) -> Self: ...
 _R_iand = TypeVar('_R_iand', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIAnd(Protocol[_T_iand, _R_iand]):
     def __iand__(self, rhs: _T_iand, /) -> _R_iand: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIAndSelf(CanIAnd[_T_iand, 'CanIAndSelf[Any]'], Protocol[_T_iand]):
     @override
@@ -1069,11 +1164,13 @@ def __iand__(self, rhs: _T_iand, /) -> Self: ...
 _R_ixor = TypeVar('_R_ixor', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIXor(Protocol[_T_ixor, _R_ixor]):
     def __ixor__(self, rhs: _T_ixor, /) -> _R_ixor: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIXorSelf(CanIXor[_T_ixor, 'CanIXorSelf[Any]'], Protocol[_T_ixor]):
     @override
@@ -1084,11 +1181,13 @@ def __ixor__(self, rhs: _T_ixor, /) -> Self: ...
 _R_ior = TypeVar('_R_ior', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIOr(Protocol[_T_ior, _R_ior]):
     def __ior__(self, rhs: _T_ior, /) -> _R_ior: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanIOrSelf(CanIOr[_T_ior, 'CanIOrSelf[Any]'], Protocol[_T_ior]):
     @override
@@ -1102,11 +1201,13 @@ def __ior__(self, rhs: _T_ior, /) -> Self: ...
 _R_neg = TypeVar('_R_neg', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanNeg(Protocol[_R_neg]):
     def __neg__(self, /) -> _R_neg: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanNegSelf(CanNeg['CanNegSelf'], Protocol):
     @override
@@ -1116,11 +1217,13 @@ def __neg__(self, /) -> Self: ...
 _R_pos = TypeVar('_R_pos', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanPos(Protocol[_R_pos]):
     def __pos__(self, /) -> _R_pos: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanPosSelf(CanPos['CanPosSelf'], Protocol):
     @override
@@ -1130,11 +1233,13 @@ def __pos__(self, /) -> Self: ...
 _R_abs = TypeVar('_R_abs', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAbs(Protocol[_R_abs]):
     def __abs__(self, /) -> _R_abs: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAbsSelf(CanAbs['CanAbsSelf'], Protocol):
     @override
@@ -1144,11 +1249,13 @@ def __abs__(self, /) -> Self: ...
 _R_invert = TypeVar('_R_invert', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanInvert(Protocol[_R_invert]):
     def __invert__(self, /) -> _R_invert: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanInvertSelf(CanInvert['CanInvertSelf'], Protocol):
     @override
@@ -1162,6 +1269,7 @@ def __invert__(self, /) -> Self: ...
 _R_round1 = TypeVar('_R_round1', infer_variance=True, default=int)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRound1(Protocol[_R_round1]):
     @overload
@@ -1174,6 +1282,7 @@ def __round__(self, /, ndigits: None = None) -> _R_round1: ...
 _RT_round2 = TypeVar('_RT_round2', infer_variance=True, default=float)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRound2(Protocol[_T_round2, _RT_round2]):
     def __round__(self, /, ndigits: _T_round2) -> _RT_round2: ...
@@ -1184,6 +1293,7 @@ def __round__(self, /, ndigits: _T_round2) -> _RT_round2: ...
 _RT_round = TypeVar('_RT_round', infer_variance=True, default=float)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanRound(
     CanRound1[_R_round],
@@ -1201,6 +1311,7 @@ def __round__(self, /, ndigits: _T_round) -> _RT_round: ...
 _R_trunc = TypeVar('_R_trunc', infer_variance=True, default=int)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanTrunc(Protocol[_R_trunc]):
     def __trunc__(self, /) -> _R_trunc: ...
@@ -1209,6 +1320,7 @@ def __trunc__(self, /) -> _R_trunc: ...
 _R_floor = TypeVar('_R_floor', infer_variance=True, default=int)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanFloor(Protocol[_R_floor]):
     def __floor__(self, /) -> _R_floor: ...
@@ -1217,6 +1329,7 @@ def __floor__(self, /) -> _R_floor: ...
 _R_ceil = TypeVar('_R_ceil', infer_variance=True, default=int)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanCeil(Protocol[_R_ceil]):
     def __ceil__(self, /) -> _R_ceil: ...
@@ -1229,11 +1342,13 @@ def __ceil__(self, /) -> _R_ceil: ...
 _C_enter = TypeVar('_C_enter', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanEnter(Protocol[_C_enter]):
     def __enter__(self, /) -> _C_enter: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanEnterSelf(CanEnter['CanEnterSelf'], Protocol):
     @override
@@ -1244,6 +1359,7 @@ def __enter__(self, /) -> Self: ...  # pyright: ignore[reportMissingSuperCall]
 _R_exit = TypeVar('_R_exit', infer_variance=True, default=None)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanExit(Protocol[_R_exit]):
     @overload
@@ -1268,6 +1384,7 @@ def __exit__(
 _R_with = TypeVar('_R_with', infer_variance=True, default=None)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanWith(CanEnter[_C_with], CanExit[_R_with], Protocol[_C_with, _R_with]):
     """
@@ -1279,6 +1396,7 @@ class CanWith(CanEnter[_C_with], CanExit[_R_with], Protocol[_C_with, _R_with]):
 _R_with_self = TypeVar('_R_with_self', infer_variance=True, default=None)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanWithSelf(CanEnterSelf, CanExit[_R_with_self], Protocol[_R_with_self]):
     """
@@ -1294,11 +1412,13 @@ class CanWithSelf(CanEnterSelf, CanExit[_R_with_self], Protocol[_R_with_self]):
 _C_aenter = TypeVar('_C_aenter', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAEnter(Protocol[_C_aenter]):
     def __aenter__(self, /) -> CanAwait[_C_aenter]: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAEnterSelf(CanAEnter['CanAEnterSelf'], Protocol):
     @override
@@ -1309,6 +1429,7 @@ def __aenter__(self, /) -> CanAwait[Self]: ...
 _R_aexit = TypeVar('_R_aexit', infer_variance=True, default=None)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAExit(Protocol[_R_aexit]):
     @overload
@@ -1333,6 +1454,7 @@ def __aexit__(
 _R_async_with = TypeVar('_R_async_with', infer_variance=True, default=None)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAsyncWith(
     CanAEnter[_C_async_with],
@@ -1345,6 +1467,7 @@ class CanAsyncWith(
     """
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAsyncWithSelf(
     CanAEnterSelf,
@@ -1364,11 +1487,13 @@ class CanAsyncWithSelf(
 _T_buffer = TypeVar('_T_buffer', infer_variance=True, bound=int, default=int)
 
 
+@set_module('optype')
 @runtime_checkable
 class CanBuffer(Protocol[_T_buffer]):
     def __buffer__(self, buffer: _T_buffer, /) -> memoryview: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class CanReleaseBuffer(Protocol):
     def __release_buffer__(self, buffer: memoryview, /) -> None: ...
@@ -1387,6 +1512,7 @@ def __release_buffer__(self, buffer: memoryview, /) -> None: ...
 _AsyncGen: TypeAlias = 'Generator[_FutureOrNone, None, _R_await]'
 
 
+@set_module('optype')
 @runtime_checkable
 class CanAwait(Protocol[_R_await]):
     # Technically speaking, this can return any
diff --git a/optype/_do.py b/optype/_do.py
index 0e7eb97d..f2456334 100644
--- a/optype/_do.py
+++ b/optype/_do.py
@@ -6,6 +6,8 @@
 import optype._can as _c
 import optype._does as _d
 
+from ._utils import set_module
+
 
 # type conversion
 do_bool: _d.DoesBool = bool  # pyright: ignore[reportAssignmentType]
@@ -50,10 +52,13 @@
 
 # callables
 
-if _sys.version_info < (3, 11):
+if _sys.version_info >= (3, 11):
+    do_call: _d.DoesCall = _o.call
+else:
     _Pss_call = ParamSpec('_Pss_call')
     _R_call = TypeVar('_R_call')
 
+    @set_module('optype')
     def do_call(
         f: _c.CanCall[_Pss_call, _R_call],
         /,
@@ -61,8 +66,6 @@ def do_call(
         **kwargs: _Pss_call.kwargs,
     ) -> _R_call:
         return f(*args, **kwargs)
-else:
-    do_call: _d.DoesCall = _o.call
 
 
 # containers and sequences
@@ -81,19 +84,22 @@ def do_call(
 
 
 @overload
+@set_module('optype')
 def do_getitem(
-    obj:  _c.CanGetMissing[_K_getitem, _V_getitem, _D_getitem],
+    obj: _c.CanGetMissing[_K_getitem, _V_getitem, _D_getitem],
     key: _K_getitem,
     /,
 ) -> _V_getitem | _D_getitem: ...
 @overload
+@set_module('optype')
 def do_getitem(
-    obj:  _c.CanGetitem[_K_getitem, _V_getitem],
+    obj: _c.CanGetitem[_K_getitem, _V_getitem],
     key: _K_getitem,
     /,
 ) -> _V_getitem: ...
+@set_module('optype')
 def do_getitem(
-    obj:  (
+    obj: (
         _c.CanGetitem[_K_getitem, _V_getitem]
         | _c.CanGetMissing[_K_getitem, _V_getitem, _D_getitem]
     ),
@@ -108,6 +114,7 @@ def do_getitem(
 _V_setitem = TypeVar('_V_setitem')
 
 
+@set_module('optype')
 def do_setitem(
     obj: _c.CanSetitem[_K_setitem, _V_setitem],
     key: _K_setitem,
@@ -121,6 +128,7 @@ def do_setitem(
 _K_delitem = TypeVar('_K_delitem')
 
 
+@set_module('optype')
 def do_delitem(obj: _c.CanDelitem[_K_delitem], key: _K_delitem, /) -> None:
     """Same as `del obj[key]`."""
     del obj[key]
@@ -130,6 +138,7 @@ def do_delitem(obj: _c.CanDelitem[_K_delitem], key: _K_delitem, /) -> None:
 _D_missing = TypeVar('_D_missing')
 
 
+@set_module('optype')
 def do_missing(
     obj: _c.CanMissing[_K_missing, _D_missing],
     key: _K_missing,
@@ -143,6 +152,7 @@ def do_missing(
 _K_contains = TypeVar('_K_contains', bound=object)
 
 
+@set_module('optype')
 def do_contains(obj: _c.CanContains[_K_contains], key: _K_contains, /) -> bool:
     """Same as `key in obj`."""
     return key in obj
@@ -179,6 +189,7 @@ def do_contains(obj: _c.CanContains[_K_contains], key: _K_contains, /) -> bool:
 _R_radd = TypeVar('_R_radd')
 
 
+@set_module('optype')
 def do_radd(a: _c.CanRAdd[_T_radd, _R_radd], b: _T_radd, /) -> _R_radd:
     """Same as `b + a`."""
     return b + a
@@ -188,6 +199,7 @@ def do_radd(a: _c.CanRAdd[_T_radd, _R_radd], b: _T_radd, /) -> _R_radd:
 _R_rsub = TypeVar('_R_rsub')
 
 
+@set_module('optype')
 def do_rsub(a: _c.CanRSub[_T_rsub, _R_rsub], b: _T_rsub, /) -> _R_rsub:
     """Same as `b - a`."""
     return b - a
@@ -197,6 +209,7 @@ def do_rsub(a: _c.CanRSub[_T_rsub, _R_rsub], b: _T_rsub, /) -> _R_rsub:
 _R_rmul = TypeVar('_R_rmul')
 
 
+@set_module('optype')
 def do_rmul(a: _c.CanRMul[_T_rmul, _R_rmul], b: _T_rmul, /) -> _R_rmul:
     """Same as `b * a`."""
     return b * a
@@ -206,6 +219,7 @@ def do_rmul(a: _c.CanRMul[_T_rmul, _R_rmul], b: _T_rmul, /) -> _R_rmul:
 _R_rmatmul = TypeVar('_R_rmatmul')
 
 
+@set_module('optype')
 def do_rmatmul(
     a: _c.CanRMatmul[_T_rmatmul, _R_rmatmul],
     b: _T_rmatmul,
@@ -219,6 +233,7 @@ def do_rmatmul(
 _R_rtruediv = TypeVar('_R_rtruediv')
 
 
+@set_module('optype')
 def do_rtruediv(
     a: _c.CanRTruediv[_T_rtruediv, _R_rtruediv],
     b: _T_rtruediv,
@@ -232,6 +247,7 @@ def do_rtruediv(
 _R_rfloordiv = TypeVar('_R_rfloordiv')
 
 
+@set_module('optype')
 def do_rfloordiv(
     a: _c.CanRFloordiv[_T_rfloordiv, _R_rfloordiv],
     b: _T_rfloordiv,
@@ -245,6 +261,7 @@ def do_rfloordiv(
 _R_rmod = TypeVar('_R_rmod')
 
 
+@set_module('optype')
 def do_rmod(a: _c.CanRMod[_T_rmod, _R_rmod], b: _T_rmod, /) -> _R_rmod:
     """Same as `b % a`."""
     return b % a
@@ -254,6 +271,7 @@ def do_rmod(a: _c.CanRMod[_T_rmod, _R_rmod], b: _T_rmod, /) -> _R_rmod:
 _R_rdivmod = TypeVar('_R_rdivmod')
 
 
+@set_module('optype')
 def do_rdivmod(
     a: _c.CanRDivmod[_T_rdivmod, _R_rdivmod],
     b: _T_rdivmod,
@@ -267,6 +285,7 @@ def do_rdivmod(
 _R_rpow = TypeVar('_R_rpow')
 
 
+@set_module('optype')
 def do_rpow(a: _c.CanRPow[_T_rpow, _R_rpow], b: _T_rpow, /) -> _R_rpow:
     """Same as `b ** a`."""
     return b**a
@@ -276,6 +295,7 @@ def do_rpow(a: _c.CanRPow[_T_rpow, _R_rpow], b: _T_rpow, /) -> _R_rpow:
 _R_rlshift = TypeVar('_R_rlshift')
 
 
+@set_module('optype')
 def do_rlshift(
     a: _c.CanRLshift[_T_rlshift, _R_rlshift],
     b: _T_rlshift,
@@ -289,6 +309,7 @@ def do_rlshift(
 _R_rrshift = TypeVar('_R_rrshift')
 
 
+@set_module('optype')
 def do_rrshift(
     a: _c.CanRRshift[_T_rrshift, _R_rrshift],
     b: _T_rrshift,
@@ -302,6 +323,7 @@ def do_rrshift(
 _R_rand = TypeVar('_R_rand')
 
 
+@set_module('optype')
 def do_rand(a: _c.CanRAnd[_T_rand, _R_rand], b: _T_rand, /) -> _R_rand:
     """Same as `b & a`."""
     return b & a
@@ -311,6 +333,7 @@ def do_rand(a: _c.CanRAnd[_T_rand, _R_rand], b: _T_rand, /) -> _R_rand:
 _R_rxor = TypeVar('_R_rxor')
 
 
+@set_module('optype')
 def do_rxor(a: _c.CanRXor[_T_rxor, _R_rxor], b: _T_rxor, /) -> _R_rxor:
     """Same as `b ^ a`."""
     return b ^ a
@@ -320,6 +343,7 @@ def do_rxor(a: _c.CanRXor[_T_rxor, _R_rxor], b: _T_rxor, /) -> _R_rxor:
 _R_ror = TypeVar('_R_ror')
 
 
+@set_module('optype')
 def do_ror(a: _c.CanROr[_T_ror, _R_ror], b: _T_ror, /) -> _R_ror:
     """Same as `b | a`."""
     return b | a
@@ -362,6 +386,7 @@ def do_ror(a: _c.CanROr[_T_ror, _R_ror], b: _T_ror, /) -> _R_ror:
 
 
 # type-check the custom ops
+# TODO: move these to `tests/do.py`
 if TYPE_CHECKING:
     _do_call: _d.DoesCall = do_call
 
diff --git a/optype/_does.py b/optype/_does.py
index 078ada6b..dd44d859 100644
--- a/optype/_does.py
+++ b/optype/_does.py
@@ -2,14 +2,16 @@
 from collections.abc import Callable
 from typing import Any, Literal, TypeAlias
 
-import optype._can as _c
-
 
 if sys.version_info >= (3, 13):
     from typing import ParamSpec, Protocol, TypeVar, final, overload
 else:
     from typing_extensions import ParamSpec, Protocol, TypeVar, final, overload
 
+import optype._can as _c
+
+from ._utils import set_module
+
 
 # iteration
 
@@ -18,6 +20,7 @@
 _D_next = TypeVar('_D_next')
 
 
+@set_module('optype')
 @final
 class DoesNext(Protocol):
     @overload
@@ -39,6 +42,7 @@ def __call__(
 _D_anext = TypeVar('_D_anext')
 
 
+@set_module('optype')
 @final
 class DoesANext(Protocol):
     @overload
@@ -61,6 +65,7 @@ async def __call__(
 _R_iter = TypeVar('_R_iter', bound=_c.CanNext[Any])
 
 
+@set_module('optype')
 @final
 class DoesIter(Protocol):
     @overload
@@ -94,6 +99,7 @@ def __call__(
 _R_aiter = TypeVar('_R_aiter', bound=_c.CanANext[Any])
 
 
+@set_module('optype')
 @final
 class DoesAIter(Protocol):
     def __call__(self, iterable: _c.CanAIter[_R_aiter], /) -> _R_aiter: ...
@@ -101,11 +107,13 @@ def __call__(self, iterable: _c.CanAIter[_R_aiter], /) -> _R_aiter: ...
 
 # type conversion
 
+@set_module('optype')
 @final
 class DoesComplex(Protocol):
     def __call__(self, obj: _c.CanComplex, /) -> complex: ...
 
 
+@set_module('optype')
 @final
 class DoesFloat(Protocol):
     def __call__(self, obj: _c.CanFloat, /) -> float: ...
@@ -114,6 +122,7 @@ def __call__(self, obj: _c.CanFloat, /) -> float: ...
 _R_int = TypeVar('_R_int', bound=int)
 
 
+@set_module('optype')
 @final
 class DoesInt(Protocol):
     def __call__(self, obj: _c.CanInt[_R_int], /) -> _R_int: ...
@@ -132,6 +141,7 @@ def __call__(self, obj: _c.CanInt[_R_int], /) -> _R_int: ...
 _R_bool = TypeVar('_R_bool', _IsTrue, _IsFalse, bool)
 
 
+@set_module('optype')
 @final
 class DoesBool(Protocol):
     @overload
@@ -149,6 +159,7 @@ def __call__(self, obj: object, /) -> _IsTrue: ...
 _R_str = TypeVar('_R_str', bound=str)
 
 
+@set_module('optype')
 @final
 class DoesStr(Protocol):
     def __call__(self, obj: _c.CanStr[_R_str], /) -> _R_str: ...
@@ -157,6 +168,7 @@ def __call__(self, obj: _c.CanStr[_R_str], /) -> _R_str: ...
 _R_bytes = TypeVar('_R_bytes', bound=bytes)
 
 
+@set_module('optype')
 @final
 class DoesBytes(Protocol):
     def __call__(self, obj: _c.CanBytes[_R_bytes], /) -> _R_bytes: ...
@@ -168,6 +180,7 @@ def __call__(self, obj: _c.CanBytes[_R_bytes], /) -> _R_bytes: ...
 _R_repr = TypeVar('_R_repr', bound=str)
 
 
+@set_module('optype')
 @final
 class DoesRepr(Protocol):
     def __call__(self, obj: _c.CanRepr[_R_repr], /) -> _R_repr: ...
@@ -177,6 +190,7 @@ def __call__(self, obj: _c.CanRepr[_R_repr], /) -> _R_repr: ...
 _R_format = TypeVar('_R_format', bound=str)
 
 
+@set_module('optype')
 @final
 class DoesFormat(Protocol):
     def __call__(
@@ -195,6 +209,7 @@ def __call__(
 _R_lt = TypeVar('_R_lt')
 
 
+@set_module('optype')
 @final
 class DoesLt(Protocol):
     @overload
@@ -218,6 +233,7 @@ def __call__(
 _R_le = TypeVar('_R_le')
 
 
+@set_module('optype')
 @final
 class DoesLe(Protocol):
     @overload
@@ -241,6 +257,7 @@ def __call__(
 _R_eq = TypeVar('_R_eq')
 
 
+@set_module('optype')
 @final
 class DoesEq(Protocol):
     @overload
@@ -264,6 +281,7 @@ def __call__(  # pyright: ignore[reportOverlappingOverload]
 _R_ne = TypeVar('_R_ne')
 
 
+@set_module('optype')
 @final
 class DoesNe(Protocol):
     @overload
@@ -287,6 +305,7 @@ def __call__(  # pyright: ignore[reportOverlappingOverload]
 _R_gt = TypeVar('_R_gt')
 
 
+@set_module('optype')
 @final
 class DoesGt(Protocol):
     @overload
@@ -310,6 +329,7 @@ def __call__(
 _R_ge = TypeVar('_R_ge')
 
 
+@set_module('optype')
 @final
 class DoesGe(Protocol):
     @overload
@@ -336,6 +356,7 @@ def __call__(
 _D_getattr = TypeVar('_D_getattr')
 
 
+@set_module('optype')
 @final
 class DoesGetattr(Protocol):
     @overload
@@ -374,6 +395,7 @@ def __call__(
 _V_setattr = TypeVar('_V_setattr')
 
 
+@set_module('optype')
 @final
 class DoesSetattr(Protocol):
     def __call__(
@@ -388,6 +410,7 @@ def __call__(
 _N_delattr = TypeVar('_N_delattr', bound=str)
 
 
+@set_module('optype')
 @final
 class DoesDelattr(Protocol):
     def __call__(
@@ -401,6 +424,7 @@ def __call__(
 _R_dir = TypeVar('_R_dir', bound=_c.CanIter[Any])
 
 
+@set_module('optype')
 @final
 class DoesDir(Protocol):
     @overload
@@ -415,6 +439,7 @@ def __call__(self, obj: _c.CanDir[_R_dir], /) -> _R_dir: ...
 _R_call = TypeVar('_R_call')
 
 
+@set_module('optype')
 @final
 class DoesCall(Protocol):
     def __call__(
@@ -433,6 +458,7 @@ def __call__(
 _R_len = TypeVar('_R_len', bound=int)
 
 
+@set_module('optype')
 @final
 class DoesLen(Protocol):
     def __call__(self, obj: _c.CanLen[_R_len], /) -> _R_len: ...
@@ -441,6 +467,7 @@ def __call__(self, obj: _c.CanLen[_R_len], /) -> _R_len: ...
 _R_length_hint = TypeVar('_R_length_hint', bound=int)
 
 
+@set_module('optype')
 @final
 class DoesLengthHint(Protocol):
     def __call__(
@@ -455,6 +482,7 @@ def __call__(
 _D_getitem = TypeVar('_D_getitem')
 
 
+@set_module('optype')
 @final
 class DoesGetitem(Protocol):
     def __call__(
@@ -472,6 +500,7 @@ def __call__(
 _V_setitem = TypeVar('_V_setitem')
 
 
+@set_module('optype')
 @final
 class DoesSetitem(Protocol):
     def __call__(
@@ -486,6 +515,7 @@ def __call__(
 _K_delitem = TypeVar('_K_delitem')
 
 
+@set_module('optype')
 @final
 class DoesDelitem(Protocol):
     def __call__(
@@ -500,6 +530,7 @@ def __call__(
 _D_missing = TypeVar('_D_missing')
 
 
+@set_module('optype')
 @final
 class DoesMissing(Protocol):
     def __call__(
@@ -514,6 +545,7 @@ def __call__(
 _R_contains = TypeVar('_R_contains', Literal[True], Literal[False], bool)
 
 
+@set_module('optype')
 @final
 class DoesContains(Protocol):
     def __call__(
@@ -528,6 +560,7 @@ def __call__(
 _R_reversed = TypeVar('_R_reversed')
 
 
+@set_module('optype')
 @final
 class DoesReversed(Protocol):
     """
@@ -558,6 +591,7 @@ def __call__(
 _R_add = TypeVar('_R_add')
 
 
+@set_module('optype')
 @final
 class DoesAdd(Protocol):
     @overload
@@ -581,6 +615,7 @@ def __call__(
 _R_sub = TypeVar('_R_sub')
 
 
+@set_module('optype')
 @final
 class DoesSub(Protocol):
     @overload
@@ -604,6 +639,7 @@ def __call__(
 _R_mul = TypeVar('_R_mul')
 
 
+@set_module('optype')
 @final
 class DoesMul(Protocol):
     @overload
@@ -627,6 +663,7 @@ def __call__(
 _R_matmul = TypeVar('_R_matmul')
 
 
+@set_module('optype')
 @final
 class DoesMatmul(Protocol):
     @overload
@@ -650,6 +687,7 @@ def __call__(
 _R_truediv = TypeVar('_R_truediv')
 
 
+@set_module('optype')
 @final
 class DoesTruediv(Protocol):
     @overload
@@ -673,6 +711,7 @@ def __call__(
 _R_floordiv = TypeVar('_R_floordiv')
 
 
+@set_module('optype')
 @final
 class DoesFloordiv(Protocol):
     @overload
@@ -696,6 +735,7 @@ def __call__(
 _R_mod = TypeVar('_R_mod')
 
 
+@set_module('optype')
 @final
 class DoesMod(Protocol):
     @overload
@@ -719,6 +759,7 @@ def __call__(
 _R_divmod = TypeVar('_R_divmod')
 
 
+@set_module('optype')
 @final
 class DoesDivmod(Protocol):
     @overload
@@ -743,6 +784,7 @@ def __call__(
 _R_pow = TypeVar('_R_pow')
 
 
+@set_module('optype')
 @final
 class DoesPow(Protocol):
     @overload
@@ -774,6 +816,7 @@ def __call__(
 _R_lshift = TypeVar('_R_lshift')
 
 
+@set_module('optype')
 @final
 class DoesLshift(Protocol):
     @overload
@@ -797,6 +840,7 @@ def __call__(
 _R_rshift = TypeVar('_R_rshift')
 
 
+@set_module('optype')
 @final
 class DoesRshift(Protocol):
     @overload
@@ -820,6 +864,7 @@ def __call__(
 _R_and = TypeVar('_R_and')
 
 
+@set_module('optype')
 @final
 class DoesAnd(Protocol):
     @overload
@@ -843,6 +888,7 @@ def __call__(
 _R_xor = TypeVar('_R_xor')
 
 
+@set_module('optype')
 @final
 class DoesXor(Protocol):
     @overload
@@ -866,6 +912,7 @@ def __call__(
 _R_or = TypeVar('_R_or')
 
 
+@set_module('optype')
 @final
 class DoesOr(Protocol):
     @overload
@@ -891,6 +938,7 @@ def __call__(
 _R_radd = TypeVar('_R_radd')
 
 
+@set_module('optype')
 @final
 class DoesRAdd(Protocol):
     def __call__(
@@ -905,6 +953,7 @@ def __call__(
 _R_rsub = TypeVar('_R_rsub')
 
 
+@set_module('optype')
 @final
 class DoesRSub(Protocol):
     def __call__(
@@ -919,6 +968,7 @@ def __call__(
 _R_rmul = TypeVar('_R_rmul')
 
 
+@set_module('optype')
 @final
 class DoesRMul(Protocol):
     def __call__(
@@ -933,6 +983,7 @@ def __call__(
 _R_rmatmul = TypeVar('_R_rmatmul')
 
 
+@set_module('optype')
 @final
 class DoesRMatmul(Protocol):
     def __call__(
@@ -947,6 +998,7 @@ def __call__(
 _R_rtruediv = TypeVar('_R_rtruediv')
 
 
+@set_module('optype')
 @final
 class DoesRTruediv(Protocol):
     def __call__(
@@ -961,6 +1013,7 @@ def __call__(
 _R_rfloordiv = TypeVar('_R_rfloordiv')
 
 
+@set_module('optype')
 @final
 class DoesRFloordiv(Protocol):
     def __call__(
@@ -975,6 +1028,7 @@ def __call__(
 _R_rmod = TypeVar('_R_rmod')
 
 
+@set_module('optype')
 @final
 class DoesRMod(Protocol):
     def __call__(
@@ -1002,6 +1056,7 @@ def __call__(
 _R_rpow = TypeVar('_R_rpow')
 
 
+@set_module('optype')
 @final
 class DoesRPow(Protocol):
     def __call__(
@@ -1016,6 +1071,7 @@ def __call__(
 _R_rlshift = TypeVar('_R_rlshift')
 
 
+@set_module('optype')
 @final
 class DoesRLshift(Protocol):
     def __call__(
@@ -1030,6 +1086,7 @@ def __call__(
 _R_rrshift = TypeVar('_R_rrshift')
 
 
+@set_module('optype')
 @final
 class DoesRRshift(Protocol):
     def __call__(
@@ -1044,6 +1101,7 @@ def __call__(
 _R_rand = TypeVar('_R_rand')
 
 
+@set_module('optype')
 @final
 class DoesRAnd(Protocol):
     def __call__(
@@ -1058,6 +1116,7 @@ def __call__(
 _R_rxor = TypeVar('_R_rxor')
 
 
+@set_module('optype')
 @final
 class DoesRXor(Protocol):
     def __call__(
@@ -1072,6 +1131,7 @@ def __call__(
 _R_ror = TypeVar('_R_ror')
 
 
+@set_module('optype')
 @final
 class DoesROr(Protocol):
     def __call__(
@@ -1090,6 +1150,7 @@ def __call__(
 _R_iadd = TypeVar('_R_iadd')
 
 
+@set_module('optype')
 @final
 class DoesIAdd(Protocol):
     @overload
@@ -1120,6 +1181,7 @@ def __call__(
 _R_isub = TypeVar('_R_isub')
 
 
+@set_module('optype')
 @final
 class DoesISub(Protocol):
     @overload
@@ -1150,6 +1212,7 @@ def __call__(
 _R_imul = TypeVar('_R_imul')
 
 
+@set_module('optype')
 @final
 class DoesIMul(Protocol):
     @overload
@@ -1180,6 +1243,7 @@ def __call__(
 _R_imatmul = TypeVar('_R_imatmul')
 
 
+@set_module('optype')
 @final
 class DoesIMatmul(Protocol):
     @overload
@@ -1210,6 +1274,7 @@ def __call__(
 _R_itruediv = TypeVar('_R_itruediv')
 
 
+@set_module('optype')
 @final
 class DoesITruediv(Protocol):
     @overload
@@ -1240,6 +1305,7 @@ def __call__(
 _R_ifloordiv = TypeVar('_R_ifloordiv')
 
 
+@set_module('optype')
 @final
 class DoesIFloordiv(Protocol):
     @overload
@@ -1270,6 +1336,7 @@ def __call__(
 _R_imod = TypeVar('_R_imod')
 
 
+@set_module('optype')
 @final
 class DoesIMod(Protocol):
     @overload
@@ -1300,6 +1367,7 @@ def __call__(
 _R_ipow = TypeVar('_R_ipow')
 
 
+@set_module('optype')
 @final
 class DoesIPow(Protocol):
     @overload
@@ -1327,6 +1395,7 @@ def __call__(self,
 _R_ilshift = TypeVar('_R_ilshift')
 
 
+@set_module('optype')
 @final
 class DoesILshift(Protocol):
     @overload
@@ -1357,6 +1426,7 @@ def __call__(
 _R_irshift = TypeVar('_R_irshift')
 
 
+@set_module('optype')
 @final
 class DoesIRshift(Protocol):
     @overload
@@ -1387,6 +1457,7 @@ def __call__(
 _R_iand = TypeVar('_R_iand')
 
 
+@set_module('optype')
 @final
 class DoesIAnd(Protocol):
     @overload
@@ -1417,6 +1488,7 @@ def __call__(
 _R_ixor = TypeVar('_R_ixor')
 
 
+@set_module('optype')
 @final
 class DoesIXor(Protocol):
     @overload
@@ -1447,6 +1519,7 @@ def __call__(
 _R_ior = TypeVar('_R_ior')
 
 
+@set_module('optype')
 @final
 class DoesIOr(Protocol):
     @overload
@@ -1477,6 +1550,7 @@ def __call__(
 _R_neg = TypeVar('_R_neg')
 
 
+@set_module('optype')
 @final
 class DoesNeg(Protocol):
     def __call__(self, val: _c.CanNeg[_R_neg], /) -> _R_neg: ...
@@ -1485,6 +1559,7 @@ def __call__(self, val: _c.CanNeg[_R_neg], /) -> _R_neg: ...
 _R_pos = TypeVar('_R_pos')
 
 
+@set_module('optype')
 @final
 class DoesPos(Protocol):
     def __call__(self, val: _c.CanPos[_R_pos], /) -> _R_pos: ...
@@ -1493,6 +1568,7 @@ def __call__(self, val: _c.CanPos[_R_pos], /) -> _R_pos: ...
 _R_abs = TypeVar('_R_abs')
 
 
+@set_module('optype')
 @final
 class DoesAbs(Protocol):
     def __call__(self, val: _c.CanAbs[_R_abs], /) -> _R_abs: ...
@@ -1501,6 +1577,7 @@ def __call__(self, val: _c.CanAbs[_R_abs], /) -> _R_abs: ...
 _R_invert = TypeVar('_R_invert')
 
 
+@set_module('optype')
 @final
 class DoesInvert(Protocol):
     def __call__(self, val: _c.CanInvert[_R_invert], /) -> _R_invert: ...
@@ -1512,11 +1589,13 @@ def __call__(self, val: _c.CanInvert[_R_invert], /) -> _R_invert: ...
 _R_index = TypeVar('_R_index', bound=int)
 
 
+@set_module('optype')
 @final
 class DoesIndex(Protocol):
     def __call__(self, obj: _c.CanIndex[_R_index], /) -> _R_index: ...
 
 
+@set_module('optype')
 @final
 class DoesHash(Protocol):
     def __call__(self, obj: _c.CanHash, /) -> int: ...
@@ -1528,6 +1607,7 @@ def __call__(self, obj: _c.CanHash, /) -> int: ...
 _R_round = TypeVar('_R_round')
 
 
+@set_module('optype')
 @final
 class DoesRound(Protocol):
     @overload
@@ -1555,6 +1635,7 @@ def __call__(
 _R_trunc = TypeVar('_R_trunc')
 
 
+@set_module('optype')
 @final
 class DoesTrunc(Protocol):
     def __call__(self, obj: _c.CanTrunc[_R_trunc], /) -> _R_trunc: ...
@@ -1563,6 +1644,7 @@ def __call__(self, obj: _c.CanTrunc[_R_trunc], /) -> _R_trunc: ...
 _R_floor = TypeVar('_R_floor')
 
 
+@set_module('optype')
 @final
 class DoesFloor(Protocol):
     def __call__(self, obj: _c.CanFloor[_R_floor], /) -> _R_floor: ...
@@ -1571,6 +1653,7 @@ def __call__(self, obj: _c.CanFloor[_R_floor], /) -> _R_floor: ...
 _R_ceil = TypeVar('_R_ceil')
 
 
+@set_module('optype')
 @final
 class DoesCeil(Protocol):
     def __call__(self, obj: _c.CanCeil[_R_ceil], /) -> _R_ceil: ...
diff --git a/optype/_has.py b/optype/_has.py
index e957a7ec..7c685b20 100644
--- a/optype/_has.py
+++ b/optype/_has.py
@@ -29,7 +29,9 @@
         runtime_checkable,
     )
 
-import optype._can as _c
+
+from ._can import CanIter, CanNext
+from ._utils import set_module
 
 
 if TYPE_CHECKING:
@@ -48,6 +50,7 @@
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class HasMatchArgs(Protocol[_V_match_args]):
     __match_args__: ClassVar[tuple[str, ...] | list[str]]
@@ -56,11 +59,12 @@ class HasMatchArgs(Protocol[_V_match_args]):
 _V_slots = TypeVar(
     '_V_slots',
     infer_variance=True,
-    bound=str | _c.CanIter[_c.CanNext[str]],
+    bound=str | CanIter[CanNext[str]],
     default=Any,
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class HasSlots(Protocol[_V_slots]):
     __slots__: Final[_V_slots]
@@ -74,6 +78,7 @@ class HasSlots(Protocol[_V_slots]):
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class HasDict(Protocol[_V_dict]):
     # the typeshed annotations for `builtins.object.__dict__` too narrow
@@ -88,6 +93,7 @@ class HasDict(Protocol[_V_dict]):
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class HasClass(Protocol[_V_class_set]):
     @property
@@ -105,6 +111,7 @@ def __class__(  # pyright: ignore[reportIncompatibleMethodOverride]
 _V_module = TypeVar('_V_module', infer_variance=True, bound=str, default=str)
 
 
+@set_module('optype')
 @runtime_checkable
 class HasModule(Protocol[_V_module]):
     __module__: _V_module
@@ -113,6 +120,7 @@ class HasModule(Protocol[_V_module]):
 _V_name = TypeVar('_V_name', infer_variance=True, bound=str, default=str)
 
 
+@set_module('optype')
 @runtime_checkable
 class HasName(Protocol[_V_name]):
     __name__: _V_name
@@ -126,6 +134,7 @@ class HasName(Protocol[_V_name]):
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class HasQualname(Protocol[_V_qualname]):
     __qualname__: _V_qualname
@@ -145,6 +154,7 @@ class HasQualname(Protocol[_V_qualname]):
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class HasNames(
     HasName[_V_names_name],
@@ -165,6 +175,7 @@ class HasNames(
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class HasDoc(Protocol[_V_doc]):
     # note that docstrings are stripped if ran with e.g. `python -OO`
@@ -179,6 +190,7 @@ class HasDoc(Protocol[_V_doc]):
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class HasAnnotations(Protocol[_V_annotations]):
     __annotations__: _V_annotations  # pyright: ignore[reportIncompatibleVariableOverride]
@@ -188,6 +200,7 @@ class HasAnnotations(Protocol[_V_annotations]):
 _Ps_type_params = TypeVarTuple('_Ps_type_params')
 
 
+@set_module('optype')
 @runtime_checkable
 class HasTypeParams(Protocol[Unpack[_Ps_type_params]]):
     # Note that `*Ps: (TypeVar, ParamSpec, TypeVarTuple)` should hold
@@ -200,6 +213,7 @@ class HasTypeParams(Protocol[Unpack[_Ps_type_params]]):
 _V_func = TypeVar('_V_func', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class HasFunc(Protocol[_Pss_func, _V_func]):
     __func__: Callable[_Pss_func, _V_func]
@@ -209,6 +223,7 @@ class HasFunc(Protocol[_Pss_func, _V_func]):
 _V_wrapped = TypeVar('_V_wrapped', infer_variance=True)
 
 
+@set_module('optype')
 @runtime_checkable
 class HasWrapped(Protocol[_Pss_wrapped, _V_wrapped]):
     __wrapped__: Callable[_Pss_wrapped, _V_wrapped]
@@ -222,12 +237,14 @@ class HasWrapped(Protocol[_Pss_wrapped, _V_wrapped]):
 )
 
 
+@set_module('optype')
 @runtime_checkable
 class HasSelf(Protocol[_V_self]):
     @property
     def __self__(self) -> _V_self: ...
 
 
+@set_module('optype')
 @runtime_checkable
 class HasCode(Protocol):
     __code__: CodeType
diff --git a/optype/_utils.py b/optype/_utils.py
new file mode 100644
index 00000000..06475293
--- /dev/null
+++ b/optype/_utils.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+import sys
+
+
+if sys.version_info >= (3, 13):
+    from typing import LiteralString, Protocol, TypeVar, final
+else:
+    from typing_extensions import (  # noqa: TCH002
+        LiteralString,
+        Protocol,
+        TypeVar,
+        final,
+    )
+
+
+__all__ = ('set_module',)
+
+
+# cannot reuse `optype._has._HasModule` due to circular imports
+class _HasModule(Protocol):
+    __module__: str
+
+
+_HasModuleT = TypeVar('_HasModuleT', bound=_HasModule)
+
+
+@final
+class _DoesSetModule(Protocol):
+    def __call__(self, obj: _HasModuleT, /) -> _HasModuleT: ...
+
+
+def set_module(module: LiteralString, /) -> _DoesSetModule:
+    """
+    Private decorator for overriding the `__module__` of a function or a class.
+
+    If used on a function that `typing.overload`s, then apply `@set_module`
+    to each overload *first*, to avoid breaking `typing.get_overloads`.
+    For example:
+
+    ```python
+    from typing import overload
+
+    @overload
+    @set_module('spamlib')
+    def process(response: None, /) -> None: ...
+
+    @overload
+    @set_module('spamlib')
+    def process(response: bytes, /) -> str: ...
+
+    @set_module('spamlib')
+    def process(response: byes | None, /) -> str | None:
+        ...  # implementation here
+    ```
+    """
+    assert module
+    assert all(map(str.isidentifier, module.split('.')))
+
+    def do_set_module(has_module: _HasModuleT, /) -> _HasModuleT:
+        assert hasattr(has_module, '__module__')
+        has_module.__module__ = module
+        return has_module
+
+    return do_set_module


Test patch:
diff --git a/tests/helpers.py b/tests/helpers.py
index ab7161d7..3c5d8e61 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -22,7 +22,7 @@ def get_callable_members(module: ModuleType, /) -> frozenset[str]:
     """
     Return the public callables of a module, that aren't protocols, and
     """
-    module_blacklist = {'typing', 'typing_extensions'}
+    module_blacklist = {'typing', 'typing_extensions', 'optype._utils'}
     return frozenset({
         name for name in dir(module)
         if not name.startswith('_')
diff --git a/tests/test_protocols.py b/tests/test_protocols.py
index f877e2ff..c7c1a653 100644
--- a/tests/test_protocols.py
+++ b/tests/test_protocols.py
@@ -79,7 +79,9 @@ def test_name_matches_dunder(cls: type):
     than it has super optypes. I.e. require at most 1 member (attr, prop or
     method) for each **concrete** Protocol.
     """
-    prefix = cls.__module__.rsplit('.', 1)[1].removeprefix('_').title()
+    assert cls.__module__ == 'optype'
+
+    prefix = cls.__qualname__[:3]
     assert prefix in {'Can', 'Has'}
 
     name = cls.__name__