Skip to content

Commit 6262014

Browse files
committed
feat: error handling store decorators
1 parent aa0632a commit 6262014

File tree

2 files changed

+80
-20
lines changed

2 files changed

+80
-20
lines changed

dol/tools.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ def store_aggregate(
115115
) = identity, # function to apply to the aggregate before returning
116116
key_filter: Callable[[KT], bool] | None = None, # Filter function for keys
117117
value_filter: Callable[[VT], bool] | None = None, # Filter function for values
118-
kv_filter: None
119-
| (Callable[[tuple[KT, VT]], bool]) = None, # Filter function for key-value pairs
118+
kv_filter: None | (
119+
Callable[[tuple[KT, VT]], bool]
120+
) = None, # Filter function for key-value pairs
120121
local_store_factory: Callable[
121122
[str], Mapping[KT, VT]
122123
] = Latin1TextFiles, # Factory function for the local store
@@ -227,7 +228,9 @@ def actual_kv_filter(kv):
227228

228229
# Create the string by applying filters and kv_to_text conversion
229230
filtered_kv_pairs = filter(actual_kv_filter, content_store.items())
230-
aggregate = aggregator(kv_to_item(k, v) for k, v in filtered_kv_pairs)
231+
aggregate = aggregator(
232+
kv_to_item(k, v) for k, v in filtered_kv_pairs if v is not None
233+
)
231234

232235
return egress(aggregate)
233236

dol/trans.py

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ def validate_decorator_func(decorator_func):
9393
f"First argument of the decorator function needs to default to None. "
9494
f"Was {first_param.default}"
9595
)
96-
assert all(p.kind in {p.KEYWORD_ONLY, p.VAR_KEYWORD} for p in other_params), (
97-
f"All arguments (besides the first) need to be keyword-only"
98-
)
96+
assert all(
97+
p.kind in {p.KEYWORD_ONLY, p.VAR_KEYWORD} for p in other_params
98+
), f"All arguments (besides the first) need to be keyword-only"
9999
return True
100100

101101
validate_decorator_func(decorator_func)
@@ -986,9 +986,9 @@ def _cached_keys(
986986
)
987987
# assert keys_cache == iter_to_container
988988

989-
assert isinstance(store, type), (
990-
f"store_cls must be a type, was a {type(store)}: {store}"
991-
)
989+
assert isinstance(
990+
store, type
991+
), f"store_cls must be a type, was a {type(store)}: {store}"
992992

993993
# name = name or 'IterCached' + get_class_name(store_cls)
994994
name = name or get_class_name(store)
@@ -1220,9 +1220,9 @@ def catch_and_cache_error_keys(
12201220
12211221
"""
12221222

1223-
assert isinstance(store, type), (
1224-
f"store_cls must be a type, was a {type(store)}: {store}"
1225-
)
1223+
assert isinstance(
1224+
store, type
1225+
), f"store_cls must be a type, was a {type(store)}: {store}"
12261226

12271227
# assert isinstance(store, Mapping), f"store_cls must be a Mapping.
12281228
# Was not. mro is {store.mro()}: {store}"
@@ -3245,9 +3245,9 @@ def assert_min_num_of_args(func: Callable, num_of_args: int):
32453245
That is, it should have a signature that takes the store as the first argument
32463246
"""
32473247
try:
3248-
assert len(Sig(func).parameters) >= num_of_args, (
3249-
f"Function {func} doesn't have at least {num_of_args} arguments"
3250-
)
3248+
assert (
3249+
len(Sig(func).parameters) >= num_of_args
3250+
), f"Function {func} doesn't have at least {num_of_args} arguments"
32513251
except Exception as e:
32523252
warn(
32533253
f"Encountered error checking if {func} can be a store method. "
@@ -3256,11 +3256,24 @@ def assert_min_num_of_args(func: Callable, num_of_args: int):
32563256
)
32573257

32583258

3259+
# --------------------------------------------------------------------------------------
3260+
# Missing key handling
3261+
32593262
@store_decorator
3260-
def add_missing_key_handling(store=None, *, missing_key_callback: Callable):
3263+
def add_missing_key_handling(
3264+
store=None,
3265+
*,
3266+
missing_key_callback: Callable,
3267+
errors_that_trigger_missing=(KeyError,),
3268+
):
32613269
"""Overrides the ``__missing__`` method of a store with a custom callback.
32623270
3263-
The callback must have two arguments: the store and the key.
3271+
Note: The callback must have two arguments: the store and the key.
3272+
3273+
Args:
3274+
store: The store class to wrap.
3275+
missing_key_callback: Function(store, key) -> value for missing keys.
3276+
errors_that_trigger_missing: Tuple of exceptions that trigger __missing__.
32643277
32653278
In the following example, we endow a store to return a sub-store when a key is
32663279
missing. This substore will contain only keys that start with that missing key.
@@ -3284,15 +3297,59 @@ def add_missing_key_handling(store=None, *, missing_key_callback: Callable):
32843297
>>> v = s['a/']
32853298
>>> assert dict(v) == {'a/b': 1, 'a/c': 2}
32863299
"""
3300+
if isinstance(errors_that_trigger_missing, BaseException):
3301+
errors_that_trigger_missing = (errors_that_trigger_missing,)
32873302

32883303
assert_min_num_of_args(missing_key_callback, 2)
32893304

32903305
@wraps(store, updated=())
3291-
class StoreWithMissingKeyCallaback(store):
3306+
class StoreWithMissingKeyCallback(store):
3307+
pass
3308+
3309+
StoreWithMissingKeyCallback.__missing__ = missing_key_callback
3310+
StoreWithMissingKeyCallback._errors_that_trigger_missing = (
3311+
errors_that_trigger_missing
3312+
)
3313+
return StoreWithMissingKeyCallback
3314+
3315+
3316+
def ignore_if_error(store=None, *, errors=(KeyError,)):
3317+
def _ignore(store, k):
32923318
pass
32933319

3294-
StoreWithMissingKeyCallaback.__missing__ = missing_key_callback
3295-
return StoreWithMissingKeyCallaback
3320+
return add_missing_key_handling(
3321+
store, missing_key_callback=_ignore, errors_that_trigger_missing=errors
3322+
)
3323+
3324+
3325+
def warn_and_ignore_if_error(
3326+
store=None,
3327+
*,
3328+
errors=(KeyError,),
3329+
warn_msg='Ignoring error in __getitem__ for key {k}: {e}',
3330+
):
3331+
def _warn(store, k):
3332+
import sys
3333+
3334+
exc_type, exc_value, _ = sys.exc_info()
3335+
e = exc_value
3336+
warn(warn_msg.format(k=k, e=e))
3337+
3338+
return add_missing_key_handling(
3339+
store, missing_key_callback=_warn, errors_that_trigger_missing=errors
3340+
)
3341+
3342+
3343+
def return_default_if_error(store=None, *, default=None, errors=(KeyError,)):
3344+
def _default(store, k):
3345+
return default
3346+
3347+
return add_missing_key_handling(
3348+
store, missing_key_callback=_default, errors_that_trigger_missing=errors
3349+
)
3350+
3351+
# --------------------------------------------------------------------------------------
3352+
# Codecs
32963353

32973354

32983355
EncodedType = TypeVar("EncodedType")

0 commit comments

Comments
 (0)