Skip to content

Commit a852f8c

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 a852f8c

File tree

3 files changed

+56
-11
lines changed

3 files changed

+56
-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: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,27 @@
66
from datetime import date
77
from datetime import datetime
88
from datetime import timedelta
9+
from functools import cache
910
from math import copysign
1011
from typing import TYPE_CHECKING
12+
from typing import Any
1113
from typing import TypeVar
1214
from typing import overload
1315

1416
import pendulum
1517

1618
from pendulum.constants import DAYS_PER_MONTHS
1719
from pendulum.day import WeekDay
18-
from pendulum.formatting.difference_formatter import DifferenceFormatter
1920
from pendulum.locales.locale import Locale
2021

2122

2223
if TYPE_CHECKING:
2324
# Prevent import cycles
2425
from pendulum.duration import Duration
2526

27+
# LazyLoaded
28+
from pendulum.formatting.difference_formatter import DifferenceFormatter
29+
2630
with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"
2731

2832
_DT = TypeVar("_DT", bound=datetime)
@@ -48,7 +52,7 @@
4852
from pendulum._helpers import precise_diff # type: ignore[assignment]
4953
from pendulum._helpers import week_day
5054

51-
difference_formatter = DifferenceFormatter()
55+
difference_formatter: DifferenceFormatter
5256

5357

5458
@overload
@@ -164,7 +168,7 @@ def format_diff(
164168
if locale is None:
165169
locale = get_locale()
166170

167-
return difference_formatter.format(diff, is_now, absolute, locale)
171+
return _difference_formatter().format(diff, is_now, absolute, locale)
168172

169173

170174
def _sign(x: float) -> int:
@@ -202,6 +206,19 @@ def week_ends_at(wday: WeekDay) -> None:
202206
pendulum._WEEK_ENDS_AT = wday
203207

204208

209+
@cache
210+
def _difference_formatter() -> DifferenceFormatter:
211+
from pendulum.formatting.difference_formatter import DifferenceFormatter
212+
213+
return DifferenceFormatter()
214+
215+
216+
def __getattr__(name: str) -> Any:
217+
if name == "difference_formatter":
218+
return _difference_formatter()
219+
raise AttributeError(name)
220+
221+
205222
__all__ = [
206223
"PreciseDiff",
207224
"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)