Skip to content

Commit f0b149a

Browse files
authored
Merge branch 'main' into grid-polygons
2 parents 79384cd + 24dd81b commit f0b149a

19 files changed

+683
-85
lines changed

.github/workflows/ci.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
run: |
5151
pytest -n auto --cov=./ --cov-report=xml
5252
- name: Upload code coverage to Codecov
53-
uses: codecov/codecov-action@v3.1.4
53+
uses: codecov/codecov-action@v4.3.0
5454
with:
5555
file: ./coverage.xml
5656
flags: unittests
@@ -114,7 +114,7 @@ jobs:
114114
run: |
115115
python -m mypy --install-types --non-interactive --cobertura-xml-report mypy_report cf_xarray/
116116
- name: Upload mypy coverage to Codecov
117-
uses: codecov/codecov-action@v3.1.4
117+
uses: codecov/codecov-action@v4.3.0
118118
with:
119119
file: mypy_report/cobertura.xml
120120
flags: mypy

.github/workflows/pypi.yaml

+7-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- uses: actions/checkout@v4
1616
with:
1717
fetch-depth: 0
18-
- uses: actions/setup-python@v4
18+
- uses: actions/setup-python@v5
1919
name: Install Python
2020
with:
2121
python-version: "3.10"
@@ -41,7 +41,7 @@ jobs:
4141
else
4242
echo "✅ Looks good"
4343
fi
44-
- uses: actions/upload-artifact@v3
44+
- uses: actions/upload-artifact@v4
4545
with:
4646
name: releases
4747
path: dist
@@ -50,11 +50,11 @@ jobs:
5050
needs: build-artifacts
5151
runs-on: ubuntu-latest
5252
steps:
53-
- uses: actions/setup-python@v4
53+
- uses: actions/setup-python@v5
5454
name: Install Python
5555
with:
5656
python-version: "3.10"
57-
- uses: actions/download-artifact@v3
57+
- uses: actions/download-artifact@v4
5858
with:
5959
name: releases
6060
path: dist
@@ -72,7 +72,7 @@ jobs:
7272
7373
- name: Publish package to TestPyPI
7474
if: github.event_name == 'push'
75-
uses: pypa/gh-action-pypi-publish@v1.8.11
75+
uses: pypa/gh-action-pypi-publish@v1.8.14
7676
with:
7777
password: ${{ secrets.TESTPYPI_TOKEN }}
7878
repository_url: https://test.pypi.org/legacy/
@@ -91,11 +91,11 @@ jobs:
9191
id-token: write
9292

9393
steps:
94-
- uses: actions/download-artifact@v3
94+
- uses: actions/download-artifact@v4
9595
with:
9696
name: releases
9797
path: dist
9898
- name: Publish package to PyPI
99-
uses: pypa/gh-action-pypi-publish@v1.8.11
99+
uses: pypa/gh-action-pypi-publish@v1.8.14
100100
with:
101101
verbose: true

.github/workflows/testpypi-release.yaml

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
with:
2222
fetch-depth: 0
2323

24-
- uses: actions/setup-python@v4
24+
- uses: actions/setup-python@v5
2525
name: Install Python
2626
with:
2727
python-version: "3.10"
@@ -53,7 +53,7 @@ jobs:
5353
echo "✅ Looks good"
5454
fi
5555
56-
- uses: actions/upload-artifact@v3
56+
- uses: actions/upload-artifact@v4
5757
with:
5858
name: releases
5959
path: dist
@@ -62,11 +62,11 @@ jobs:
6262
needs: build-artifacts
6363
runs-on: ubuntu-latest
6464
steps:
65-
- uses: actions/setup-python@v4
65+
- uses: actions/setup-python@v5
6666
name: Install Python
6767
with:
6868
python-version: "3.10"
69-
- uses: actions/download-artifact@v3
69+
- uses: actions/download-artifact@v4
7070
with:
7171
name: releases
7272
path: dist

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
upstream-dev:
2323
name: upstream-dev
2424
runs-on: ubuntu-latest
25-
if: ${{ (contains( github.event.pull_request.labels.*.name, 'test-upstream') && github.event_name == 'pull_request') || github.event_name == 'workflow_dispatch' }}
25+
if: ${{ (contains( github.event.pull_request.labels.*.name, 'test-upstream') && github.event_name == 'pull_request') || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}
2626
defaults:
2727
run:
2828
shell: bash -l {0}

.pre-commit-config.yaml

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ repos:
1010

1111
- repo: https://github.com/astral-sh/ruff-pre-commit
1212
# Ruff version.
13-
rev: 'v0.1.1'
13+
rev: 'v0.1.9'
1414
hooks:
1515
- id: ruff
1616
args: ["--show-fixes", "--fix"]
1717

1818
- repo: https://github.com/psf/black-pre-commit-mirror
19-
rev: 23.10.1
19+
rev: 23.12.1
2020
hooks:
2121
- id: black
2222

@@ -36,7 +36,7 @@ repos:
3636
- mdformat-myst
3737

3838
- repo: https://github.com/nbQA-dev/nbQA
39-
rev: 1.7.0
39+
rev: 1.7.1
4040
hooks:
4141
- id: nbqa-black
4242
- id: nbqa-ruff
@@ -56,7 +56,7 @@ repos:
5656
- id: debug-statements
5757

5858
- repo: https://github.com/keewis/blackdoc
59-
rev: v0.3.8
59+
rev: v0.3.9
6060
hooks:
6161
- id: blackdoc
6262
files: .+\.py$

cf_xarray/datasets.py

+10
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,16 @@ def _create_inexact_bounds():
503503
name="flag_var",
504504
)
505505

506+
flag_indep_uint16 = xr.DataArray(
507+
np.array([1, 10, 100, 1000, 10000, 65535], dtype=np.uint16),
508+
dims=("time",),
509+
attrs={
510+
"flag_masks": [2**i for i in range(16)],
511+
"flag_meanings": " ".join([f"flag_{2**i}" for i in range(16)]),
512+
"standard_name": "flag_independent",
513+
},
514+
name="flag_var",
515+
)
506516

507517
flag_mix = xr.DataArray(
508518
np.array([4, 8, 13, 5, 10, 14, 7, 3], np.uint8),

cf_xarray/formatting.py

+55-9
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,47 @@ def _maybe_panel(textgen, title: str, rich: bool):
151151
return title + ":\n" + text
152152

153153

154-
def find_set_bits(mask, value, repeated_masks):
155-
bitpos = np.arange(8)[::-1]
154+
def _get_bit_length(dtype):
155+
# Check if dtype is a numpy dtype, if not, convert it
156+
if not isinstance(dtype, np.dtype):
157+
dtype = np.dtype(dtype)
158+
159+
# Calculate the bit length
160+
bit_length = 8 * dtype.itemsize
161+
162+
return bit_length
163+
164+
165+
def _unpackbits(mask, bit_length):
166+
# Ensure the array is a numpy array
167+
arr = np.asarray(mask)
168+
169+
# Create an output array of the appropriate shape
170+
output_shape = arr.shape + (bit_length,)
171+
output = np.zeros(output_shape, dtype=np.uint8)
172+
173+
# Unpack bits
174+
for i in range(bit_length):
175+
output[..., i] = (arr >> i) & 1
176+
177+
return output[..., ::-1]
178+
179+
180+
def _max_chars_for_bit_length(bit_length):
181+
"""
182+
Find the maximum characters needed for a fixed-width display
183+
for integer values of a certain bit_length. Use calculation
184+
for signed integers, since it conservatively will always have
185+
enough characters for signed or unsigned.
186+
"""
187+
# Maximum value for signed integers of this bit length
188+
max_val = 2 ** (bit_length - 1) - 1
189+
# Add 1 for the negative sign
190+
return len(str(max_val)) + 1
191+
192+
193+
def find_set_bits(mask, value, repeated_masks, bit_length):
194+
bitpos = np.arange(bit_length)[::-1]
156195
if mask not in repeated_masks:
157196
if value == 0:
158197
return [-1]
@@ -161,8 +200,8 @@ def find_set_bits(mask, value, repeated_masks):
161200
else:
162201
return [int(np.log2(mask))]
163202
else:
164-
allset = bitpos[np.unpackbits(np.uint8(mask)) == 1]
165-
setbits = bitpos[np.unpackbits(np.uint8(mask & value)) == 1]
203+
allset = bitpos[_unpackbits(mask, bit_length) == 1]
204+
setbits = bitpos[_unpackbits(mask & value, bit_length) == 1]
166205
return [b if abs(b) in setbits else -b for b in allset]
167206

168207

@@ -184,25 +223,30 @@ def _format_flags(accessor, rich):
184223
# for f, (m, _) in flag_dict.items()
185224
# if m is not None and m not in repeated_masks
186225
# ]
226+
227+
bit_length = _get_bit_length(accessor._obj.dtype)
228+
mask_width = _max_chars_for_bit_length(bit_length)
229+
key_width = max(len(key) for key in flag_dict)
230+
187231
bit_text = []
188232
value_text = []
189233
for key, (mask, value) in flag_dict.items():
190234
if mask is None:
191235
bit_text.append("✗" if rich else "")
192236
value_text.append(str(value))
193237
continue
194-
bits = find_set_bits(mask, value, repeated_masks)
195-
bitstring = ["."] * 8
238+
bits = find_set_bits(mask, value, repeated_masks, bit_length)
239+
bitstring = ["."] * bit_length
196240
if bits == [-1]:
197241
continue
198242
else:
199243
for b in bits:
200244
bitstring[abs(b)] = _format_cf_name("1" if b >= 0 else "0", rich)
201245
text = "".join(bitstring[::-1])
202246
value_text.append(
203-
f"{mask} & {value}"
247+
f"{mask:{mask_width}} & {value}"
204248
if key in excl_flags and value is not None
205-
else str(mask)
249+
else f"{mask:{mask_width}}"
206250
)
207251
bit_text.append(text if rich else f" / Bit: {text}")
208252

@@ -230,7 +274,9 @@ def _format_flags(accessor, rich):
230274
else:
231275
rows = []
232276
for val, bit, key in zip(value_text, bit_text, flag_dict):
233-
rows.append(f"{TAB}{_format_cf_name(key, rich)}: {TAB} {val} {bit}")
277+
rows.append(
278+
f"{TAB}{_format_cf_name(key, rich):>{key_width}}: {TAB} {val} {bit}"
279+
)
234280
return _print_rows("Flag Meanings", rows, rich)
235281

236282

0 commit comments

Comments
 (0)