From 58a843678d085bfa9ecf87c7fbfb23d3542b8854 Mon Sep 17 00:00:00 2001 From: spencerkclark Date: Sat, 10 Dec 2022 18:23:44 -0500 Subject: [PATCH 1/9] Add inclusive argument to cftime_range and date_range; deprecate closed --- xarray/coding/cftime_offsets.py | 82 +++++++++++++++++++++---- xarray/tests/test_cftime_offsets.py | 94 ++++++++++++++++++++++++----- 2 files changed, 148 insertions(+), 28 deletions(-) diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index 801fbbf7052..e2a61fd22a6 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -43,6 +43,7 @@ import re from datetime import datetime, timedelta +from enum import Enum from functools import partial from typing import ClassVar @@ -58,6 +59,7 @@ ) from xarray.core.common import _contains_datetime_like_objects, is_np_datetime_like from xarray.core.pdcompat import count_not_none +from xarray.core.utils import emit_user_level_warning try: import cftime @@ -849,6 +851,47 @@ def _generate_range(start, end, periods, offset): current = next_date +class _NoDefault(Enum): + """Used by pandas to specify a default value for a deprecated argument.""" + + no_default = "NO_DEFAULT" + + def __repr__(self) -> str: + return "" + + +def _translate_closed_to_inclusive(closed): + emit_user_level_warning( + "Following pandas, the `closed` argument is deprecated in " + "favor of the `inclusive` argument, and will be removed in " + "a future version of xarray.", + FutureWarning, + ) + if closed is None: + inclusive = "both" + elif closed in ("left", "right"): + inclusive = closed + else: + raise ValueError( + f"Argument `closed` must be either 'left', 'right', or None. " + f"Got {closed!r}." + ) + return inclusive + + +def _infer_inclusive(closed, inclusive): + if closed is not _NoDefault and inclusive is not None: + raise ValueError( + "Deprecated argument `closed` cannot be passed if " + "argument `inclusive` is not None." + ) + if closed is not _NoDefault: + inclusive = _translate_closed_to_inclusive(closed) + elif inclusive is None: + inclusive = "both" + return inclusive + + def cftime_range( start=None, end=None, @@ -856,7 +899,8 @@ def cftime_range( freq="D", normalize=False, name=None, - closed=None, + closed=_NoDefault, + inclusive=None, calendar="standard", ): """Return a fixed frequency CFTimeIndex. @@ -875,9 +919,11 @@ def cftime_range( Normalize start/end dates to midnight before generating date range. name : str, default: None Name of the resulting index - closed : {"left", "right"} or None, default: None + closed : {"left", "right"} or None, default: _NoDefault Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). + inclusive : {None, "both", "neither", "left", "right"}, default None + Include boundaries; Whether to set each bound as closed or open. calendar : str, default: "standard" Calendar type for the datetimes. @@ -1047,18 +1093,25 @@ def cftime_range( offset = to_offset(freq) dates = np.array(list(_generate_range(start, end, periods, offset))) - left_closed = False - right_closed = False + inclusive = _infer_inclusive(closed, inclusive) - if closed is None: + if inclusive == "neither": + left_closed = False + right_closed = False + elif inclusive == "left": left_closed = True + right_closed = False + elif inclusive == "right": + left_closed = False right_closed = True - elif closed == "left": + elif inclusive == "both": left_closed = True - elif closed == "right": right_closed = True else: - raise ValueError("Closed must be either 'left', 'right' or None") + raise ValueError( + f"Argument `inclusive` must be either 'both', 'neither', " + f"'left', 'right', or None. Got {inclusive}." + ) if not left_closed and len(dates) and start is not None and dates[0] == start: dates = dates[1:] @@ -1076,7 +1129,8 @@ def date_range( tz=None, normalize=False, name=None, - closed=None, + closed=_NoDefault, + inclusive=None, calendar="standard", use_cftime=None, ): @@ -1103,9 +1157,11 @@ def date_range( Normalize start/end dates to midnight before generating date range. name : str, default: None Name of the resulting index - closed : {"left", "right"} or None, default: None + closed : {"left", "right"} or None, default: _NoDefault Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). + inclusive : {None, "both", "neither", "left", "right"}, default None + Include boundaries; Whether to set each bound as closed or open. calendar : str, default: "standard" Calendar type for the datetimes. use_cftime : boolean, optional @@ -1129,6 +1185,8 @@ def date_range( if tz is not None: use_cftime = False + inclusive = _infer_inclusive(closed, inclusive) + if _is_standard_calendar(calendar) and use_cftime is not True: try: return pd.date_range( @@ -1139,7 +1197,7 @@ def date_range( tz=tz, normalize=normalize, name=name, - closed=closed, + inclusive=inclusive, ) except pd.errors.OutOfBoundsDatetime as err: if use_cftime is False: @@ -1158,7 +1216,7 @@ def date_range( freq=freq, normalize=normalize, name=name, - closed=closed, + inclusive=inclusive, calendar=calendar, ) diff --git a/xarray/tests/test_cftime_offsets.py b/xarray/tests/test_cftime_offsets.py index c981015521b..6a322f09da6 100644 --- a/xarray/tests/test_cftime_offsets.py +++ b/xarray/tests/test_cftime_offsets.py @@ -33,7 +33,7 @@ ) from xarray.coding.frequencies import infer_freq from xarray.core.dataarray import DataArray -from xarray.tests import _CFTIME_CALENDARS, requires_cftime +from xarray.tests import _CFTIME_CALENDARS, has_cftime, requires_cftime cftime = pytest.importorskip("cftime") @@ -1022,7 +1022,25 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg "0001-01-04", None, "D", + "neither", + False, + [(1, 1, 2), (1, 1, 3)], + ), + ( + "0001-01-01", + "0001-01-04", + None, + "D", + None, + False, + [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)], + ), + ( + "0001-01-01", + "0001-01-04", None, + "D", + "both", False, [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)], ), @@ -1049,7 +1067,7 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg "0001-01-04", None, "D", - None, + "both", False, [(1, 1, 1, 1), (1, 1, 2, 1), (1, 1, 3, 1)], ), @@ -1058,7 +1076,7 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg "0001-01-04", None, "D", - None, + "both", False, [(1, 1, 1, 1), (1, 1, 2, 1), (1, 1, 3, 1)], ), @@ -1067,7 +1085,7 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg "0001-01-04", None, "D", - None, + "both", True, [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)], ), @@ -1076,7 +1094,7 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg None, 4, "D", - None, + "both", False, [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)], ), @@ -1085,7 +1103,7 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg "0001-01-04", 4, "D", - None, + "both", False, [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)], ), @@ -1094,7 +1112,7 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg "0001-01-04", None, "D", - None, + "both", False, [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)], ), @@ -1103,7 +1121,7 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg (1, 1, 4), None, "D", - None, + "both", False, [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)], ), @@ -1112,17 +1130,17 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg "0011-02-01", None, "3AS-JUN", - None, + "both", False, [(1, 6, 1), (4, 6, 1), (7, 6, 1), (10, 6, 1)], ), - ("0001-01-04", "0001-01-01", None, "D", None, False, []), + ("0001-01-04", "0001-01-01", None, "D", "both", False, []), ( "0010", None, 4, YearBegin(n=-2), - None, + "both", False, [(10, 1, 1), (8, 1, 1), (6, 1, 1), (4, 1, 1)], ), @@ -1131,7 +1149,7 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg "0001-01-04", 4, None, - None, + "both", False, [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)], ), @@ -1140,7 +1158,7 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg None, 4, "3QS-JUN", - None, + "both", False, [(1, 6, 1), (2, 3, 1), (2, 12, 1), (3, 9, 1)], ), @@ -1148,12 +1166,12 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg @pytest.mark.parametrize( - ("start", "end", "periods", "freq", "closed", "normalize", "expected_date_args"), + ("start", "end", "periods", "freq", "inclusive", "normalize", "expected_date_args"), _CFTIME_RANGE_TESTS, ids=_id_func, ) def test_cftime_range( - start, end, periods, freq, closed, normalize, calendar, expected_date_args + start, end, periods, freq, inclusive, normalize, calendar, expected_date_args ): date_type = get_date_type(calendar) expected_dates = [date_type(*args) for args in expected_date_args] @@ -1168,7 +1186,7 @@ def test_cftime_range( end=end, periods=periods, freq=freq, - closed=closed, + inclusive=inclusive, normalize=normalize, calendar=calendar, ) @@ -1390,3 +1408,47 @@ def as_timedelta_not_implemented_error(): tick = Tick() with pytest.raises(NotImplementedError): tick.as_timedelta() + + +@pytest.mark.parametrize("function", [cftime_range, date_range]) +def test_cftime_or_date_range_closed_and_inclusive_error(function): + if function == cftime_range and not has_cftime: + pytest.skip("requires cftime") + + with pytest.raises(ValueError, match="Deprecated argument `closed`"): + function("2000", periods=3, closed=None, inclusive="right") + + +@pytest.mark.parametrize("function", [cftime_range, date_range]) +def test_cftime_or_date_range_invalid_closed_value(function): + if function == cftime_range and not has_cftime: + pytest.skip("requires cftime") + + with pytest.raises(ValueError, match="Argument `closed` must be"): + function("2000", periods=3, closed="foo") + + +@pytest.mark.parametrize("function", [cftime_range, date_range]) +@pytest.mark.parametrize( + ("closed", "inclusive"), [(None, "both"), ("left", "left"), ("right", "right")] +) +def test_cftime_or_date_range_closed(function, closed, inclusive): + if function == cftime_range and not has_cftime: + pytest.skip("requires cftime") + + with pytest.warns(FutureWarning, match="Following pandas"): + result_closed = function("2000-01-01", "2000-01-04", freq="D", closed=closed) + result_inclusive = function( + "2000-01-01", "2000-01-04", freq="D", inclusive=inclusive + ) + np.testing.assert_equal(result_closed.values, result_inclusive.values) + + +@pytest.mark.parametrize("function", [cftime_range, date_range]) +def test_cftime_or_date_range_inclusive_None(function): + if function == cftime_range and not has_cftime: + pytest.skip("requires cftime") + + result_None = function("2000-01-01", "2000-01-04") + result_both = function("2000-01-01", "2000-01-04", inclusive="both") + np.testing.assert_equal(result_None.values, result_both.values) From 1a5f13e60f05a76dff9a17dbf0e7d042de916178 Mon Sep 17 00:00:00 2001 From: spencerkclark Date: Sun, 22 Jan 2023 16:26:00 -0500 Subject: [PATCH 2/9] Add documentation of deprecation --- doc/whats-new.rst | 5 ++- xarray/coding/cftime_offsets.py | 53 +++++++++++++++++------ xarray/core/types.py | 1 + xarray/tests/test_cftime_offsets.py | 10 ++--- xarray/tests/test_cftimeindex_resample.py | 9 +++- 5 files changed, 58 insertions(+), 20 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 26bd72b0727..cf31faf88fc 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -45,7 +45,10 @@ Breaking changes Deprecations ~~~~~~~~~~~~ - +- Following pandas, the `closed` arguments of :py:func:`cftime_range` and + :py:func:`date_range` are deprecated in favor of the `inclusive` arguments, + and will be removed in a future version of xarray (:issue:`6985`:, + :pull:`7373`). By `Spencer Clark `_. Bug fixes ~~~~~~~~~ diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index e2a61fd22a6..282ae3fdcfb 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -45,7 +45,7 @@ from datetime import datetime, timedelta from enum import Enum from functools import partial -from typing import ClassVar +from typing import TYPE_CHECKING, ClassVar, Literal import numpy as np import pandas as pd @@ -67,6 +67,10 @@ cftime = None +if TYPE_CHECKING: + from xarray.core.types import InclusiveOptions, SideOptions + + def get_date_type(calendar, use_cftime=True): """Return the cftime date type for a given calendar name.""" if cftime is None: @@ -852,7 +856,9 @@ def _generate_range(start, end, periods, offset): class _NoDefault(Enum): - """Used by pandas to specify a default value for a deprecated argument.""" + """Used by pandas to specify a default value for a deprecated argument. + Copied from pandas._libs.lib._NoDefault. + """ no_default = "NO_DEFAULT" @@ -860,7 +866,14 @@ def __repr__(self) -> str: return "" +no_default = ( + _NoDefault.no_default +) # Sentinel indicating the default value following pandas +NoDefault = Literal[_NoDefault.no_default] # For typing following pandas + + def _translate_closed_to_inclusive(closed): + """Follows code added in pandas #43504.""" emit_user_level_warning( "Following pandas, the `closed` argument is deprecated in " "favor of the `inclusive` argument, and will be removed in " @@ -880,12 +893,13 @@ def _translate_closed_to_inclusive(closed): def _infer_inclusive(closed, inclusive): - if closed is not _NoDefault and inclusive is not None: + """Follows code added in pandas #43504.""" + if closed is not no_default and inclusive is not None: raise ValueError( - "Deprecated argument `closed` cannot be passed if " - "argument `inclusive` is not None." + "Following pandas, deprecated argument `closed` cannot be " + "passed if argument `inclusive` is not None." ) - if closed is not _NoDefault: + if closed is not no_default: inclusive = _translate_closed_to_inclusive(closed) elif inclusive is None: inclusive = "both" @@ -899,8 +913,8 @@ def cftime_range( freq="D", normalize=False, name=None, - closed=_NoDefault, - inclusive=None, + closed: NoDefault | SideOptions = no_default, + inclusive: None | InclusiveOptions = None, calendar="standard", ): """Return a fixed frequency CFTimeIndex. @@ -922,8 +936,15 @@ def cftime_range( closed : {"left", "right"} or None, default: _NoDefault Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). + + .. deprecated:: 2023.01.1 + Following pandas, the `closed` argument is deprecated in favor + of the `inclusive` argument, and will be removed in a future + version of xarray. inclusive : {None, "both", "neither", "left", "right"}, default None - Include boundaries; Whether to set each bound as closed or open. + Include boundaries; whether to set each bound as closed or open. + + .. versionadded:: 2023.01.1 calendar : str, default: "standard" Calendar type for the datetimes. @@ -938,7 +959,6 @@ def cftime_range( features of ``pandas.date_range`` (e.g. specifying how the index is ``closed`` on either side, or whether or not to ``normalize`` the start and end bounds); however, there are some notable exceptions: - - You cannot specify a ``tz`` (time zone) argument. - Start or end dates specified as partial-datetime strings must use the `ISO-8601 format `_. @@ -1129,8 +1149,8 @@ def date_range( tz=None, normalize=False, name=None, - closed=_NoDefault, - inclusive=None, + closed: NoDefault | SideOptions = no_default, + inclusive: None | InclusiveOptions = None, calendar="standard", use_cftime=None, ): @@ -1160,8 +1180,15 @@ def date_range( closed : {"left", "right"} or None, default: _NoDefault Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). + + .. deprecated:: 2023.01.1 + Following pandas, the `closed` argument is deprecated in favor + of the `inclusive` argument, and will be removed in a future + version of xarray. inclusive : {None, "both", "neither", "left", "right"}, default None - Include boundaries; Whether to set each bound as closed or open. + Include boundaries; whether to set each bound as closed or open. + + .. versionadded:: 2023.01.1 calendar : str, default: "standard" Calendar type for the datetimes. use_cftime : boolean, optional diff --git a/xarray/core/types.py b/xarray/core/types.py index fc3c6712be2..7149443c0c7 100644 --- a/xarray/core/types.py +++ b/xarray/core/types.py @@ -168,6 +168,7 @@ def dtype(self) -> np.dtype: CoarsenBoundaryOptions = Literal["exact", "trim", "pad"] SideOptions = Literal["left", "right"] +InclusiveOptions = Literal["both", "neither", "left", "right"] ScaleOptions = Literal["linear", "symlog", "log", "logit", None] HueStyleOptions = Literal["continuous", "discrete", None] diff --git a/xarray/tests/test_cftime_offsets.py b/xarray/tests/test_cftime_offsets.py index 6a322f09da6..6b628c15488 100644 --- a/xarray/tests/test_cftime_offsets.py +++ b/xarray/tests/test_cftime_offsets.py @@ -1411,16 +1411,16 @@ def as_timedelta_not_implemented_error(): @pytest.mark.parametrize("function", [cftime_range, date_range]) -def test_cftime_or_date_range_closed_and_inclusive_error(function): +def test_cftime_or_date_range_closed_and_inclusive_error(function) -> None: if function == cftime_range and not has_cftime: pytest.skip("requires cftime") - with pytest.raises(ValueError, match="Deprecated argument `closed`"): + with pytest.raises(ValueError, match="Following pandas, deprecated"): function("2000", periods=3, closed=None, inclusive="right") @pytest.mark.parametrize("function", [cftime_range, date_range]) -def test_cftime_or_date_range_invalid_closed_value(function): +def test_cftime_or_date_range_invalid_closed_value(function) -> None: if function == cftime_range and not has_cftime: pytest.skip("requires cftime") @@ -1432,7 +1432,7 @@ def test_cftime_or_date_range_invalid_closed_value(function): @pytest.mark.parametrize( ("closed", "inclusive"), [(None, "both"), ("left", "left"), ("right", "right")] ) -def test_cftime_or_date_range_closed(function, closed, inclusive): +def test_cftime_or_date_range_closed(function, closed, inclusive) -> None: if function == cftime_range and not has_cftime: pytest.skip("requires cftime") @@ -1445,7 +1445,7 @@ def test_cftime_or_date_range_closed(function, closed, inclusive): @pytest.mark.parametrize("function", [cftime_range, date_range]) -def test_cftime_or_date_range_inclusive_None(function): +def test_cftime_or_date_range_inclusive_None(function) -> None: if function == cftime_range and not has_cftime: pytest.skip("requires cftime") diff --git a/xarray/tests/test_cftimeindex_resample.py b/xarray/tests/test_cftimeindex_resample.py index e780421e09e..5f818b7663d 100644 --- a/xarray/tests/test_cftimeindex_resample.py +++ b/xarray/tests/test_cftimeindex_resample.py @@ -1,6 +1,7 @@ from __future__ import annotations import datetime +from typing import TypedDict import numpy as np import pandas as pd @@ -189,6 +190,12 @@ def test_calendars(calendar) -> None: xr.testing.assert_identical(da_cftime, da_datetime) +class DateRangeKwargs(TypedDict): + start: str + periods: int + freq: str + + @pytest.mark.parametrize("closed", ["left", "right"]) @pytest.mark.parametrize( "origin", @@ -198,7 +205,7 @@ def test_calendars(calendar) -> None: def test_origin(closed, origin) -> None: initial_freq, resample_freq = ("3H", "9H") start = "1969-12-31T12:07:01" - index_kwargs = dict(start=start, periods=12, freq=initial_freq) + index_kwargs: DateRangeKwargs = dict(start=start, periods=12, freq=initial_freq) datetime_index = pd.date_range(**index_kwargs) cftime_index = xr.cftime_range(**index_kwargs) da_datetimeindex = da(datetime_index) From 139485adf1a384dddeb024728cc95821415afc0a Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Wed, 25 Jan 2023 10:59:44 -0700 Subject: [PATCH 3/9] Update xarray/coding/cftime_offsets.py Co-authored-by: Justus Magin --- xarray/coding/cftime_offsets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index 282ae3fdcfb..47f048a8939 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -938,8 +938,9 @@ def cftime_range( "left", "right", or both sides (None). .. deprecated:: 2023.01.1 - Following pandas, the `closed` argument is deprecated in favor - of the `inclusive` argument, and will be removed in a future + + Following pandas, the ``closed`` argument is deprecated in favor + of the ``inclusive`` argument, and will be removed in a future version of xarray. inclusive : {None, "both", "neither", "left", "right"}, default None Include boundaries; whether to set each bound as closed or open. From 1420ec8bbfb5a451e76b455fe4c313b207ebf172 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Wed, 25 Jan 2023 11:56:00 -0700 Subject: [PATCH 4/9] [skip-ci] [test-upstream] Update xarray/coding/cftime_offsets.py --- xarray/coding/cftime_offsets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index 47f048a8939..cf2ee558aaa 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -942,10 +942,12 @@ def cftime_range( Following pandas, the ``closed`` argument is deprecated in favor of the ``inclusive`` argument, and will be removed in a future version of xarray. + inclusive : {None, "both", "neither", "left", "right"}, default None Include boundaries; whether to set each bound as closed or open. .. versionadded:: 2023.01.1 + calendar : str, default: "standard" Calendar type for the datetimes. From 0c42f7c8af0437b4490c62a58f7150613461fa78 Mon Sep 17 00:00:00 2001 From: spencerkclark Date: Sun, 29 Jan 2023 07:09:46 -0500 Subject: [PATCH 5/9] [test-upstream] Use parameter instead of argument when describing function signature --- doc/whats-new.rst | 4 ++-- xarray/coding/cftime_offsets.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index cf31faf88fc..63ac491e61e 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -45,8 +45,8 @@ Breaking changes Deprecations ~~~~~~~~~~~~ -- Following pandas, the `closed` arguments of :py:func:`cftime_range` and - :py:func:`date_range` are deprecated in favor of the `inclusive` arguments, +- Following pandas, the `closed` parameters of :py:func:`cftime_range` and + :py:func:`date_range` are deprecated in favor of the `inclusive` parameters, and will be removed in a future version of xarray (:issue:`6985`:, :pull:`7373`). By `Spencer Clark `_. diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index cf2ee558aaa..433b21e5d6d 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -875,8 +875,8 @@ def __repr__(self) -> str: def _translate_closed_to_inclusive(closed): """Follows code added in pandas #43504.""" emit_user_level_warning( - "Following pandas, the `closed` argument is deprecated in " - "favor of the `inclusive` argument, and will be removed in " + "Following pandas, the `closed` parameter is deprecated in " + "favor of the `inclusive` parameter, and will be removed in " "a future version of xarray.", FutureWarning, ) @@ -933,14 +933,14 @@ def cftime_range( Normalize start/end dates to midnight before generating date range. name : str, default: None Name of the resulting index - closed : {"left", "right"} or None, default: _NoDefault + closed : {"left", "right"} or None, default: "NO_DEFAULT" Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). .. deprecated:: 2023.01.1 - Following pandas, the ``closed`` argument is deprecated in favor - of the ``inclusive`` argument, and will be removed in a future + Following pandas, the ``closed`` parameter is deprecated in favor + of the ``inclusive`` parameter, and will be removed in a future version of xarray. inclusive : {None, "both", "neither", "left", "right"}, default None @@ -1180,13 +1180,13 @@ def date_range( Normalize start/end dates to midnight before generating date range. name : str, default: None Name of the resulting index - closed : {"left", "right"} or None, default: _NoDefault + closed : {"left", "right"} or None, default: "NO_DEFAULT" Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). .. deprecated:: 2023.01.1 - Following pandas, the `closed` argument is deprecated in favor - of the `inclusive` argument, and will be removed in a future + Following pandas, the `closed` parameter is deprecated in favor + of the `inclusive` parameter, and will be removed in a future version of xarray. inclusive : {None, "both", "neither", "left", "right"}, default None Include boundaries; whether to set each bound as closed or open. From 15f4034c47fa1c88ea757341135187cef161b4da Mon Sep 17 00:00:00 2001 From: spencerkclark Date: Sun, 29 Jan 2023 07:35:58 -0500 Subject: [PATCH 6/9] [test-upstream] Add references to pandas PRs for _NoDefault --- xarray/coding/cftime_offsets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index 433b21e5d6d..f5304d10053 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -858,6 +858,12 @@ def _generate_range(start, end, periods, offset): class _NoDefault(Enum): """Used by pandas to specify a default value for a deprecated argument. Copied from pandas._libs.lib._NoDefault. + + See also: + - pandas-dev/pandas#30788 + - pandas-dev/pandas#40684 + - pandas-dev/pandas#40715 + - pandas-dev/pandas#47045 """ no_default = "NO_DEFAULT" From 1f0dc880cb6c856f888630990b5443c4b876d372 Mon Sep 17 00:00:00 2001 From: spencerkclark Date: Sat, 4 Feb 2023 15:43:54 -0500 Subject: [PATCH 7/9] [test-upstream] Move _NoDefault to pdcompat; fix doc build --- xarray/coding/cftime_offsets.py | 38 +++++++-------------------------- xarray/core/pdcompat.py | 26 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index f5304d10053..b0442ddbd7c 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -43,9 +43,8 @@ import re from datetime import datetime, timedelta -from enum import Enum from functools import partial -from typing import TYPE_CHECKING, ClassVar, Literal +from typing import TYPE_CHECKING, ClassVar import numpy as np import pandas as pd @@ -58,7 +57,7 @@ format_cftime_datetime, ) from xarray.core.common import _contains_datetime_like_objects, is_np_datetime_like -from xarray.core.pdcompat import count_not_none +from xarray.core.pdcompat import NoDefault, count_not_none, no_default from xarray.core.utils import emit_user_level_warning try: @@ -855,29 +854,6 @@ def _generate_range(start, end, periods, offset): current = next_date -class _NoDefault(Enum): - """Used by pandas to specify a default value for a deprecated argument. - Copied from pandas._libs.lib._NoDefault. - - See also: - - pandas-dev/pandas#30788 - - pandas-dev/pandas#40684 - - pandas-dev/pandas#40715 - - pandas-dev/pandas#47045 - """ - - no_default = "NO_DEFAULT" - - def __repr__(self) -> str: - return "" - - -no_default = ( - _NoDefault.no_default -) # Sentinel indicating the default value following pandas -NoDefault = Literal[_NoDefault.no_default] # For typing following pandas - - def _translate_closed_to_inclusive(closed): """Follows code added in pandas #43504.""" emit_user_level_warning( @@ -939,12 +915,11 @@ def cftime_range( Normalize start/end dates to midnight before generating date range. name : str, default: None Name of the resulting index - closed : {"left", "right"} or None, default: "NO_DEFAULT" + closed : {None, "left", "right"}, default: "NO_DEFAULT" Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). .. deprecated:: 2023.01.1 - Following pandas, the ``closed`` parameter is deprecated in favor of the ``inclusive`` parameter, and will be removed in a future version of xarray. @@ -968,6 +943,7 @@ def cftime_range( features of ``pandas.date_range`` (e.g. specifying how the index is ``closed`` on either side, or whether or not to ``normalize`` the start and end bounds); however, there are some notable exceptions: + - You cannot specify a ``tz`` (time zone) argument. - Start or end dates specified as partial-datetime strings must use the `ISO-8601 format `_. @@ -1186,7 +1162,7 @@ def date_range( Normalize start/end dates to midnight before generating date range. name : str, default: None Name of the resulting index - closed : {"left", "right"} or None, default: "NO_DEFAULT" + closed : {None, "left", "right"}, default: "NO_DEFAULT" Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). @@ -1194,10 +1170,12 @@ def date_range( Following pandas, the `closed` parameter is deprecated in favor of the `inclusive` parameter, and will be removed in a future version of xarray. - inclusive : {None, "both", "neither", "left", "right"}, default None + + inclusive : {None, "both", "neither", "left", "right"}, default: None Include boundaries; whether to set each bound as closed or open. .. versionadded:: 2023.01.1 + calendar : str, default: "standard" Calendar type for the datetimes. use_cftime : boolean, optional diff --git a/xarray/core/pdcompat.py b/xarray/core/pdcompat.py index 7a4b3c405ae..018bb19b871 100644 --- a/xarray/core/pdcompat.py +++ b/xarray/core/pdcompat.py @@ -35,6 +35,9 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations +from enum import Enum +from typing import Literal + def count_not_none(*args) -> int: """Compute the number of non-None arguments. @@ -42,3 +45,26 @@ def count_not_none(*args) -> int: Copied from pandas.core.common.count_not_none (not part of the public API) """ return sum(arg is not None for arg in args) + + +class _NoDefault(Enum): + """Used by pandas to specify a default value for a deprecated argument. + Copied from pandas._libs.lib._NoDefault. + + See also: + - pandas-dev/pandas#30788 + - pandas-dev/pandas#40684 + - pandas-dev/pandas#40715 + - pandas-dev/pandas#47045 + """ + + no_default = "NO_DEFAULT" + + def __repr__(self) -> str: + return "" + + +no_default = ( + _NoDefault.no_default +) # Sentinel indicating the default value following pandas +NoDefault = Literal[_NoDefault.no_default] # For typing following pandas From 2505546f6292c0b3aae32257f0265f0d3750ee4c Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 6 Feb 2023 10:15:35 -0700 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Spencer Clark --- xarray/coding/cftime_offsets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index b0442ddbd7c..906bb888383 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -919,7 +919,7 @@ def cftime_range( Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). - .. deprecated:: 2023.01.1 + .. deprecated:: 2023.02.0 Following pandas, the ``closed`` parameter is deprecated in favor of the ``inclusive`` parameter, and will be removed in a future version of xarray. @@ -927,7 +927,7 @@ def cftime_range( inclusive : {None, "both", "neither", "left", "right"}, default None Include boundaries; whether to set each bound as closed or open. - .. versionadded:: 2023.01.1 + .. versionadded:: 2023.02.0 calendar : str, default: "standard" Calendar type for the datetimes. @@ -1174,7 +1174,7 @@ def date_range( inclusive : {None, "both", "neither", "left", "right"}, default: None Include boundaries; whether to set each bound as closed or open. - .. versionadded:: 2023.01.1 + .. versionadded:: 2023.02.0 calendar : str, default: "standard" Calendar type for the datetimes. From 3b1cba02fa383c9a40278edcd9b9d3e904225fea Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 6 Feb 2023 10:15:46 -0700 Subject: [PATCH 9/9] Update xarray/coding/cftime_offsets.py Co-authored-by: Spencer Clark --- xarray/coding/cftime_offsets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index 906bb888383..bc3e3545892 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -1166,7 +1166,7 @@ def date_range( Make the interval closed with respect to the given frequency to the "left", "right", or both sides (None). - .. deprecated:: 2023.01.1 + .. deprecated:: 2023.02.0 Following pandas, the `closed` parameter is deprecated in favor of the `inclusive` parameter, and will be removed in a future version of xarray.