Skip to content

Commit bac94d7

Browse files
committed
Merge branch 'main' into forman-47-example_nb
2 parents 38cd411 + 7b5ab81 commit bac94d7

File tree

6 files changed

+66
-5
lines changed

6 files changed

+66
-5
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
### Fixes
44

5+
* When `slice_source` was given as class or function and passed
6+
to the `zappend()` function either as configuration entry or as keyword
7+
argument, a `ValidationError` was accidentally raised. [#49]
8+
59
* Fixed an issue where an absolute lock file path was computed if the target
610
Zarr path was relative in the local filesystem, and had no parent directory.
711
[#45]

tests/test_api.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
import unittest
77

88
import xarray as xr
9+
10+
from zappend.api import FileObj
11+
from zappend.api import SliceSource
912
from zappend.api import zappend
10-
from zappend.fsutil.fileobj import FileObj
1113
from .helpers import clear_memory_fs
1214
from .helpers import make_test_dataset
1315

@@ -50,6 +52,27 @@ def test_some_slices_local(self):
5052
for slice_dir in slices:
5153
shutil.rmtree(slice_dir, ignore_errors=True)
5254

55+
def test_some_slices_with_class_slice_source(self):
56+
target_dir = "memory://target.zarr"
57+
slices = [make_test_dataset(), make_test_dataset(), make_test_dataset()]
58+
zappend(slices, target_dir=target_dir, slice_source=MySliceSource)
59+
ds = xr.open_zarr(target_dir)
60+
self.assertEqual({"time": 9, "y": 50, "x": 100}, ds.sizes)
61+
self.assertEqual({"chl"}, set(ds.data_vars))
62+
self.assertEqual({"time", "y", "x"}, set(ds.coords))
63+
64+
def test_some_slices_with_func_slice_source(self):
65+
def process_slice(ctx, slice_ds: xr.Dataset) -> SliceSource:
66+
return MySliceSource(ctx, slice_ds)
67+
68+
target_dir = "memory://target.zarr"
69+
slices = [make_test_dataset(), make_test_dataset(), make_test_dataset()]
70+
zappend(slices, target_dir=target_dir, slice_source=process_slice)
71+
ds = xr.open_zarr(target_dir)
72+
self.assertEqual({"time": 9, "y": 50, "x": 100}, ds.sizes)
73+
self.assertEqual({"chl"}, set(ds.data_vars))
74+
self.assertEqual({"time", "y", "x"}, set(ds.coords))
75+
5376
def test_some_slices_with_profiling(self):
5477
target_dir = "memory://target.zarr"
5578
slices = [
@@ -74,3 +97,12 @@ def test_some_slices_with_profiling(self):
7497
finally:
7598
if os.path.exists("prof.out"):
7699
os.remove("prof.out")
100+
101+
102+
class MySliceSource(SliceSource):
103+
def __init__(self, ctx, slice_ds):
104+
super().__init__(ctx)
105+
self.slice_ds = slice_ds
106+
107+
def get_dataset(self) -> xr.Dataset:
108+
return self.slice_ds.drop_vars(["tsm"])

tests/test_config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010
import yaml
1111

12+
from zappend.config import exclude_from_config
1213
from zappend.config import get_config_schema
1314
from zappend.config import merge_configs
1415
from zappend.config import normalize_config
@@ -220,6 +221,18 @@ def test_merge_config(self):
220221
merge_configs({"a": {"b": 2, "c": 4}}, {"a": {"b": 3}}),
221222
)
222223

224+
def test_exclude_from_config(self):
225+
with exclude_from_config({}) as config:
226+
self.assertEqual({}, config)
227+
with exclude_from_config({"a": 1, "b": 2}) as config:
228+
self.assertEqual({"a": 1, "b": 2}, config)
229+
with exclude_from_config({}, "a") as config:
230+
self.assertEqual({}, config)
231+
with exclude_from_config({"a": 1, "b": 2}, "a") as config:
232+
self.assertEqual({"b": 2}, config)
233+
with exclude_from_config({"a": 1, "b": 2}, "b", "a") as config:
234+
self.assertEqual({}, config)
235+
223236
def test_get_config_schema(self):
224237
schema = get_config_schema()
225238
self.assertIn("properties", schema)

zappend/config/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .config import ConfigItem
66
from .config import ConfigList
77
from .config import ConfigLike
8+
from .config import exclude_from_config
89
from .config import merge_configs
910
from .config import normalize_config
1011
from .config import validate_config

zappend/config/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Permissions are hereby granted under the terms of the MIT License:
33
# https://opensource.org/licenses/MIT.
44

5+
from contextlib import contextmanager
56
import json
67
import os.path
78
from typing import Any
@@ -135,3 +136,8 @@ def _merge_values(value_1: Any, value_2: Any) -> Any:
135136
if isinstance(value_1, (list, tuple)) and isinstance(value_2, (list, tuple)):
136137
return _merge_lists(value_1, value_2)
137138
return value_2
139+
140+
141+
@contextmanager
142+
def exclude_from_config(config: dict[str, Any], *keys: str) -> dict[str, Any]:
143+
yield {k: v for k, v in config.items() if k not in keys}

zappend/processor.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import xarray as xr
88

99
from .config import ConfigLike
10+
from .config import exclude_from_config
1011
from .config import normalize_config
1112
from .config import validate_config
1213
from .context import Context
@@ -31,20 +32,24 @@ class Processor:
3132
the aforementioned. If a sequence is used, subsequent configurations
3233
are incremental to the previous ones.
3334
kwargs: Additional configuration parameters.
34-
Can be used to pass or override configuration values in *config*.
35+
Can be used to pass or override configuration values in `config`.
3536
"""
3637

3738
def __init__(self, config: ConfigLike = None, **kwargs):
3839
config = normalize_config(config)
3940
config.update({k: v for k, v in kwargs.items() if v is not None})
40-
validate_config(config)
41+
# Validate value of slice_source later,
42+
# it could be a callable instead of a str
43+
# See https://github.com/bcdev/zappend/issues/49
44+
with exclude_from_config(config, "slice_source") as _config:
45+
validate_config(_config)
4146
configure_logging(config.get("logging"))
4247
self._profiler = Profiler(config.get("profiling"))
4348
self._config = config
4449

4550
def process_slices(self, slices: Iterable[SliceObj]):
46-
"""Process the given *slices*.
47-
Passes each slice in *slices* to the `process_slice()` method.
51+
"""Process the given `slices`.
52+
Passes each slice in `slices` to the `process_slice()` method.
4853
4954
Args:
5055
slices: Slice objects.

0 commit comments

Comments
 (0)