Skip to content

Commit 2fd3b8b

Browse files
committed
Merge remote-tracking branch 'origin/main' into backend-indexing
* origin/main: call `np.cross` with 3D vectors only (#8993) Mark `test_use_cftime_false_standard_calendar_in_range` as an expected failure (#8996) Migration of datatree/ops.py -> datatree_ops.py (#8976) avoid a couple of warnings in `polyfit` (#8939)
2 parents 245c3db + aaa778c commit 2fd3b8b

13 files changed

+175
-43
lines changed

doc/whats-new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ Internal Changes
7373
``xarray/testing/assertions`` for ``DataTree``. (:pull:`8967`)
7474
By `Owen Littlejohns <https://github.com/owenlittlejohns>`_ and
7575
`Tom Nicholas <https://github.com/TomNicholas>`_.
76+
- Migrates ``ops.py`` functionality into ``xarray/core/datatree_ops.py`` (:pull:`8976`)
77+
By `Matt Savoie <https://github.com/flamingbear>`_ and `Tom Nicholas <https://github.com/TomNicholas>`_.
7678
- ``transpose``, ``set_dims``, ``stack`` & ``unstack`` now use a ``dim`` kwarg
7779
rather than ``dims`` or ``dimensions``. This is the final change to make xarray methods
7880
consistent with their use of ``dim``. Using the existing kwarg will raise a

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ filterwarnings = [
330330
"default:Using a non-tuple sequence for multidimensional indexing is deprecated:FutureWarning",
331331
"default:Duplicate dimension names present:UserWarning:xarray.namedarray.core",
332332
"default:::xarray.tests.test_strategies",
333+
# TODO: remove once we know how to deal with a changed signature in protocols
334+
"ignore:__array__ implementation doesn't accept a copy keyword, so passing copy=False failed.",
333335
]
334336

335337
log_cli_level = "INFO"

xarray/core/dataarray.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5269,7 +5269,7 @@ def differentiate(
52695269
edge_order: Literal[1, 2] = 1,
52705270
datetime_unit: DatetimeUnitOptions = None,
52715271
) -> Self:
5272-
""" Differentiate the array with the second order accurate central
5272+
"""Differentiate the array with the second order accurate central
52735273
differences.
52745274
52755275
.. note::

xarray/core/dataset.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,7 +1524,7 @@ def __iter__(self) -> Iterator[Hashable]:
15241524

15251525
else:
15261526

1527-
def __array__(self, dtype=None):
1527+
def __array__(self, dtype=None, copy=None):
15281528
raise TypeError(
15291529
"cannot directly convert an xarray.Dataset into a "
15301530
"numpy array. Instead, create an xarray.DataArray "
@@ -8354,7 +8354,7 @@ def differentiate(
83548354
edge_order: Literal[1, 2] = 1,
83558355
datetime_unit: DatetimeUnitOptions | None = None,
83568356
) -> Self:
8357-
""" Differentiate with the second order accurate central
8357+
"""Differentiate with the second order accurate central
83588358
differences.
83598359
83608360
.. note::
@@ -8937,9 +8937,7 @@ def polyfit(
89378937
lhs = np.vander(x, order)
89388938

89398939
if rcond is None:
8940-
rcond = (
8941-
x.shape[0] * np.core.finfo(x.dtype).eps # type: ignore[attr-defined]
8942-
)
8940+
rcond = x.shape[0] * np.finfo(x.dtype).eps
89438941

89448942
# Weights:
89458943
if w is not None:

xarray/core/datatree.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
check_isomorphic,
2424
map_over_subtree,
2525
)
26+
from xarray.core.datatree_ops import (
27+
DataTreeArithmeticMixin,
28+
MappedDatasetMethodsMixin,
29+
MappedDataWithCoords,
30+
)
2631
from xarray.core.datatree_render import RenderDataTree
2732
from xarray.core.formatting import datatree_repr
2833
from xarray.core.formatting_html import (
@@ -42,11 +47,6 @@
4247
)
4348
from xarray.core.variable import Variable
4449
from xarray.datatree_.datatree.common import TreeAttrAccessMixin
45-
from xarray.datatree_.datatree.ops import (
46-
DataTreeArithmeticMixin,
47-
MappedDatasetMethodsMixin,
48-
MappedDataWithCoords,
49-
)
5050

5151
try:
5252
from xarray.core.variable import calculate_dimensions
@@ -624,7 +624,7 @@ def __bool__(self) -> bool:
624624
def __iter__(self) -> Iterator[Hashable]:
625625
return itertools.chain(self.ds.data_vars, self.children)
626626

627-
def __array__(self, dtype=None):
627+
def __array__(self, dtype=None, copy=None):
628628
raise TypeError(
629629
"cannot directly convert a DataTree into a "
630630
"numpy array. Instead, create an xarray.DataArray "

xarray/core/datatree_mapping.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ def map_over_subtree(func: Callable) -> Callable:
9898
Function will not be applied to any nodes without datasets.
9999
*args : tuple, optional
100100
Positional arguments passed on to `func`. If DataTrees any data-containing nodes will be converted to Datasets
101-
via .ds .
101+
via `.ds`.
102102
**kwargs : Any
103103
Keyword arguments passed on to `func`. If DataTrees any data-containing nodes will be converted to Datasets
104-
via .ds .
104+
via `.ds`.
105105
106106
Returns
107107
-------

xarray/datatree_/datatree/ops.py renamed to xarray/core/datatree_ops.py

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from __future__ import annotations
2+
3+
import re
14
import textwrap
25

36
from xarray.core.dataset import Dataset
4-
57
from xarray.core.datatree_mapping import map_over_subtree
68

79
"""
@@ -12,11 +14,10 @@
1214
"""
1315

1416

15-
_MAPPED_DOCSTRING_ADDENDUM = textwrap.fill(
17+
_MAPPED_DOCSTRING_ADDENDUM = (
1618
"This method was copied from xarray.Dataset, but has been altered to "
1719
"call the method on the Datasets stored in every node of the subtree. "
18-
"See the `map_over_subtree` function for more details.",
19-
width=117,
20+
"See the `map_over_subtree` function for more details."
2021
)
2122

2223
# TODO equals, broadcast_equals etc.
@@ -173,7 +174,7 @@ def _wrap_then_attach_to_cls(
173174
target_cls_dict, source_cls, methods_to_set, wrap_func=None
174175
):
175176
"""
176-
Attach given methods on a class, and optionally wrap each method first. (i.e. with map_over_subtree)
177+
Attach given methods on a class, and optionally wrap each method first. (i.e. with map_over_subtree).
177178
178179
Result is like having written this in the classes' definition:
179180
```
@@ -208,16 +209,62 @@ def method_name(self, *args, **kwargs):
208209
if wrap_func is map_over_subtree:
209210
# Add a paragraph to the method's docstring explaining how it's been mapped
210211
orig_method_docstring = orig_method.__doc__
211-
# if orig_method_docstring is not None:
212-
# if "\n" in orig_method_docstring:
213-
# new_method_docstring = orig_method_docstring.replace(
214-
# "\n", _MAPPED_DOCSTRING_ADDENDUM, 1
215-
# )
216-
# else:
217-
# new_method_docstring = (
218-
# orig_method_docstring + f"\n\n{_MAPPED_DOCSTRING_ADDENDUM}"
219-
# )
220-
setattr(target_cls_dict[method_name], "__doc__", orig_method_docstring)
212+
213+
if orig_method_docstring is not None:
214+
new_method_docstring = insert_doc_addendum(
215+
orig_method_docstring, _MAPPED_DOCSTRING_ADDENDUM
216+
)
217+
setattr(target_cls_dict[method_name], "__doc__", new_method_docstring)
218+
219+
220+
def insert_doc_addendum(docstring: str | None, addendum: str) -> str | None:
221+
"""Insert addendum after first paragraph or at the end of the docstring.
222+
223+
There are a number of Dataset's functions that are wrapped. These come from
224+
Dataset directly as well as the mixins: DataWithCoords, DatasetAggregations, and DatasetOpsMixin.
225+
226+
The majority of the docstrings fall into a parseable pattern. Those that
227+
don't, just have the addendum appeneded after. None values are returned.
228+
229+
"""
230+
if docstring is None:
231+
return None
232+
233+
pattern = re.compile(
234+
r"^(?P<start>(\S+)?(.*?))(?P<paragraph_break>\n\s*\n)(?P<whitespace>[ ]*)(?P<rest>.*)",
235+
re.DOTALL,
236+
)
237+
capture = re.match(pattern, docstring)
238+
if capture is None:
239+
### single line docstring.
240+
return (
241+
docstring
242+
+ "\n\n"
243+
+ textwrap.fill(
244+
addendum,
245+
subsequent_indent=" ",
246+
width=79,
247+
)
248+
)
249+
250+
if len(capture.groups()) == 6:
251+
return (
252+
capture["start"]
253+
+ capture["paragraph_break"]
254+
+ capture["whitespace"]
255+
+ ".. note::\n"
256+
+ textwrap.fill(
257+
addendum,
258+
initial_indent=capture["whitespace"] + " ",
259+
subsequent_indent=capture["whitespace"] + " ",
260+
width=79,
261+
)
262+
+ capture["paragraph_break"]
263+
+ capture["whitespace"]
264+
+ capture["rest"]
265+
)
266+
else:
267+
return docstring
221268

222269

223270
class MappedDatasetMethodsMixin:

xarray/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def _importorskip(
142142
not has_scipy_or_netCDF4, reason="requires scipy or netCDF4"
143143
)
144144
has_numpy_array_api, requires_numpy_array_api = _importorskip("numpy", "1.26.0")
145+
has_numpy_2, requires_numpy_2 = _importorskip("numpy", "2.0.0")
145146

146147

147148
def _importorskip_h5netcdf_ros3():

xarray/tests/test_assertions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def dims(self):
149149
warnings.warn("warning in test")
150150
return super().dims
151151

152-
def __array__(self):
152+
def __array__(self, dtype=None, copy=None):
153153
warnings.warn("warning in test")
154154
return super().__array__()
155155

xarray/tests/test_backends.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
assert_no_warnings,
6464
has_dask,
6565
has_netCDF4,
66+
has_numpy_2,
6667
has_scipy,
6768
mock,
6869
network,
@@ -5088,6 +5089,9 @@ def test_use_cftime_true(calendar, units_year) -> None:
50885089

50895090
@requires_scipy_or_netCDF4
50905091
@pytest.mark.parametrize("calendar", _STANDARD_CALENDARS)
5092+
@pytest.mark.xfail(
5093+
has_numpy_2, reason="https://github.com/pandas-dev/pandas/issues/56996"
5094+
)
50915095
def test_use_cftime_false_standard_calendar_in_range(calendar) -> None:
50925096
x = [0, 1]
50935097
time = [0, 720]

0 commit comments

Comments
 (0)