Source code for whenever._common

from __future__ import annotations

from collections.abc import Callable

# Unused imports are necessary for sphinc autodoc due to
# scoping issues introduced by add_alternate_constructors().
from datetime import (  # noqa: F401
    date as _date,
    datetime as _datetime,
    time as _time,
    timedelta as _timedelta,
    timezone as _timezone,
)
from functools import lru_cache
from typing import TYPE_CHECKING, Any, TypeVar, no_type_check
from warnings import warn

UTC = _timezone.utc
DUMMY_LEAP_YEAR = 4
Nanos = int  # 0-999_999_999

# A self-set variable to detect if we're being run by sphinx autodoc
try:
    from sphinx import (  # type: ignore[attr-defined, import-not-found, unused-ignore]
        SPHINX_RUNNING as SPHINX_RUNNING,
    )
except ImportError:
    SPHINX_RUNNING = False

# A sentinel value that looks nice in autodoc.
# Used in cases where `None` would be a valid value, or where we want to
# avoid allowing `None` to be passed in by users.
UNSET: Any = type(
    "UNSET", (), {"__repr__": lambda _: "...", "__bool__": lambda _: False}
)()


# We cache fixed-offset tzinfo objects to avoid creating multiple identical ones.
# It's very common to only have whole-hour offsets, so this helps a lot.
@lru_cache
def mk_fixed_tzinfo(secs: int, /) -> _timezone:
    return _timezone(_timedelta(seconds=secs))


def check_utc_bounds(dt: _datetime) -> _datetime:
    try:
        dt.astimezone(UTC)
    except (OverflowError, ValueError):
        raise ValueError("Instant out of range")
    return dt


# A custom warnings class to prevent silent deprecation warnings in user code.
# See https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries
[docs] class WheneverDeprecationWarning(UserWarning): """Raised when a deprecated feature of the ``whenever`` library is used. This is a custom warning class (not a subclass of :class:`DeprecationWarning`) so that deprecation warnings from this library are visible by default—unlike standard ``DeprecationWarning``, which Python silences in production code. """
_T = TypeVar("_T") # Basic behavior common to all classes class _Base: __slots__ = () # Immutable classes don't need to be copied @no_type_check def __copy__(self): return self @no_type_check def __deepcopy__(self, _): return self @no_type_check @classmethod def __get_pydantic_core_schema__(cls, *_, **kwargs): from ._utils import pydantic_schema return pydantic_schema(cls) @classmethod def parse_iso(cls: type[_T], s: str, /) -> _T: raise NotImplementedError # pragma: no cover if TYPE_CHECKING: from typing import final as final # re-export to suppress linting errors else: def final(cls): def init_subclass_not_allowed(cls, **kwargs): # pragma: no cover raise TypeError("Subclassing not allowed") cls.__init_subclass__ = init_subclass_not_allowed return cls _Tcall = TypeVar("_Tcall", bound=Callable[..., None]) # I'd love for this to be a decorator, but every attempt I made resulted # in mypy getting too confused. I've tried a lot. def add_alternate_constructors( init_default: _Tcall, py_type: type | None = None, deprecation_msg: str | None = None, ) -> _Tcall: """Add alternate constructors to a class's __init__ method.""" def __init__(self: Any, *args: Any, **kwargs: Any) -> None: match args: case [str() as iso_string] if not kwargs: if deprecation_msg: warn( deprecation_msg, WheneverDeprecationWarning, stacklevel=2, ) self._init_from_iso(iso_string) case [obj] if ( py_type is not None and not kwargs and isinstance(obj, py_type) ): self._init_from_py(obj) case _: init_default(self, *args, **kwargs) return __init__ # type: ignore[return-value]