Skip to content

Commit 4340909

Browse files
lusewellLuke Sewell
andauthored
Fix performance bug from cftime import (#5640)
Co-authored-by: Luke Sewell <lukeddsewell@gmail.com>
1 parent b8f164c commit 4340909

File tree

6 files changed

+61
-27
lines changed

6 files changed

+61
-27
lines changed

doc/whats-new.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ Deprecations
5959

6060
Bug fixes
6161
~~~~~~~~~
62+
- Fixed performance bug where ``cftime`` import attempted within various core operations if ``cftime`` not
63+
installed (:pull:`5640`).
64+
By `Luke Sewell <https://github.com/lusewell>`_
6265

6366
- Numbers are properly formatted in a plot's title (:issue:`5788`, :pull:`5789`).
6467
By `Maxime Liquet <https://github.com/maximlt>`_.

xarray/coding/cftime_offsets.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,15 @@
5252
from .cftimeindex import CFTimeIndex, _parse_iso8601_with_reso
5353
from .times import format_cftime_datetime
5454

55+
try:
56+
import cftime
57+
except ImportError:
58+
cftime = None
59+
5560

5661
def get_date_type(calendar):
5762
"""Return the cftime date type for a given calendar name."""
58-
try:
59-
import cftime
60-
except ImportError:
63+
if cftime is None:
6164
raise ImportError("cftime is required for dates with non-standard calendars")
6265
else:
6366
calendars = {
@@ -99,7 +102,8 @@ def __add__(self, other):
99102
return self.__apply__(other)
100103

101104
def __sub__(self, other):
102-
import cftime
105+
if cftime is None:
106+
raise ModuleNotFoundError("No module named 'cftime'")
103107

104108
if isinstance(other, cftime.datetime):
105109
raise TypeError("Cannot subtract a cftime.datetime from a time offset.")
@@ -221,7 +225,8 @@ def _adjust_n_years(other, n, month, reference_day):
221225

222226
def _shift_month(date, months, day_option="start"):
223227
"""Shift the date to a month start or end a given number of months away."""
224-
import cftime
228+
if cftime is None:
229+
raise ModuleNotFoundError("No module named 'cftime'")
225230

226231
delta_year = (date.month + months) // 12
227232
month = (date.month + months) % 12
@@ -378,7 +383,8 @@ def onOffset(self, date):
378383
return mod_month == 0 and date.day == self._get_offset_day(date)
379384

380385
def __sub__(self, other):
381-
import cftime
386+
if cftime is None:
387+
raise ModuleNotFoundError("No module named 'cftime'")
382388

383389
if isinstance(other, cftime.datetime):
384390
raise TypeError("Cannot subtract cftime.datetime from offset.")
@@ -463,7 +469,8 @@ def __apply__(self, other):
463469
return _shift_month(other, months, self._day_option)
464470

465471
def __sub__(self, other):
466-
import cftime
472+
if cftime is None:
473+
raise ModuleNotFoundError("No module named 'cftime'")
467474

468475
if isinstance(other, cftime.datetime):
469476
raise TypeError("Cannot subtract cftime.datetime from offset.")
@@ -688,7 +695,8 @@ def to_offset(freq):
688695

689696

690697
def to_cftime_datetime(date_str_or_date, calendar=None):
691-
import cftime
698+
if cftime is None:
699+
raise ModuleNotFoundError("No module named 'cftime'")
692700

693701
if isinstance(date_str_or_date, str):
694702
if calendar is None:
@@ -724,7 +732,8 @@ def _maybe_normalize_date(date, normalize):
724732
def _generate_linear_range(start, end, periods):
725733
"""Generate an equally-spaced sequence of cftime.datetime objects between
726734
and including two dates (whose length equals the number of periods)."""
727-
import cftime
735+
if cftime is None:
736+
raise ModuleNotFoundError("No module named 'cftime'")
728737

729738
total_seconds = (end - start).total_seconds()
730739
values = np.linspace(0.0, total_seconds, periods, endpoint=True)

xarray/coding/cftimeindex.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@
5454
from ..core.options import OPTIONS
5555
from .times import _STANDARD_CALENDARS, cftime_to_nptime, infer_calendar_name
5656

57+
try:
58+
import cftime
59+
except ImportError:
60+
cftime = None
61+
62+
5763
# constants for cftimeindex.repr
5864
CFTIME_REPR_LENGTH = 19
5965
ITEMS_IN_REPR_MAX_ELSE_ELLIPSIS = 100
@@ -114,7 +120,8 @@ def parse_iso8601_like(datetime_string):
114120

115121

116122
def _parse_iso8601_with_reso(date_type, timestr):
117-
import cftime
123+
if cftime is None:
124+
raise ModuleNotFoundError("No module named 'cftime'")
118125

119126
default = date_type(1, 1, 1)
120127
result = parse_iso8601_like(timestr)
@@ -189,7 +196,8 @@ def _field_accessor(name, docstring=None, min_cftime_version="0.0"):
189196
"""Adapted from pandas.tseries.index._field_accessor"""
190197

191198
def f(self, min_cftime_version=min_cftime_version):
192-
import cftime
199+
if cftime is None:
200+
raise ModuleNotFoundError("No module named 'cftime'")
193201

194202
version = cftime.__version__
195203

@@ -215,7 +223,8 @@ def get_date_type(self):
215223

216224

217225
def assert_all_valid_date_type(data):
218-
import cftime
226+
if cftime is None:
227+
raise ModuleNotFoundError("No module named 'cftime'")
219228

220229
if len(data) > 0:
221230
sample = data[0]

xarray/coding/times.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
unpack_for_encoding,
2323
)
2424

25+
try:
26+
import cftime
27+
except ImportError:
28+
cftime = None
29+
2530
# standard calendars recognized by cftime
2631
_STANDARD_CALENDARS = {"standard", "gregorian", "proleptic_gregorian"}
2732

@@ -164,8 +169,8 @@ def _decode_cf_datetime_dtype(data, units, calendar, use_cftime):
164169

165170

166171
def _decode_datetime_with_cftime(num_dates, units, calendar):
167-
import cftime
168-
172+
if cftime is None:
173+
raise ModuleNotFoundError("No module named 'cftime'")
169174
return np.asarray(
170175
cftime.num2date(num_dates, units, calendar, only_use_cftime_datetimes=True)
171176
)
@@ -414,7 +419,8 @@ def _encode_datetime_with_cftime(dates, units, calendar):
414419
This method is more flexible than xarray's parsing using datetime64[ns]
415420
arrays but also slower because it loops over each element.
416421
"""
417-
import cftime
422+
if cftime is None:
423+
raise ModuleNotFoundError("No module named 'cftime'")
418424

419425
if np.issubdtype(dates.dtype, np.datetime64):
420426
# numpy's broken datetime conversion only works for us precision

xarray/core/common.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
from .rolling_exp import RollingExp
3232
from .utils import Frozen, either_dict_or_kwargs, is_scalar
3333

34+
try:
35+
import cftime
36+
except ImportError:
37+
cftime = None
38+
3439
# Used as a sentinel value to indicate a all dimensions
3540
ALL_DIMS = ...
3641

@@ -1820,9 +1825,7 @@ def is_np_timedelta_like(dtype: DTypeLike) -> bool:
18201825

18211826
def _contains_cftime_datetimes(array) -> bool:
18221827
"""Check if an array contains cftime.datetime objects"""
1823-
try:
1824-
from cftime import datetime as cftime_datetime
1825-
except ImportError:
1828+
if cftime is None:
18261829
return False
18271830
else:
18281831
if array.dtype == np.dtype("O") and array.size > 0:
@@ -1831,7 +1834,7 @@ def _contains_cftime_datetimes(array) -> bool:
18311834
sample = sample.compute()
18321835
if isinstance(sample, np.ndarray):
18331836
sample = sample.item()
1834-
return isinstance(sample, cftime_datetime)
1837+
return isinstance(sample, cftime.datetime)
18351838
else:
18361839
return False
18371840

xarray/plot/utils.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
except ImportError:
2020
nc_time_axis_available = False
2121

22+
23+
try:
24+
import cftime
25+
except ImportError:
26+
cftime = None
27+
2228
ROBUST_PERCENTILE = 2.0
2329

2430

@@ -628,13 +634,11 @@ def _ensure_plottable(*args):
628634
np.str_,
629635
]
630636
other_types = [datetime]
631-
try:
632-
import cftime
633-
634-
cftime_datetime = [cftime.datetime]
635-
except ImportError:
636-
cftime_datetime = []
637-
other_types = other_types + cftime_datetime
637+
if cftime is not None:
638+
cftime_datetime_types = [cftime.datetime]
639+
other_types = other_types + cftime_datetime_types
640+
else:
641+
cftime_datetime_types = []
638642
for x in args:
639643
if not (
640644
_valid_numpy_subdtype(np.array(x), numpy_types)
@@ -647,7 +651,7 @@ def _ensure_plottable(*args):
647651
f"pandas.Interval. Received data of type {np.array(x).dtype} instead."
648652
)
649653
if (
650-
_valid_other_type(np.array(x), cftime_datetime)
654+
_valid_other_type(np.array(x), cftime_datetime_types)
651655
and not nc_time_axis_available
652656
):
653657
raise ImportError(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy