Skip to content

Commit bc7ebbe

Browse files
committed
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.
1 parent 2982f25 commit bc7ebbe

File tree

3 files changed

+53
-11
lines changed

3 files changed

+53
-11
lines changed

src/pendulum/__init__.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import datetime as _datetime
44

5+
from functools import cache
6+
from typing import TYPE_CHECKING
57
from typing import Any
68
from typing import Union
79
from typing import cast
@@ -30,7 +32,6 @@
3032
from pendulum.helpers import week_starts_at
3133
from pendulum.interval import Interval
3234
from pendulum.parser import parse as parse
33-
from pendulum.testing.traveller import Traveller
3435
from pendulum.time import Time
3536
from pendulum.tz import UTC
3637
from pendulum.tz import fixed_timezone
@@ -334,17 +335,43 @@ def interval(
334335
return Interval(start, end, absolute=absolute)
335336

336337

337-
# Testing
338+
if TYPE_CHECKING:
339+
from pendulum.testing.traveller import Traveller
338340

339-
_traveller = Traveller(DateTime)
341+
_traveller = Traveller(DateTime)
342+
freeze = _traveller.freeze
343+
travel = _traveller.travel
344+
travel_to = _traveller.travel_to
345+
travel_back = _traveller.travel_back
346+
else:
347+
# We do this in an if-not-typing block so we don't have to duplicate the function signatures.
348+
@cache
349+
def _traveller() -> Traveller:
350+
# Lazy load this, so we don't eagerly load Pytest if we don't need to
351+
from pendulum.testing.traveller import Traveller
340352

341-
freeze = _traveller.freeze
342-
travel = _traveller.travel
343-
travel_to = _traveller.travel_to
344-
travel_back = _traveller.travel_back
353+
return Traveller(DateTime)
354+
355+
def freeze(*args, **kwargs) -> Traveller:
356+
return _traveller().freeze(*args, **kwargs)
357+
358+
def travel(*args, **kwargs):
359+
_traveller().travel(*args, **kwargs)
360+
361+
def travel_to(*args, **kwargs):
362+
_traveller().travel_to(*args, **kwargs)
363+
364+
def travel_back(*args, **kwargs):
365+
_traveller().travel_back(*args, **kwargs)
345366

346367

347368
def __getattr__(name: str) -> Any:
369+
if name == "Traveller":
370+
# This wasn't in `__all__`, but it was defined before, so keep it for back compat
371+
from pendulum.testing.traveller import Traveller
372+
373+
return Traveller
374+
348375
if name == "__version__":
349376
import importlib.metadata
350377
import warnings

src/pendulum/helpers.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@
88
from datetime import timedelta
99
from math import copysign
1010
from typing import TYPE_CHECKING
11+
from typing import Any
1112
from typing import TypeVar
1213
from typing import overload
1314

1415
import pendulum
1516

1617
from pendulum.constants import DAYS_PER_MONTHS
1718
from pendulum.day import WeekDay
18-
from pendulum.formatting.difference_formatter import DifferenceFormatter
1919
from pendulum.locales.locale import Locale
2020

2121

2222
if TYPE_CHECKING:
2323
# Prevent import cycles
2424
from pendulum.duration import Duration
2525

26+
# LazyLoad
27+
from pendulum.formatting.difference_formatter import DifferenceFormatter
28+
2629
with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"
2730

2831
_DT = TypeVar("_DT", bound=datetime)
@@ -48,7 +51,7 @@
4851
from pendulum._helpers import precise_diff # type: ignore[assignment]
4952
from pendulum._helpers import week_day
5053

51-
difference_formatter = DifferenceFormatter()
54+
difference_formatter: DifferenceFormatter
5255

5356

5457
@overload
@@ -164,7 +167,7 @@ def format_diff(
164167
if locale is None:
165168
locale = get_locale()
166169

167-
return difference_formatter.format(diff, is_now, absolute, locale)
170+
return __getattr__("difference_formatter").format(diff, is_now, absolute, locale)
168171

169172

170173
def _sign(x: float) -> int:
@@ -202,6 +205,17 @@ def week_ends_at(wday: WeekDay) -> None:
202205
pendulum._WEEK_ENDS_AT = wday
203206

204207

208+
def __getattr__(name: str) -> Any:
209+
if name == "difference_formatter":
210+
# Lazily create
211+
from pendulum.formatting.difference_formatter import DifferenceFormatter
212+
213+
global difference_formatter
214+
difference_formatter = DifferenceFormatter()
215+
return difference_formatter
216+
raise AttributeError(name)
217+
218+
205219
__all__ = [
206220
"PreciseDiff",
207221
"add_duration",

src/pendulum/locales/locale.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import re
44

5-
from importlib import import_module, resources
65
from pathlib import Path
76
from typing import Any
87
from typing import ClassVar
@@ -24,6 +23,8 @@ def __init__(self, locale: str, data: Any) -> None:
2423

2524
@classmethod
2625
def load(cls, locale: str | Locale) -> Locale:
26+
from importlib import import_module, resources
27+
2728
if isinstance(locale, Locale):
2829
return locale
2930

0 commit comments

Comments
 (0)