Skip to content

Commit

Permalink
feat(spec): support specify spec name for err msg
Browse files Browse the repository at this point in the history
  • Loading branch information
CJHwong committed Jun 7, 2024
1 parent 36668b2 commit 9d2d1f1
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 9 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,24 @@ A DSVError is raised with 3 errors in args.
"""
```
---
### Feature: Self-defined Spec name in error message
```python
from data_spec_validator.spec import Checker, dsv_feature, validate_data_spec, INT

@dsv_feature(spec_name='CustomSpecName')
class _MySpec:
a = Checker([INT])

nok_data = dict(
a='abc',
)

validate_data_spec(nok_data, _MySpec)
"""
TypeError: field: CustomSpecName.a, reason: 'abc' is not an integer
"""
```

---
## Test
Expand Down
4 changes: 3 additions & 1 deletion data_spec_validator/spec/defines.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ def __str__(self, *args, **kwargs):

class ValidateResult:
def __init__(self, spec: Type = None, field: str = None, value: Any = None, check: str = None, error=None):
from .features import get_spec_name # FIXME: refine the structure to avoid circular import

# TODO: Output spec & check information when there's a debug message level for development.
self.__spec = spec.__name__ if spec else None
self.__spec = get_spec_name(spec) if spec else None
self.__field = field
self.__value = value
self.__check = check
Expand Down
30 changes: 23 additions & 7 deletions data_spec_validator/spec/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@


class _DSVFeatureParams:
__slots__ = ('_strict', '_any_keys_set', '_err_mode')
__slots__ = ('_strict', '_any_keys_set', '_err_mode', '_spec_name')

def __init__(self, strict, any_keys_set: Union[Set[Tuple[str, ...]], None], err_mode):
def __init__(self, strict, any_keys_set: Union[Set[Tuple[str, ...]], None], err_mode, spec_name):
self._strict = strict
self._any_keys_set = any_keys_set or set()
self._err_mode = err_mode
self._spec_name = spec_name

@property
def err_mode(self) -> ErrorMode:
Expand All @@ -24,30 +25,45 @@ def strict(self) -> bool:
def any_keys_set(self) -> set:
return self._any_keys_set

@property
def spec_name(self) -> str:
return self._spec_name

def __repr__(self):
return f'_DSVFeatureParams(strict={self._strict}, any_keys_set={self._any_keys_set}, err_mode={self._err_mode})'
return f'_DSVFeatureParams(strict={self._strict}, any_keys_set={self._any_keys_set}, err_mode={self._err_mode}), spec_name={self._spec_name}'


_FEAT_PARAMS = '__feat_params__'


def _process_class(
cls: Type, strict: bool, any_keys_set: Union[Set[Tuple[str, ...]], None], err_mode: ErrorMode
cls: Type,
strict: bool,
any_keys_set: Union[Set[Tuple[str, ...]], None],
err_mode: ErrorMode,
spec_name: Optional[str],
) -> Type:
setattr(cls, _FEAT_PARAMS, _DSVFeatureParams(strict, any_keys_set, err_mode))
setattr(cls, _FEAT_PARAMS, _DSVFeatureParams(strict, any_keys_set, err_mode, spec_name))

return cls


def dsv_feature(
strict: bool = False, any_keys_set: Optional[Set[Tuple[str, ...]]] = None, err_mode=ErrorMode.MSE
strict: bool = False,
any_keys_set: Optional[Set[Tuple[str, ...]]] = None,
err_mode=ErrorMode.MSE,
spec_name: Optional[str] = None,
) -> Callable:
def wrap(cls: Type) -> Type:
return _process_class(cls, strict, any_keys_set, err_mode)
return _process_class(cls, strict, any_keys_set, err_mode, spec_name)

return wrap


def get_spec_name(spec) -> str:
return getattr(spec, _FEAT_PARAMS).spec_name if hasattr(spec, _FEAT_PARAMS) else spec.__name__


def get_err_mode(spec) -> ErrorMode:
feat_params: Union[_DSVFeatureParams, None] = getattr(spec, _FEAT_PARAMS, None)
return feat_params.err_mode if feat_params else ErrorMode.MSE
Expand Down
28 changes: 27 additions & 1 deletion test/test_nested_spec.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import unittest

from data_spec_validator.spec import BOOL, DICT, DIGIT_STR, FLOAT, INT, NONE, SPEC, STR, Checker, validate_data_spec
from data_spec_validator.spec import BOOL, DICT, DIGIT_STR, FLOAT, INT, NONE, SPEC, STR, Checker, validate_data_spec, \
dsv_feature

from .utils import is_something_error

Expand Down Expand Up @@ -61,3 +62,28 @@ class ChildSpec3:
c3_f=[],
)
assert is_something_error(TypeError, validate_data_spec, nok_data, NestedSpec)

def test_nested_error_field_name(self):
class NestedSpec:
class ChildSpec1:
@dsv_feature(spec_name='NestedSpec.c1_f.s_1')
class ChildSpec11:
f_11 = Checker([FLOAT])

s_1 = Checker([SPEC], SPEC=ChildSpec11)

c1_f = Checker([SPEC], SPEC=ChildSpec1)

nok_data = dict(
c1_f=dict(
s_1=dict(
f_11=None,
),
),
)

with self.assertRaises(TypeError) as exc_info:
validate_data_spec(nok_data, NestedSpec)

exc_msg = str(exc_info.exception)
self.assertEqual(exc_msg, 'field: NestedSpec.c1_f.s_1.f_11, reason: None is not a float')

0 comments on commit 9d2d1f1

Please sign in to comment.