Skip to content

Commit b576b0d

Browse files
committed
Merge branch 'main' into fix-coord-transform-indexing
* main: Document limitations of cftime arithmetic (pydata#10653) Remove RTD htmlzip output format (pydata#10977) Support decoding unsigned integers to timedelta (pydata#10972) Use `._data` in `Variable._replace` (pydata#10969) small changes to the pixi env definitions (pydata#10976) Add GitHub Codespaces config for Pixi (pydata#10929) Fix workflow name to embed `matrix.pytest-addopts` (pydata#10970)
2 parents eae0798 + 50218d0 commit b576b0d

File tree

17 files changed

+327
-167
lines changed

17 files changed

+327
-167
lines changed

.devcontainer/Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM mcr.microsoft.com/devcontainers/base:jammy
2+
3+
ARG PIXI_VERSION=v0.59.0
4+
5+
RUN curl -L -o /usr/local/bin/pixi -fsSL --compressed "https://github.com/prefix-dev/pixi/releases/download/${PIXI_VERSION}/pixi-$(uname -m)-unknown-linux-musl" \
6+
&& chmod +x /usr/local/bin/pixi \
7+
&& pixi info
8+
9+
# set some user and workdir settings to work nicely with vscode
10+
USER vscode
11+
WORKDIR /home/vscode
12+
13+
RUN echo 'eval "$(pixi completion -s bash)"' >> /home/vscode/.bashrc

.devcontainer/devcontainer.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "my-workspace",
3+
"build": {
4+
"dockerfile": "Dockerfile",
5+
"context": ".."
6+
},
7+
"hostRequirements": {
8+
"cpus": 4,
9+
"memory": "16gb"
10+
},
11+
"customizations": {
12+
"vscode": {
13+
"settings": {},
14+
"extensions": ["ms-python.python", "charliermarsh.ruff", "GitHub.copilot"]
15+
}
16+
},
17+
"features": {
18+
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
19+
},
20+
"mounts": [
21+
"source=${localWorkspaceFolderBasename}-pixi,target=${containerWorkspaceFolder}/.pixi,type=volume"
22+
],
23+
"postCreateCommand": "sudo chown vscode .pixi && pixi install"
24+
}

.github/workflows/ci-additional.yaml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
run:
4949
shell: bash -l {0}
5050
env:
51-
PIXI_ENV: test
51+
PIXI_ENV: "test-py313"
5252
steps:
5353
- uses: actions/checkout@v6
5454
with:
@@ -75,7 +75,7 @@ jobs:
7575

7676
- name: Version info
7777
run: |
78-
pixi run -e ${{env.PIXI_ENV}} python xarray/util/print_versions.py
78+
pixi run -e ${{env.PIXI_ENV}} -- python xarray/util/print_versions.py
7979
- name: Run doctests
8080
run: |
8181
# Raise an error if there are warnings in the doctests, with `-Werror`.
@@ -84,7 +84,7 @@ jobs:
8484
#
8585
# If dependencies emit warnings we can't do anything about, add ignores to
8686
# `xarray/tests/__init__.py`.
87-
pixi run -e ${{env.PIXI_ENV}} python -m pytest --doctest-modules xarray --ignore xarray/tests -Werror
87+
pixi run -e ${{env.PIXI_ENV}} -- python -m pytest --doctest-modules xarray --ignore xarray/tests -Werror
8888
8989
mypy:
9090
name: Mypy
@@ -94,7 +94,7 @@ jobs:
9494
run:
9595
shell: bash -l {0}
9696
env:
97-
PIXI_ENV: test-with-typing
97+
PIXI_ENV: test-py313-with-typing
9898

9999
steps:
100100
- uses: actions/checkout@v6
@@ -117,14 +117,14 @@ jobs:
117117
- name: set environment variables
118118
run: |
119119
echo "TODAY=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
120-
echo "PYTHON_VERSION=$(pixi run -e ${{env.PIXI_ENV}} python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
120+
echo "PYTHON_VERSION=$(pixi run -e ${{env.PIXI_ENV}} -- python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
121121
- name: Version info
122122
run: |
123-
pixi run -e ${{env.PIXI_ENV}} python xarray/util/print_versions.py
123+
pixi run -e ${{env.PIXI_ENV}} -- python xarray/util/print_versions.py
124124
125125
- name: Run mypy
126126
run: |
127-
pixi run -e ${{env.PIXI_ENV}} python -m mypy --install-types --non-interactive --cobertura-xml-report mypy_report
127+
pixi run -e ${{env.PIXI_ENV}} -- python -m mypy --install-types --non-interactive --cobertura-xml-report mypy_report
128128
129129
- name: Upload mypy coverage to Codecov
130130
uses: codecov/[email protected]
@@ -166,14 +166,14 @@ jobs:
166166
- name: set environment variables
167167
run: |
168168
echo "TODAY=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
169-
echo "PYTHON_VERSION=$(pixi run -e ${{env.PIXI_ENV}} python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
169+
echo "PYTHON_VERSION=$(pixi run -e ${{env.PIXI_ENV}} -- python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
170170
- name: Version info
171171
run: |
172-
pixi run -e ${{env.PIXI_ENV}} python xarray/util/print_versions.py
172+
pixi run -e ${{env.PIXI_ENV}} -- python xarray/util/print_versions.py
173173
174174
- name: Run mypy
175175
run: |
176-
pixi run -e ${{env.PIXI_ENV}} python -m mypy --install-types --non-interactive --cobertura-xml-report mypy_report
176+
pixi run -e ${{env.PIXI_ENV}} -- python -m mypy --install-types --non-interactive --cobertura-xml-report mypy_report
177177
178178
- name: Upload mypy coverage to Codecov
179179
uses: codecov/[email protected]
@@ -191,7 +191,7 @@ jobs:
191191
strategy:
192192
fail-fast: false
193193
matrix:
194-
pixi-env: ["test-with-typing", "test-py311-with-typing"]
194+
pixi-env: ["test-py313-with-typing", "test-py311-with-typing"]
195195
if: |
196196
always()
197197
&& (
@@ -223,14 +223,14 @@ jobs:
223223
- name: set environment variables
224224
run: |
225225
echo "TODAY=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
226-
echo "PYTHON_VERSION=$(pixi run -e ${{ matrix.pixi-env }} python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
226+
echo "PYTHON_VERSION=$(pixi run -e ${{ matrix.pixi-env }} -- python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
227227
- name: Version info
228228
run: |
229-
pixi run -e ${{ matrix.pixi-env }} python xarray/util/print_versions.py
229+
pixi run -e ${{ matrix.pixi-env }} -- python xarray/util/print_versions.py
230230
231231
- name: Run pyright
232232
run: |
233-
pixi run -e ${{ matrix.pixi-env }} python -m pyright xarray/
233+
pixi run -e ${{ matrix.pixi-env }} -- python -m pyright xarray/
234234
235235
- name: Upload pyright coverage to Codecov
236236
uses: codecov/[email protected]

.github/workflows/ci.yaml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
with:
4141
pixi-version: "v0.58.0" # keep in sync with env var above
4242
test:
43-
name: "${{ matrix.os }} | ${{ matrix.pixi-env }}"
43+
name: "${{ matrix.os }} | ${{ matrix.pixi-env }}${{ matrix.pytest-addopts && format(' ({0})', matrix.pytest-addopts) || '' }}"
4444
runs-on: ${{ matrix.os }}
4545
needs: [detect-ci-trigger, cache-pixi-lock]
4646
if: needs.detect-ci-trigger.outputs.triggered == 'false'
@@ -52,22 +52,22 @@ jobs:
5252
matrix:
5353
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
5454
# Bookend python versions
55-
pixi-env: ["test-py311", "test"]
55+
pixi-env: ["test-py311", "test-py313"]
5656
pytest-addopts: [""]
5757
include:
5858
# Minimum python version:
59-
- pixi-env: "test-bare-minimum"
59+
- pixi-env: "test-py311-bare-minimum"
6060
os: ubuntu-latest
61-
- pixi-env: "test-bare-min-and-scipy"
61+
- pixi-env: "test-py311-bare-min-and-scipy"
6262
os: ubuntu-latest
63-
- pixi-env: "test-min-versions"
63+
- pixi-env: "test-py311-min-versions"
6464
os: ubuntu-latest
6565
# Latest python version:
66-
- pixi-env: "test-no-numba"
66+
- pixi-env: "test-py313-no-numba"
6767
os: ubuntu-latest
68-
- pixi-env: "test-no-dask"
68+
- pixi-env: "test-py313-no-dask"
6969
os: ubuntu-latest
70-
- pixi-env: "test"
70+
- pixi-env: "test-py313"
7171
pytest-addopts: "flaky"
7272
os: ubuntu-latest
7373
# The mypy tests must be executed using only 1 process in order to guarantee
@@ -76,7 +76,7 @@ jobs:
7676
pytest-addopts: "mypy"
7777
numprocesses: 1
7878
os: ubuntu-latest
79-
- pixi-env: "test-with-typing"
79+
- pixi-env: "test-py313-with-typing"
8080
numprocesses: 1
8181
os: ubuntu-latest
8282
steps:
@@ -101,7 +101,7 @@ jobs:
101101
- name: Set environment variables
102102
run: |
103103
echo "TODAY=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
104-
echo "PYTHON_VERSION=$(pixi run -e ${{env.PIXI_ENV}} python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
104+
echo "PYTHON_VERSION=$(pixi run -e ${{ matrix.pixi-env }} -- python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
105105
106106
if [[ "${{ matrix.pytest-addopts }}" != "" ]] ;
107107
then
@@ -128,11 +128,11 @@ jobs:
128128
129129
- name: Version info
130130
run: |
131-
pixi run -e ${{ matrix.pixi-env }} python xarray/util/print_versions.py
131+
pixi run -e ${{ matrix.pixi-env }} -- python xarray/util/print_versions.py
132132
133133
- name: Import xarray
134134
run: |
135-
pixi run -e ${{ matrix.pixi-env }} python -c "import xarray"
135+
pixi run -e ${{ matrix.pixi-env }} -- python -c "import xarray"
136136
137137
- name: Restore cached hypothesis directory
138138
uses: actions/cache@v4
@@ -143,12 +143,12 @@ jobs:
143143
save-always: true
144144

145145
- name: Run tests
146-
run:
147-
pixi run -e ${{ matrix.pixi-env }} python -m pytest -n ${{ matrix.numprocesses || 4 }}
148-
--timeout 180
149-
--cov=xarray
150-
--cov-report=xml
151-
--junitxml=pytest.xml
146+
run: |
147+
pixi run -e ${{ matrix.pixi-env }} -- python -m pytest -n ${{ matrix.numprocesses || 4 }} \
148+
--timeout 180 \
149+
--cov=xarray \
150+
--cov-report=xml \
151+
--junitxml=pytest.xml
152152
153153
- name: Upload test results
154154
if: always()

.github/workflows/hypothesis.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
shell: bash -l {0}
5656

5757
env:
58-
PIXI_ENV: test
58+
PIXI_ENV: "test-py313"
5959

6060
steps:
6161
- uses: actions/checkout@v6

.github/workflows/upstream-dev-ci.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ jobs:
7979
cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }}
8080
- name: Version info
8181
run: |
82-
pixi run -e ${{matrix.pixi-env}} python xarray/util/print_versions.py
82+
pixi run -e ${{matrix.pixi-env}} -- python xarray/util/print_versions.py
8383
- name: Import xarray
8484
run: |
85-
pixi run -e ${{matrix.pixi-env}} python -c 'import xarray'
85+
pixi run -e ${{matrix.pixi-env}} -- python -c 'import xarray'
8686
- name: Run Tests
8787
if: success()
8888
id: status
8989
run: |
90-
pixi run -e ${{matrix.pixi-env}} python -m pytest --timeout=60 -rf -nauto \
90+
pixi run -e ${{matrix.pixi-env}} -- python -m pytest --timeout=60 -rf -nauto \
9191
--report-log output-${{ matrix.pixi-env }}-log.jsonl
9292
- name: Generate and publish the report
9393
if: |
@@ -138,13 +138,13 @@ jobs:
138138
- name: set environment variables
139139
run: |
140140
echo "TODAY=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
141-
echo "PYTHON_VERSION=$(pixi run -e ${{matrix.pixi-env}} python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
141+
echo "PYTHON_VERSION=$(pixi run -e ${{matrix.pixi-env}} -- python --version | cut -d' ' -f2 | cut -d. -f1,2)" >> $GITHUB_ENV
142142
- name: Version info
143143
run: |
144-
pixi run -e ${{matrix.pixi-env}} python xarray/util/print_versions.py
144+
pixi run -e ${{matrix.pixi-env}} -- python xarray/util/print_versions.py
145145
- name: Run mypy
146146
run: |
147-
pixi run -e ${{matrix.pixi-env}} python -m mypy --install-types --non-interactive --cobertura-xml-report mypy_report
147+
pixi run -e ${{matrix.pixi-env}} -- python -m mypy --install-types --non-interactive --cobertura-xml-report mypy_report
148148
- name: Upload mypy coverage to Codecov
149149
uses: codecov/[email protected]
150150
with:

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ repos:
3535
- id: blackdoc
3636
exclude: "generate_aggregations.py"
3737
# make sure this is the most recent version of black
38-
additional_dependencies: ["black==25.9.0"]
38+
additional_dependencies: ["black==25.11.0"]
3939
- repo: https://github.com/rbubley/mirrors-prettier
4040
rev: v3.7.3
4141
hooks:

.readthedocs.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,3 @@ build:
2424
build:
2525
html:
2626
- pixi run doc BUILDDIR=$READTHEDOCS_OUTPUT
27-
28-
formats:
29-
- htmlzip

doc/user-guide/weather-climate.rst

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,90 @@ For data indexed by a :py:class:`~xarray.CFTimeIndex` xarray currently supports:
279279
.. _precision range: https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#timestamp-limitations
280280
.. _ISO 8601 standard: https://en.wikipedia.org/wiki/ISO_8601
281281
.. _partial datetime string indexing: https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#partial-string-indexing
282+
283+
.. _cftime_arithmetic_limitations:
284+
285+
Arithmetic limitations with ``cftime`` objects
286+
----------------------------------------------
287+
288+
A current limitation when working with non-standard calendars and :py:class:`cftime.datetime`
289+
objects is that they support arithmetic with :py:class:`datetime.timedelta`, but **not** with :py:class:`numpy.timedelta64`.
290+
291+
This means that certain xarray operations (such as :py:meth:`~xarray.DataArray.diff`)
292+
may produce ``timedelta64`` results that cannot be directly combined with ``cftime`` coordinates.
293+
294+
For example, let's define a time axis using ``cftime`` objects:
295+
296+
.. jupyter-execute::
297+
298+
import xarray as xr
299+
import numpy as np
300+
import pandas as pd
301+
import cftime
302+
303+
time = xr.DataArray(
304+
xr.date_range("2000", periods=3, freq="MS", use_cftime=True),
305+
dims="time",
306+
)
307+
308+
If you want to compute, e.g., midpoints in the time intervals, this will not work:
309+
310+
.. code-block:: python
311+
312+
# Attempt to compute midpoints
313+
time[:-1] + 0.5 * time.diff("time")
314+
315+
and result in an error like this:
316+
317+
.. code-block:: none
318+
319+
UFuncTypeError: ufunc 'add' cannot use operands with types dtype('O') and dtype('<m8[ns]')
320+
321+
This is because :py:meth:`~xarray.DataArray.diff` returns ``timedelta64``, which is not
322+
compatible with ``cftime.datetime``. These limitations stem from the ``cftime`` library itself;
323+
arithmetic between ``cftime.datetime`` and ``numpy.timedelta64`` is not implemented.
324+
325+
**Workarounds**
326+
327+
One possible workaround is to use :py:meth:`numpy.diff` on the underlying values,
328+
which returns ``datetime.timedelta`` objects that are compatible:
329+
330+
.. jupyter-execute::
331+
332+
time[:-1] + 0.5 * np.diff(time.values)
333+
334+
or you can convert ``timedelta64`` values to Python ``timedelta`` objects explicitly,
335+
for example via :py:meth:`pandas.to_timedelta`:
336+
337+
.. jupyter-execute::
338+
339+
td = pd.to_timedelta(time.diff("time").values).to_pytimedelta()
340+
time[:-1] + 0.5 * td
341+
342+
You could also consider a more verbose, but also more robust, workaround that adds the two
343+
DataArrays via :py:meth:`~xarray.apply_ufunc`:
344+
345+
.. jupyter-execute::
346+
347+
def add_cftime_and_timedelta64(cftime_da, timedelta64_da, join="inner"):
348+
def func(cftime_array, timedelta64_array):
349+
shape = cftime_array.shape
350+
cftime_array = cftime_array.ravel()
351+
timedelta64_array = timedelta64_array.ravel()
352+
timedelta_array = pd.to_timedelta(timedelta64_array).to_pytimedelta()
353+
return (cftime_array + timedelta_array).reshape(shape)
354+
355+
return xr.apply_ufunc(
356+
func,
357+
cftime_da,
358+
timedelta64_da,
359+
dask="parallelized",
360+
output_dtypes=[cftime_da.dtype],
361+
join=join
362+
)
363+
364+
365+
add_cftime_and_timedelta64(time, 0.5 * time.diff("time"))
366+
367+
This function has the advantage that it preserves the coordinate alignment, as well as multi-dimensional
368+
and dask compatibility features of xarray.

doc/whats-new.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,15 @@ Bug Fixes
4646
By `Justus Magin <https://github.com/keewis>`_.
4747
- Avoid casting custom indexes in ``Dataset.drop_attrs`` (:pull:`10961`)
4848
By `Justus Magin <https://github.com/keewis>`_.
49+
- Support decoding unsigned integers to ``np.timedelta64``.
50+
By `Deepak Cherian <https://github.com/dcherian>`_.
51+
4952

5053
Documentation
5154
~~~~~~~~~~~~~
5255

56+
- Added section on the `limitations of cftime arithmetic <https://docs.xarray.dev/en/stable/user-guide/weather-climate.html#arithmetic-limitations-with-non-standard-calendars>`_ (:pull:`10653`).
57+
By `Lars Buntemeyer <https://github.com/larsbuntemeyer>`_
5358

5459
Internal Changes
5560
~~~~~~~~~~~~~~~~

0 commit comments

Comments
 (0)