Skip to content

Core

typed_sentinels._core

Classes

Sentinel

Statically-typed sentinel object with singleton qualities.

Sentinel instances provide unique placeholder objects that maintain singleton behavior for a given type. They are particularly well-suited for use with types requiring parameters which are only available at runtime, where creating a default instance of the type may not be possible in advance, but the structural contract of the type is otherwise guaranteed to be fulfilled once present.

Examples:

Basic usage:

from typed_sentinels import Sentinel

SNTL = Sentinel()                         # Same as `typing.Any`
BYTES_SNTL = Sentinel(bytes)              # Same as `bytes`
TUPLE_SNTL = Sentinel[tuple[str, ...]]()  # Same as `tuple[str, ...]`

assert SNTL is not BYTES_SNTL                         # True
assert Sentinel() is SNTL                             # True
assert Sentinel(tuple[str, ...]) is TUPLE_SNTL        # True
assert Sentinel(tuple[bytes, ...]) is not TUPLE_SNTL  # True

To the type-checker, Sentinel objects are indistinguishable from an instance of the assigned type:

from typing import reveal_type

class Custom:
    def __init__(self, req_str: str, req_bytes: bytes) -> None:
        if not req_str or not req_bytes:
            raise ValueError

CUSTOM_SNTL = Sentinel(Custom)
reveal_type(CUSTOM_SNTL)  # Revealed type is `Custom` -> Runtime type is `Sentinel`

def example_func(b: bytes = BYTES_SNTL, c: Custom = CUSTOM_SNTL) -> Any:
    if not b:
        print('Sentinels are falsy so this check works')
    if not c: ...

This even works for complex types like Callable:

CALLABLE_SNTL = Sentinel[Callable[..., str]]()
reveal_type(CALLABLE_SNTL)  # Type of "CALLABLE_SNTL" is "(...) -> str"
Attributes
__slots__ class-attribute instance-attribute
__slots__ = ('__weakref__', '_hint')
hint property
hint: T

Type associated with this Sentinel instance.

Functions
__new__
__new__(hint: type[T]) -> T
__new__(
    hint: Sentinel
    | type[Sentinel]
    | EllipsisType
    | Literal[True, False]
    | None,
) -> Never
__new__(hint: Any) -> Any
__new__() -> T
__new__(hint: Any = _OBJECT) -> Any

Create or retrieve a Sentinel instance for the given hint type.

Parameters:

Name Type Description Default
hint T

Type that this Sentinel should represent. If not provided, and if the class has not been otherwise parameterized by subscription, this defaults to Any.

_OBJECT

Returns:

Type Description
T

Sentinel object instance for the given hint type, either created anew or retrievd from the class-level WeakValueDictionary cache. The Sentinel instance will appear to type-checkers as an instance of hint.

Raises:

Type Description
InvalidHintError

If hint is any of: Sentinel, Ellipsis, True, False, None, or a Sentinel instance.

SubscriptedTypeError

If provided both a subscripted type parameter and a direct type argument and the types should differ (e.g., Sentinel[A](B) will raise SubscriptedTypeError).

Source code in src/typed_sentinels/_core.py
def __new__(cls, hint: Any = _OBJECT, /) -> Any:
    """Create or retrieve a `Sentinel` instance for the given `hint` type.

    Parameters
    ----------
    hint : T, optional
        Type that this `Sentinel` should represent. If not provided, and if the class has not been otherwise
        parameterized by subscription, this defaults to `Any`.

    Returns
    -------
    T
        `Sentinel` object instance for the given `hint` type, either created anew or retrievd from the class-level
        `WeakValueDictionary` cache. The `Sentinel` instance will appear to type-checkers as an instance of `hint`.

    Raises
    ------
    InvalidHintError
        If `hint` is any of: `Sentinel`, `Ellipsis`, `True`, `False`, `None`, or a `Sentinel` instance.
    SubscriptedTypeError
        If provided both a subscripted type parameter and a direct type argument and the types should differ (e.g.,
        `Sentinel[A](B)` will raise `SubscriptedTypeError`).
    """
    if (_cls_hint := cls._cls_hint) is not _OBJECT:
        cls._cls_hint = _OBJECT
    if (hint is _OBJECT) and (_cls_hint is not _OBJECT):
        hint = _cls_hint
    if hint is _OBJECT:
        hint = Any

    key = (cls.__name__, hint)
    if (inst := cls._cls_cache.get(key)) is not None:
        return inst

    if hint not in (_OBJECT, Any) and (_cls_hint not in (_OBJECT, Any)):
        if (hint != _cls_hint) and (hint is not _cls_hint):
            raise SubscriptedTypeError(hint=hint, subscripted=_cls_hint)

    if isinstance(hint, Sentinel) or (hint in (Sentinel, Ellipsis, True, False, None)):
        raise InvalidHintError(hint)

    with cls._cls_lock:
        if (inst := cls._cls_cache.get(key)) is None:  # pragma: no cover
            inst = super().__new__(cls)
            super().__setattr__(inst, '_hint', hint)
            cls._cls_cache[key] = inst

    return inst
__class_getitem__
__class_getitem__(key: T) -> T
Source code in src/typed_sentinels/_core.py
def __class_getitem__(cls, key: Any) -> Any:
    cls._cls_hint = key
    return cls
__getitem__
__getitem__(key: T) -> T
Source code in src/typed_sentinels/_core.py
def __getitem__(self, key: Any) -> Any:
    return self
__call__
__call__(*args: Any, **kwargs: Any) -> T
Source code in src/typed_sentinels/_core.py
def __call__(self, *args: Any, **kwargs: Any) -> Any:
    return self
__str__
__str__() -> str
Source code in src/typed_sentinels/_core.py
def __str__(self) -> str:
    hint_str, hint_repr = str(self._hint), repr(self._hint)
    if ('[' in hint_repr) and ('.' not in hint_repr):
        hint_str = hint_repr
    elif hasattr(self._hint, '__name__'):
        hint_str = self._hint.__name__
    elif hasattr(self._hint, '__qualname__'):
        hint_str = self._hint.__qualname__
    if hint_str.startswith("<class '") and hint_str.endswith("'>"):  # pragma: no cover
        hint_str = hint_str[8:-2]
    return f'<Sentinel: {hint_str}>'
__repr__
__repr__() -> str
Source code in src/typed_sentinels/_core.py
def __repr__(self) -> str:
    return f'<Sentinel: {self._hint!r}>'
__hash__
__hash__() -> int
Source code in src/typed_sentinels/_core.py
def __hash__(self) -> int:
    return hash((self.__class__, self._hint))
__bool__
__bool__() -> Literal[False]
Source code in src/typed_sentinels/_core.py
def __bool__(self) -> bool:
    return False
__eq__
__eq__(other: object) -> bool
Source code in src/typed_sentinels/_core.py
def __eq__(self, other: object) -> bool:
    if not isinstance(other, self.__class__):
        return False
    return self.__class__ == other.__class__ and self._hint == other._hint
__copy__
__copy__() -> Sentinel[T]
Source code in src/typed_sentinels/_core.py
def __copy__(self) -> Any:
    return self
__deepcopy__
__deepcopy__(memo: Any) -> Sentinel[T]
Source code in src/typed_sentinels/_core.py
def __deepcopy__(self, memo: Any) -> Any:
    return self
__reduce__
__reduce__() -> tuple[Callable[..., Sentinel[T]], tuple[T]]
Source code in src/typed_sentinels/_core.py
def __reduce__(self) -> tuple[Callable[..., Sentinel], tuple[Any]]:
    return (self.__class__, (self._hint,))
__reduce_ex__
__reduce_ex__(
    protocol: SupportsIndex,
) -> tuple[Callable[..., Sentinel[T]], tuple[T]]
Source code in src/typed_sentinels/_core.py
def __reduce_ex__(self, protocol: SupportsIndex) -> tuple[Callable[..., Sentinel], tuple[Any]]:
    return self.__reduce__()
__setattr__
__setattr__(name: str, value: Any) -> Never
Source code in src/typed_sentinels/_core.py
def __setattr__(self, name: str, value: Any) -> NoReturn:
    msg = f'Cannot modify attributes of {self!r}'
    raise AttributeError(msg)
__delattr__
__delattr__(name: str) -> Never
Source code in src/typed_sentinels/_core.py
def __delattr__(self, name: str) -> NoReturn:
    msg = f'Cannot delete attributes of {self!r}'
    raise AttributeError(msg)

Functions

is_sentinel

is_sentinel(obj: Any) -> TypeGuard[Sentinel[Any]]
is_sentinel(obj: Any, typ: T) -> TypeGuard[Sentinel[T]]
is_sentinel(
    obj: Any, typ: Any = None
) -> TypeGuard[Sentinel]

Return True if obj is a Sentinel instance, optionally further narrowed to be of typ type.

Parameters:

Name Type Description Default
obj Any

Possible Sentinel instance.

required
typ T | None

Optional type to be used to further narrow the type of the Sentinel. If provided, and if obj is a Sentinel instance, this must match obj.hint.

None

Returns:

Type Description
TypeGuard[Sentinel]
  • True if obj is a Sentinel instance.
  • False otherwise.
Source code in src/typed_sentinels/_core.py
def is_sentinel(obj: Any, typ: Any = None) -> TypeGuard[Sentinel]:
    """Return `True` if `obj` is a `Sentinel` instance, optionally further narrowed to be of `typ` type.

    Parameters
    ----------
    obj : Any
        Possible `Sentinel` instance.
    typ : T | None, optional
        Optional type to be used to further narrow the type of the `Sentinel`. If provided, and if `obj` is a `Sentinel`
        instance, this must match `obj.hint`.

    Returns
    -------
    TypeGuard[Sentinel]
        - `True` if `obj` is a `Sentinel` instance.
        - `False` otherwise.
    """
    if typ is not None:
        if isinstance(obj, Sentinel) and hasattr(obj, 'hint'):  # pragma: no cover
            return obj.hint == typ
    return isinstance(obj, Sentinel)