Source code for betty.typing

"""
Providing typing utilities.
"""

from __future__ import annotations

from typing import TypeVar, TypeAlias, final, Any

from typing_extensions import TypeIs

from betty.docstring import append

_T = TypeVar("_T")


def _should_mark(target: Any, key: str) -> bool:
    attr_name = f"_betty_typing_{key}"
    if hasattr(target, attr_name):
        return False
    setattr(target, attr_name, True)
    return True


def _internal(target: _T) -> _T:
    if _should_mark(target, "internal"):
        target.__doc__ = append(
            target.__doc__ or "",
            "This is internal. It **MAY** be used anywhere in Betty's source code, but **MUST NOT** be used by third-party code.",
        )
    return target


[docs] @_internal def internal(target: _T) -> _T: """ Mark a target as internal to Betty. Anything decorated with ``@internal`` MAY be used anywhere in Betty's source code, but MUST be considered private by third-party code. """ return _internal(target)
[docs] @internal def public(target: _T) -> _T: """ Mark a target as publicly usable. This is intended for items nested inside something marked with :py:func:`betty.typing.internal`, such as class attributes: third-party code **SHOULD NOT** use a class marked ``@internal`` directly, but **MAY** use any of its attributes that are marked ``@public``. """ return target
[docs] def private(target: _T) -> _T: """ Mark a target as private to its containing scope. This is intended for items that cannot be marked private by prefixing their names with an underscore. """ if _should_mark(target, "private"): target.__doc__ = append( target.__doc__ or "", "This is private. It **MUST NOT** be used anywhere outside its containing scope.", ) return target
[docs] def pickleable(target: _T) -> _T: """ Mark a target as pickleable. """ if _should_mark(target, "pickleable"): target.__doc__ = append( target.__doc__ or "", "This can be pickled.", ) return target
[docs] def unpickleable(target: _T) -> _T: """ Mark a target as unpickleable. """ if _should_mark(target, "unpickleable"): target.__doc__ = append( target.__doc__ or "", "This can NOT be pickled, and MUST NOT be used between different processes.", ) def _reduce_ex(*_) -> None: raise RuntimeError(f"{target} is not pickleable.") target.__reduce_ex__ = _reduce_ex # type: ignore[assignment, method-assign] return target
[docs] def threadsafe(target: _T) -> _T: """ Mark a target as thread-safe. """ if _should_mark(target, "threadsafe"): target.__doc__ = append( target.__doc__ or "", "This is thread-safe, which means you can safely use this between different threads.", ) return target
[docs] def processsafe(target: _T) -> _T: """ Mark a target as process-safe. """ if _should_mark(target, "processsafe"): target = pickleable(target) target = threadsafe(target) target.__doc__ = append( target.__doc__ or "", "This is process-safe, which means you can safely use this between different processes.", ) return target
[docs] @final class Void: """ A sentinel that describes the absence of a value. Using this sentinel allows for actual values to be ``None``. Like ``None``, ``Void`` is only ever used through its type, and never instantiated. """
[docs] def __new__(cls): # pragma: no cover # noqa D102 raise RuntimeError("The Void sentinel cannot be instantiated.")
Voidable: TypeAlias = _T | type[Void]
[docs] def not_void(value: _T | type[Void]) -> TypeIs[_T]: """ Test that a value is not :py:class:`betty.typing.Void`. """ return value is not Void