Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/change_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Unreleased
^^^^^^^^^^
TODO: highlights

* `@pp-mo`_ data constructors support `attributes={name:value, ..}`.
(`PR#71 <https://github.com/pp-mo/ncdata/pull/71>`_).

* `@pp-mo`_ dataset comparison routines now a public utility.
(`PR#70 <https://github.com/pp-mo/ncdata/pull/70>`_).

Expand Down
19 changes: 18 additions & 1 deletion lib/ncdata/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ def from_items(

If 'arg' is an iterable, its contents are added.

If 'arg' is a mapping, it must have (key == arg[key].name) for all keys.
If 'arg' is a mapping, it normally must have (key == arg[key].name)
for all keys. As a special case, only if `item_type` ==
:class:`NcAttribute`, a plain name: value map can be provided, which
is converted to name: NcAttribute(name, value).

If 'arg'' is a NameMap of the same 'item_type' (including None), then 'arg'
is returned unchanged as the result.
Expand All @@ -169,6 +172,20 @@ def from_items(
if arg is not None:
# We expect either another type of dictionary, or a list of items.
if isinstance(arg, Mapping):
if (
item_type == NcAttribute
and len(arg) > 0
and not isinstance(
next(iter(arg.values())), NcAttribute
)
):
# for attributes only, also allow simple name=value map
# for which, convert each value to an NcAttribute
arg = {
name: NcAttribute(name, value)
for name, value in arg.items()
}
# existing mapping of NameMap type
# ignore mapping keys, and set [name]=item.name for each value.
result.addall(arg.values())
elif isinstance(arg, Iterable):
Expand Down
14 changes: 7 additions & 7 deletions tests/integration/example_scripts/ex_dataset_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import iris

import ncdata.iris as nci
from ncdata import NcAttribute, NcData, NcDimension, NcVariable
from ncdata import NcData, NcDimension, NcVariable
from tests import testdata_dir


Expand All @@ -22,14 +22,14 @@ def sample_printout(): # noqa: D103
NcVariable(
name="x",
dimensions=["y", "extra_qq"],
attributes=[
NcAttribute("q1", 1),
NcAttribute("q_multi", [1.1, 2.2]),
NcAttribute("q_multstr", ["one", "two"]),
],
attributes={
"q1": 1,
"q_multi": [1.1, 2.2],
"q_multstr": ["one", "two"],
},
),
],
attributes=[NcAttribute("extra__global", "=value")],
attributes={"extra__global": "=value"},
)
print(ds)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def example_nc4_save_reload_unlimited_roundtrip():
dtype=np.float32,
data=np.arange(4),
# Just an an attribute for the sake of it.
attributes=[NcAttribute("varattr1", 1)],
attributes={"varattr1": 1},
)
ncdata.attributes["globalattr1"] = NcAttribute("globalattr1", "one")
print("Source ncdata object:")
Expand Down
87 changes: 86 additions & 1 deletion tests/unit/core/test_NameMap.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from ncdata import NameMap
from ncdata import NameMap, NcAttribute


class NamedItem:
Expand Down Expand Up @@ -144,6 +144,91 @@ def test_namemap_arg__bad_typeconvert__fail(self):
NameMap.from_items(arg, item_type=OtherNamedItem)


class TestAttributesFromItems:
"""
Extra constructor checks *specifically* for attributes.

Since they are treated differently in order to support the
"attributes={'x':1, 'y':2}" constructor style.
"""

@pytest.fixture(
params=[None, NcAttribute, NamedItem],
ids=["none", "attrs", "nonattrs"],
)
def target_itemtype(self, request):
return request.param

@pytest.fixture(params=[False, True], ids=["single", "multiple"])
def multiple(self, request):
return request.param

def test_attributes__map(self, target_itemtype, multiple):
# Create from classic map {name: NcAttr(name, value)}
arg = {"x": NcAttribute("x", 1)}
if multiple:
arg["y"] = NcAttribute("y", 2)

if target_itemtype == NamedItem:
msg = "Item expected to be of type.*NamedItem.* got NcAttribute"
with pytest.raises(TypeError, match=msg):
NameMap.from_items(arg, item_type=target_itemtype)
else:
namemap = NameMap.from_items(arg, item_type=target_itemtype)
assert namemap.item_type == target_itemtype
# Note: this asserts that the contents are the *original* uncopied
# NcAttribute objects, since we don't support == on NcAttributes
assert namemap == arg

def test_attributes__list(self, target_itemtype, multiple):
# Create from a list [*NcAttr(name, value)]
arg = [NcAttribute("x", 1), NcAttribute("y", 2)]
if not multiple:
arg = arg[:1]

if target_itemtype == NamedItem:
msg = "Item expected to be of type.*NamedItem.* got NcAttribute"
with pytest.raises(TypeError, match=msg):
NameMap.from_items(arg, item_type=target_itemtype)
else:
namemap = NameMap.from_items(arg, item_type=target_itemtype)
assert namemap.item_type == target_itemtype
assert list(namemap.keys()) == [attr.name for attr in arg]
# Again, content is the original objects
assert list(namemap.values()) == arg

def test_attributes__namevaluemap(self, target_itemtype, multiple):
# Create from a newstyle map {name: value}
arg = {"x": 1}
if multiple:
arg["y"] = 2
if target_itemtype != NcAttribute:
if target_itemtype is None:
msg = "Item has no '.name' property"
else:
# target_itemtype == NamedItem
msg = "Item expected to be of type.*NamedItem"
with pytest.raises(TypeError, match=msg):
NameMap.from_items(arg, item_type=target_itemtype)
else:
namemap = NameMap.from_items(arg, item_type=target_itemtype)
assert namemap.item_type == target_itemtype
assert list(namemap.keys()) == list(arg.keys())
# Note: a bit of a fuss because we don't have == for NcAttributes
vals = list(namemap.values())
assert all(isinstance(el, NcAttribute) for el in vals)
vals = [val.value for val in vals]
assert vals == list(arg.values())

@pytest.mark.parametrize(
"arg", [[], {}, None], ids=["list", "map", "none"]
)
def test_attributes_empty(self, arg):
# Just check correct construction from empty args.
namemap = NameMap.from_items(arg, item_type=NcAttribute)
assert namemap == {} and namemap is not arg


class Test_copy:
def test_copy(self, item_type):
source = sample_namemap(item_type=item_type)
Expand Down
11 changes: 3 additions & 8 deletions tests/unit/core/test_NcVariable.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,7 @@ def test_dimsnoargs(self):
assert result == expected

def test_oneattr(self):
var = NcVariable(
"var_w_attrs", attributes={"a1": NcAttribute("a1", 1)}
)
var = NcVariable("var_w_attrs", attributes={"a1": 1})
result = str(var)
expected = "\n".join(
[
Expand All @@ -188,10 +186,7 @@ def test_oneattr(self):
def test_multiattrs(self):
var = NcVariable(
"var_multi",
attributes={
"a1": NcAttribute("a1", 1),
"a2": NcAttribute("a2", ["one", "three"]),
},
attributes={"a1": 1, "a2": ["one", "three"]},
)
result = str(var)
expected = "\n".join(
Expand All @@ -218,7 +213,7 @@ def test_repr(self):
var = NcVariable(
"var",
dimensions=("x", "y"),
attributes=[NcAttribute("a1", 1)],
attributes={"a1": 1},
)
result = repr(var)
expected = f"<ncdata._core.NcVariable object at 0x{id(var):012x}>"
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/utils/test_save_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ def _basic_testdata():
name="vx1",
dimensions=("x"),
data=[1, 2, 3],
attributes=[NcAttribute("xx", 1)],
attributes={"xx": 1},
)
],
groups=[NcData("inner")],
attributes=[NcAttribute("x", 1)],
attributes={"x": 1},
)
return ncdata

Expand Down Expand Up @@ -122,7 +122,7 @@ def test_valid_datatypes(self, datatype, structuretype):
# These produce "unsaveable datatype" errors.
pytest.skip("invalid dtype fails")
value = attrvalue(datatype, structuretype)
ncdata = NcData(attributes=[NcAttribute("x", value)])
ncdata = NcData(attributes={"x": value})
errors = save_errors(ncdata)
assert errors == []

Expand Down
14 changes: 5 additions & 9 deletions tests/unit/xarray/test_to_xarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import numpy as np
import pytest

from ncdata import NcAttribute, NcData, NcDimension, NcVariable
from ncdata import NcData, NcDimension, NcVariable
from ncdata.xarray import to_xarray
from tests import MonitoredArray

Expand Down Expand Up @@ -86,14 +86,10 @@ def test_kwargs__scaleandoffset(scaleandoffset):
NcVariable(
name="var_x",
dimensions=["x"],
attributes=[
NcAttribute(
"scale_factor", np.array(0.1, dtype=np.float32)
),
NcAttribute(
"add_offset", np.array(-5.3, dtype=np.float32)
),
],
attributes={
"scale_factor": np.array(0.1, dtype=np.float32),
"add_offset": np.array(-5.3, dtype=np.float32),
},
data=raw_int_data,
)
],
Expand Down