Skip to content

Commit bd231d8

Browse files
authored
Rewrite dicts like .chunks and .sizes (#42)
* Rewrite dicts like .chunks and .sizes * Fix tests and add fallback behaviour. key-value pairs that are not "special" are copied over. * Add docs. * typo
1 parent 9d53560 commit bd231d8

File tree

4 files changed

+120
-4
lines changed

4 files changed

+120
-4
lines changed

cf_xarray/accessor.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,38 @@ def _getattr(
348348
An extra decorator, if necessary. This is used by _CFPlotMethods to set default
349349
kwargs based on CF attributes.
350350
"""
351-
func: Callable = getattr(obj, attr)
351+
attribute: Union[Mapping, Callable] = getattr(obj, attr)
352+
353+
if isinstance(attribute, Mapping):
354+
if not attribute:
355+
return dict(attribute)
356+
# attributes like chunks / sizes
357+
newmap = dict()
358+
unused_keys = set(attribute.keys())
359+
for key in _AXIS_NAMES + _COORD_NAMES:
360+
value = _get_axis_coord(obj, key, error=False, default=None)
361+
unused_keys -= set(value)
362+
if value != [None]:
363+
good_values = set(value) & set(obj.dims)
364+
if not good_values:
365+
continue
366+
if len(good_values) > 1:
367+
raise AttributeError(
368+
f"cf_xarray can't wrap attribute {attr!r} because there are multiple values for {key!r} viz. {good_values!r}. "
369+
f"There is no unique mapping from {key!r} to a value in {attr!r}."
370+
)
371+
newmap.update({key: attribute[good_values.pop()]})
372+
newmap.update({key: attribute[key] for key in unused_keys})
373+
return newmap
374+
375+
elif isinstance(attribute, Callable): # type: ignore
376+
func: Callable = attribute
377+
378+
else:
379+
raise AttributeError(
380+
f"cf_xarray does not know how to wrap attribute '{type(obj).__name__}.{attr}'. "
381+
"Please file an issue if you have a solution."
382+
)
352383

353384
@functools.wraps(func)
354385
def wrapper(*args, **kwargs):

cf_xarray/tests/datasets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
)
7272
anc
7373

74+
7475
multiple = xr.Dataset()
7576
multiple.coords["x1"] = ("x1", range(30), {"axis": "X"})
7677
multiple.coords["y1"] = ("y1", range(20), {"axis": "Y"})

cf_xarray/tests/test_accessor.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,40 @@
1717
objects = datasets + dataarrays
1818

1919

20+
def test_dicts():
21+
from .datasets import airds
22+
23+
actual = airds.cf.sizes
24+
expected = {"X": 50, "Y": 25, "T": 4, "longitude": 50, "latitude": 25, "time": 4}
25+
assert actual == expected
26+
27+
assert popds.cf.sizes == popds.sizes
28+
29+
with pytest.raises(AttributeError):
30+
multiple.cf.sizes
31+
32+
assert airds.cf.chunks == {}
33+
34+
expected = {
35+
"X": (50,),
36+
"Y": (5, 5, 5, 5, 5),
37+
"T": (4,),
38+
"longitude": (50,),
39+
"latitude": (5, 5, 5, 5, 5),
40+
"time": (4,),
41+
}
42+
assert airds.chunk({"lat": 5}).cf.chunks == expected
43+
44+
with pytest.raises(AttributeError):
45+
airds.da.cf.chunks
46+
47+
airds = airds.copy(deep=True)
48+
airds.lon.attrs = {}
49+
actual = airds.cf.sizes
50+
expected = {"lon": 50, "Y": 25, "T": 4, "latitude": 25, "time": 4}
51+
assert actual == expected
52+
53+
2054
def test_describe():
2155
actual = airds.cf._describe()
2256
expected = (

doc/examples/introduction.ipynb

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,58 @@
411411
"pop.cf.get_valid_keys()"
412412
]
413413
},
414+
{
415+
"cell_type": "markdown",
416+
"metadata": {},
417+
"source": [
418+
"## Feature: Rewriting property dictionaries\n",
419+
"\n",
420+
"`cf_xarray` will rewrite the `.sizes` and `.chunks` dictionaries so that one can index by a special CF axis or coordinate name"
421+
]
422+
},
423+
{
424+
"cell_type": "code",
425+
"execution_count": null,
426+
"metadata": {},
427+
"outputs": [],
428+
"source": [
429+
"ds.cf.sizes"
430+
]
431+
},
432+
{
433+
"cell_type": "markdown",
434+
"metadata": {},
435+
"source": [
436+
"Note the duplicate entries above:\n",
437+
"\n",
438+
"1. One for `X`, `Y`, `T`\n",
439+
"2. and one for `longitude`, `latitude` and `time`.\n",
440+
"\n",
441+
"An error is raised if there are multiple `'X'` variables (for example)"
442+
]
443+
},
444+
{
445+
"cell_type": "code",
446+
"execution_count": null,
447+
"metadata": {
448+
"tags": [
449+
"raises-exception"
450+
]
451+
},
452+
"outputs": [],
453+
"source": [
454+
"multiple.cf.sizes"
455+
]
456+
},
457+
{
458+
"cell_type": "code",
459+
"execution_count": null,
460+
"metadata": {},
461+
"outputs": [],
462+
"source": [
463+
"multiple.v1.cf.sizes"
464+
]
465+
},
414466
{
415467
"cell_type": "markdown",
416468
"metadata": {},
@@ -637,9 +689,7 @@
637689
"cell_type": "markdown",
638690
"metadata": {},
639691
"source": [
640-
"#### miscellaneous features\n",
641-
"\n",
642-
"You can mix \"special names\" and variable names"
692+
"## Feature: mix \"special names\" and variable names"
643693
]
644694
},
645695
{

0 commit comments

Comments
 (0)