Source code for plum._util

__all__ = (
    "Callable",
    "TypeHint",
    "Missing",
    "Comparable",
    "wrap_lambda",
    "is_in_class",
    "get_class",
    "get_context",
    "argsort",
)

import abc
import sys
from collections.abc import Callable, Sequence
from typing import Any, TypeAlias, TypeVar

# We use this to indicate a reader that we expect a type hint. Using just
# `object` as a type hint is technically correct for `int | None` for example,
# but does not convey the intention to a reader. Furthermore, if later on,
# Python has a proper type for type hints, we can just replace it here.
TypeHint: TypeAlias = object
T = TypeVar("T")
R = TypeVar("R")  # Return type


class _MissingType(type):
    """The type of :class:`Missing`."""

    def __bool__(self) -> bool:
        # For some reason, Sphinx does attempt to evaluate `bool(Missing)`.
        # Let's try to keep Sphinx working correctly by not raising an
        # exception.
        if "sphinx" in sys.modules:
            return False

        raise TypeError("`Missing` has no boolean value.")


[docs] class Missing(metaclass=_MissingType): """A class that can be used to indicate that a value is missing. This class cannot be instantiated and has no boolean value.""" def __init__(self) -> None: raise TypeError("`Missing` cannot be instantiated.")
[docs] class Comparable(metaclass=abc.ABCMeta): """A mixin that makes instances of the class comparable. Requires the subclass to just implement `__le__`. """ def __eq__(self, other: object, /) -> bool: return self <= other <= self def __ne__(self, other: object, /) -> bool: return not self == other @abc.abstractmethod def __le__(self, other: object, /) -> bool: pass # pragma: no cover def __lt__(self, other: object, /) -> bool: return self <= other and self != other def __ge__(self, other: object, /) -> bool: return other.__le__(self) # type: ignore[no-any-return,operator] def __gt__(self, other: object, /) -> bool: return self >= other and self != other
[docs] def is_comparable(self, other: object, /) -> bool: """Check whether this object is comparable with another one. Args: other (object): Object to check comparability with. Returns: Whether the object is comparable with `other`. """ return self < other or self == other or self > other
[docs] def wrap_lambda(f: Callable[[T], R], /) -> Callable[[T], R]: """Wrap a callable in a lambda function. Args: f (Callable): Function to wrap. Returns: function: Wrapped version of `f`. """ return lambda x: f(x)
[docs] def is_in_class(f: Callable[..., Any], /) -> bool: """Check if a function is part of a class. Args: f (function): Function to check. Returns: bool: Whether `f` is part of a class. """ parts = f.__qualname__.split(".") return len(parts) >= 2 and parts[-2] != "<locals>"
def _split_parts(f: Callable[..., Any], /) -> list[str]: # Under edge cases, `f.__module__` can be `None`. In this case we, skip it. # Otherwise, the fully-qualified name is the name of the module plus the qualified # name of the function. module = (f.__module__ + ".") if f.__module__ else "" return (module + f.__qualname__).split(".")
[docs] def get_class(f: Callable[..., Any], /) -> str: """Assuming that `f` is part of a class, get the fully qualified name of the class. Args: f (function): Method to get class name for. Returns: str: Fully qualified name of class. """ parts = _split_parts(f) return ".".join(parts[:-1])
[docs] def get_context(f: Callable[..., Any], /) -> str: """Get the fully qualified name of the context for `f`. If `f` is part of a class, then the context corresponds to the scope of the class. If `f` is not part of a class, then the context corresponds to the scope of the function. Args: f (function): Method to get context for. Returns: str: The context of `f`. """ parts = _split_parts(f) if is_in_class(f): # Split off function name and class. return ".".join(parts[:-2]) else: # Split off function name only. return ".".join(parts[:-1])
[docs] def argsort(seq: Sequence[int], /) -> list[int]: """Compute the indices that sort an integer sequence. Args: seq (Sequence[int]): Sequence of integers to sort. Returns: list[int]: Indices that sort `seq`. """ return sorted(range(len(seq)), key=seq.__getitem__)