From a852f8c8a3c54b7632fc7b449f457a933cf46b77 Mon Sep 17 00:00:00 2001 From: Ash Berlin-Taylor Date: Tue, 9 Dec 2025 14:34:16 +0000 Subject: [PATCH] Don't eagerly load the Locale, nor pytest until it's needed. The main problem here was that importing pendulum.testing.traveller was importing time_machine, which was also importing pytest. And creating the DifferenceFormatter at import time was also loading the locale. Both of these are "optional" in the sense that there is lots you can do in Pendulum without ever using them, so we can relatively straightforwardly delay things until they are requested. Tested by running `uv run --with time_machine -p 3.12 python -Ximporttime -c 'import pendulum'`: - Before: ~50-64ms (a lot of noise) - After: 20-29ms Not a huge slow down, but I figured it was worth it anyway, especially not not load pytest. --- src/pendulum/__init__.py | 41 ++++++++++++++++++++++++++++------ src/pendulum/helpers.py | 23 ++++++++++++++++--- src/pendulum/locales/locale.py | 3 ++- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/pendulum/__init__.py b/src/pendulum/__init__.py index ba9a0cb9..bf2b4972 100644 --- a/src/pendulum/__init__.py +++ b/src/pendulum/__init__.py @@ -2,6 +2,8 @@ import datetime as _datetime +from functools import cache +from typing import TYPE_CHECKING from typing import Any from typing import Union from typing import cast @@ -30,7 +32,6 @@ from pendulum.helpers import week_starts_at from pendulum.interval import Interval from pendulum.parser import parse as parse -from pendulum.testing.traveller import Traveller from pendulum.time import Time from pendulum.tz import UTC from pendulum.tz import fixed_timezone @@ -334,17 +335,43 @@ def interval( return Interval(start, end, absolute=absolute) -# Testing +if TYPE_CHECKING: + from pendulum.testing.traveller import Traveller -_traveller = Traveller(DateTime) + _traveller = Traveller(DateTime) + freeze = _traveller.freeze + travel = _traveller.travel + travel_to = _traveller.travel_to + travel_back = _traveller.travel_back +else: + # We do this in an if-not-typing block so we don't have to duplicate the function signatures. + @cache + def _traveller() -> Traveller: + # Lazy load this, so we don't eagerly load Pytest if we don't need to + from pendulum.testing.traveller import Traveller -freeze = _traveller.freeze -travel = _traveller.travel -travel_to = _traveller.travel_to -travel_back = _traveller.travel_back + return Traveller(DateTime) + + def freeze(*args, **kwargs) -> Traveller: + return _traveller().freeze(*args, **kwargs) + + def travel(*args, **kwargs): + _traveller().travel(*args, **kwargs) + + def travel_to(*args, **kwargs): + _traveller().travel_to(*args, **kwargs) + + def travel_back(*args, **kwargs): + _traveller().travel_back(*args, **kwargs) def __getattr__(name: str) -> Any: + if name == "Traveller": + # This wasn't in `__all__`, but it was defined before, so keep it for back compat + from pendulum.testing.traveller import Traveller + + return Traveller + if name == "__version__": import importlib.metadata import warnings diff --git a/src/pendulum/helpers.py b/src/pendulum/helpers.py index e9424d90..db42b31e 100644 --- a/src/pendulum/helpers.py +++ b/src/pendulum/helpers.py @@ -6,8 +6,10 @@ from datetime import date from datetime import datetime from datetime import timedelta +from functools import cache from math import copysign from typing import TYPE_CHECKING +from typing import Any from typing import TypeVar from typing import overload @@ -15,7 +17,6 @@ from pendulum.constants import DAYS_PER_MONTHS from pendulum.day import WeekDay -from pendulum.formatting.difference_formatter import DifferenceFormatter from pendulum.locales.locale import Locale @@ -23,6 +24,9 @@ # Prevent import cycles from pendulum.duration import Duration + # LazyLoaded + from pendulum.formatting.difference_formatter import DifferenceFormatter + with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1" _DT = TypeVar("_DT", bound=datetime) @@ -48,7 +52,7 @@ from pendulum._helpers import precise_diff # type: ignore[assignment] from pendulum._helpers import week_day -difference_formatter = DifferenceFormatter() +difference_formatter: DifferenceFormatter @overload @@ -164,7 +168,7 @@ def format_diff( if locale is None: locale = get_locale() - return difference_formatter.format(diff, is_now, absolute, locale) + return _difference_formatter().format(diff, is_now, absolute, locale) def _sign(x: float) -> int: @@ -202,6 +206,19 @@ def week_ends_at(wday: WeekDay) -> None: pendulum._WEEK_ENDS_AT = wday +@cache +def _difference_formatter() -> DifferenceFormatter: + from pendulum.formatting.difference_formatter import DifferenceFormatter + + return DifferenceFormatter() + + +def __getattr__(name: str) -> Any: + if name == "difference_formatter": + return _difference_formatter() + raise AttributeError(name) + + __all__ = [ "PreciseDiff", "add_duration", diff --git a/src/pendulum/locales/locale.py b/src/pendulum/locales/locale.py index d396ecbc..951ba8be 100644 --- a/src/pendulum/locales/locale.py +++ b/src/pendulum/locales/locale.py @@ -2,7 +2,6 @@ import re -from importlib import import_module, resources from pathlib import Path from typing import Any from typing import ClassVar @@ -24,6 +23,8 @@ def __init__(self, locale: str, data: Any) -> None: @classmethod def load(cls, locale: str | Locale) -> Locale: + from importlib import import_module, resources + if isinstance(locale, Locale): return locale