Skip to content
Open
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
108 changes: 85 additions & 23 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def test_component_schema(component, schema_args, validate, json):
jsonschema.validate(schema.json, schema._json_schema)

# json roundtrip
component.from_json(schema.json).json == json
assert component.from_json(schema.json).json == json


@pytest.mark.parametrize(
Expand All @@ -124,36 +124,98 @@ def test_attr_schema(type, value, validate, json):


@pytest.mark.parametrize(
'component, schema_args, validate, match',
'component, schema_args, schema_kwargs, validate, match',
[
(DTypeSchema, np.integer, np.float32, r'.*float.*'),
(DimsSchema, ('foo', 'bar'), ('foo',), r'.*length.*'),
(DimsSchema, ('foo', 'bar'), ('foo', 'baz'), r'.*mismatch.*'),
(ShapeSchema, (1, 2, None), (1, 2), r'.*number of dimensions.*'),
(ShapeSchema, (1, 4, 4), (1, 3, 4), r'.*mismatch.*'),
(NameSchema, 'foo', 'bar', r'.*name bar != foo.*'),
(ArrayTypeSchema, np.ndarray, 'bar', r'.*array_type.*'),
(DTypeSchema, (np.integer,), {}, np.float32, r'.*float.*'),
(DimsSchema, (('foo', 'bar'),), {}, ('foo',), r'.*length.*'),
(DimsSchema, (('foo', 'bar'),), {}, ('foo', 'baz'), r'.*mismatch.*'),
(ShapeSchema, ((1, 2, None),), {}, (1, 2), r'.*number of dimensions.*'),
(ShapeSchema, ((1, 4, 4),), {}, (1, 3, 4), r'.*mismatch.*'),
(NameSchema, ('foo',), {}, 'bar', r'.*name bar != foo.*'),
(ArrayTypeSchema, (np.ndarray,), {}, 'bar', r'.*array_type.*'),
# schema_args for ChunksSchema include [chunks, dims, shape]
(ChunksSchema, {'x': 3}, (((2, 2),), ('x',), (4,)), r'.*(3).*'),
(ChunksSchema, {'x': (2, 1)}, (((2, 2),), ('x',), (4,)), r'.*(2, 1).*'),
(ChunksSchema, {'x': (2, 1)}, (None, ('x',), (4,)), r'.*expected array to be chunked.*'),
(ChunksSchema, True, (None, ('x',), (4,)), r'.*expected array to be chunked.*'),
(ChunksSchema, ({'x': 3},), {}, (((2, 2),), ('x',), (4,)), r'.*(3).*'),
(ChunksSchema, ({'x': (2, 1)},), {}, (((2, 2),), ('x',), (4,)), r'.*(2, 1).*'),
(
ChunksSchema,
False,
({'x': (2, 1)},),
{},
(None, ('x',), (4,)),
r'.*expected array to be chunked.*',
),
(ChunksSchema, (True,), {}, (None, ('x',), (4,)), r'.*expected array to be chunked.*'),
(
ChunksSchema,
(False,),
{},
(((2, 2),), ('x',), (4,)),
r'.*expected unchunked array but it is chunked*',
),
(ChunksSchema, {'x': -1}, (((1, 2, 1),), ('x',), (4,)), r'.*did not match.*'),
(ChunksSchema, {'x': 2}, (((2, 3, 2),), ('x',), (7,)), r'.*did not match.*'),
(ChunksSchema, {'x': 2}, (((2, 2, 3),), ('x',), (7,)), r'.*did not match.*'),
(ChunksSchema, {'x': 2, 'y': -1}, (((2, 2), (5, 5)), ('x', 'y'), (4, 10)), r'.*(5).*'),
(ChunksSchema, ({'x': -1},), {}, (((1, 2, 1),), ('x',), (4,)), r'.* did not match.*'),
(ChunksSchema, ({'x': 2},), {}, (((2, 3, 2),), ('x',), (7,)), r'.* did not match.*'),
(ChunksSchema, ({'x': 2},), {}, (((2, 2, 3),), ('x',), (7,)), r'.* did not match.*'),
(
ChunksSchema,
({'x': 2, 'y': -1},),
{},
(((2, 2), (5, 5)), ('x', 'y'), (4, 10)),
r'.*(5).*',
),
(
AttrsSchema,
({'foo': AttrSchema(type=int)},),
{},
[{'foo': 'bar'}],
r'attrs .* is not of type.*',
),
(
AttrsSchema,
({'foo': AttrSchema(value=1)},),
{},
[{'foo': 'bar'}],
r'attrs .* != .*',
),
(
AttrsSchema,
({'foo': AttrSchema(value=1)},),
{'allow_extra_keys': False},
[{'foo': 'bar', 'x': 0}],
r'attrs has extra keys.*',
),
(
CoordsSchema,
({'x': DataArraySchema(name='x')},),
{},
[{'x': xr.DataArray([0, 1], name='y')}],
r'name .* != .*',
),
(
CoordsSchema,
({'x': DataArraySchema(dtype=np.str_)},),
{},
[{'x': xr.DataArray([0, 1])}],
r'dtype .* != .*',
),
(
CoordsSchema,
({'x': DataArraySchema(dims=('x',))},),
{},
[{'x': xr.DataArray([0, 1], name='x')}],
r'dim mismatch in axis .* != .*',
),
(
CoordsSchema,
({'x': DataArraySchema()},),
{'allow_extra_keys': False},
[{'x': xr.DataArray([0, 1]), 'y': xr.DataArray([0, 1])}],
r'coords has extra keys.*',
),
],
)
def test_component_raises_schema_error(component, schema_args, validate, match):
schema = component(schema_args)
def test_component_raises_schema_error(component, schema_args, schema_kwargs, validate, match):
schema = component(*schema_args, **schema_kwargs)
with pytest.raises(SchemaError, match=match):
if component in [ChunksSchema]: # special case construction
if component in (ChunksSchema, AttrsSchema, CoordsSchema): # special case construction
schema.validate(*validate)
else:
schema.validate(validate)
Expand Down Expand Up @@ -217,7 +279,7 @@ def test_dataset_empty_constructor():
ds_schema = DatasetSchema()
assert hasattr(ds_schema, 'validate')
jsonschema.validate(ds_schema.json, ds_schema._json_schema)
ds_schema.json == {}
assert ds_schema.json == {}


def test_dataset_example(ds):
Expand All @@ -244,7 +306,7 @@ def test_dataset_example(ds):
# json roundtrip
rt_schema = DatasetSchema.from_json(ds_schema.json)
assert isinstance(rt_schema, DatasetSchema)
rt_schema.json == ds_schema.json
assert rt_schema.json == ds_schema.json


def test_checks_ds(ds):
Expand Down
8 changes: 4 additions & 4 deletions xarray_schema/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ def validate(self, attr: Any):

if self.value is not None:
if self.value is not None and self.value != attr:
raise SchemaError(f'name {attr} != {self.value}')
raise SchemaError(f'attrs {attr} != {self.value}')

@property
def json(self) -> dict:
Expand All @@ -376,12 +376,12 @@ class AttrsSchema(BaseSchema):
_json_schema = {
'type': 'object',
'properties': {
'require_all_keys': {
'type': 'boolean'
}, # Question: is this the same as JSON's additionalProperties?
'require_all_keys': {'type': 'boolean'},
'allow_extra_keys': {'type': 'boolean'},
'attrs': {'type': 'object'},
},
'required': ['attrs'],
'additionalProperties': False,
}

def __init__(
Expand Down
8 changes: 4 additions & 4 deletions xarray_schema/dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,12 @@ class CoordsSchema(BaseSchema):
_json_schema = {
'type': 'object',
'properties': {
'require_all_keys': {
'type': 'boolean'
}, # Question: is this the same as JSON's additionalProperties?
'require_all_keys': {'type': 'boolean'},
'allow_extra_keys': {'type': 'boolean'},
'coords': {'type': 'object'},
'coords': {'type': 'object', 'additionalProperties': DataArraySchema._json_schema},
},
'required': ['coords'],
'additionalProperties': False,
}

def __init__(
Expand Down
15 changes: 10 additions & 5 deletions xarray_schema/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class DatasetSchema(BaseSchema):
'coords': {'type': 'object'},
'attrs': {'type': 'object'},
},
'required': [],
'additionalProperties': False,
}

def __init__(
Expand All @@ -47,9 +49,9 @@ def from_json(cls, obj: dict):
k: DataArraySchema.from_json(v) for k, v in obj['data_vars'].items()
}
if 'coords' in obj:
kwargs['coords'] = {k: CoordsSchema.from_json(v) for k, v in obj['coords'].items()}
kwargs['coords'] = CoordsSchema.from_json(obj['coords'])
if 'attrs' in obj:
kwargs['attrs'] = {k: AttrsSchema.from_json(v) for k, v in obj['attrs'].items()}
kwargs['attrs'] = AttrsSchema.from_json(obj['attrs'])

return cls(**kwargs)

Expand Down Expand Up @@ -79,8 +81,8 @@ def validate(self, ds: xr.Dataset) -> None:
else:
da_schema.validate(ds.data_vars[key])

if self.coords is not None: # pragma: no cover
raise NotImplementedError('coords schema not implemented yet')
if self.coords is not None:
self.coords.validate(ds.coords)

if self.attrs:
self.attrs.validate(ds.attrs)
Expand Down Expand Up @@ -131,10 +133,13 @@ def coords(self, value: Optional[Union[CoordsSchema, Dict[Hashable, DataArraySch

@property
def json(self):
obj = {'data_vars': {}, 'attrs': self.attrs.json if self.attrs is not None else {}}
obj = {}
if self.data_vars:
obj['data_vars'] = {}
for key, var in self.data_vars.items():
obj['data_vars'][key] = var.json
if self.coords:
obj['coords'] = self.coords.json
if self.attrs:
obj['attrs'] = self.attrs.json
return obj