Created
September 29, 2019 07:39
-
-
Save feisuzhu/39c07a3d1fb3ca9160a2e3e939922b4e to your computer and use it in GitHub Desktop.
JSON check
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
from __future__ import absolute_import, division, print_function, unicode_literals | |
# -- stdlib -- | |
import types | |
import re | |
import arrow | |
# -- third party -- | |
# -- own -- | |
# -- code -- | |
def instantiate(f): | |
return f() | |
def select_keys(d, keys): | |
return {k: d.get(k) for k in keys} | |
class CheckTypeFailed(Exception): | |
def __init__(self): | |
Exception.__init__(self) | |
self.path = [] | |
def path_string(self): | |
return ''.join([ | |
self._path_fragment(v) | |
for v in self.path | |
]) | |
def _path_fragment(self, v): | |
if isinstance(v, int): | |
return '[%s]' % v | |
elif isinstance(v, str): | |
return '.%s' % v | |
def finalize(self): | |
self.args = (self.path_string(), ) | |
def _check(cond): | |
if not cond: | |
raise CheckTypeFailed | |
def _check_isinstance(obj, cls): | |
try: | |
_check(isinstance(obj, cls)) | |
except TypeError as e: | |
raise CheckTypeFailed from e | |
_check_key_not_exists = object() | |
def check_type_exc(pattern, obj, path=None): | |
try: | |
if isinstance(pattern, (list, tuple)): | |
_check_isinstance(obj, (list, tuple)) | |
if len(pattern) == 2 and pattern[-1] is ...: | |
cls = pattern[0] | |
for i, v in enumerate(obj): | |
check_type_exc(cls, v, i) | |
else: | |
_check(len(pattern) == len(obj)) | |
for i, (cls, v) in enumerate(zip(pattern, obj)): | |
check_type_exc(cls, v, i) | |
elif isinstance(pattern, dict): | |
_check_isinstance(obj, dict) | |
if ... in pattern: | |
pattern = dict(pattern) | |
match = pattern.pop(...) | |
else: | |
match = '!' | |
if match in set('?!='): | |
lkeys = set(pattern.keys()) | |
rkeys = set(obj.keys()) | |
if match == '!': | |
iterkeys = lkeys | |
elif match == '?': | |
iterkeys = lkeys & rkeys | |
elif match == '=': | |
_check(lkeys == rkeys) | |
iterkeys = lkeys | |
else: | |
assert False, 'WTF?!' | |
for k in iterkeys: | |
check_type_exc(pattern[k], obj.get(k, _check_key_not_exists), k) | |
elif match is ...: | |
assert len(pattern) == 1, 'Invalid dict pattern' | |
kt, vt = list(pattern.items())[0] | |
for k in obj: | |
check_type_exc(kt, k, '<%s>' % kt.__name__) | |
check_type_exc(vt, obj[k], k) | |
else: | |
assert False, 'Invalid dict match type' | |
else: | |
if issubclass(type(pattern), types.FunctionType): | |
try: | |
_check(pattern(obj)) | |
except Exception as e: | |
raise CheckTypeFailed from e | |
elif issubclass(type(pattern), (int, str, bytes, tuple)): | |
_check(obj == pattern) | |
else: | |
_check_isinstance(obj, pattern) | |
except CheckTypeFailed as e: | |
if path is not None: | |
e.path.insert(0, path) | |
else: | |
e.finalize() | |
raise | |
def check_type(pattern, obj): | |
try: | |
check_type_exc(pattern, obj) | |
return None | |
except CheckTypeFailed as e: | |
return e.path_string() | |
class validators(object): | |
def phone(v): | |
return v.isdigit() and re.match(r'^1[3-9][0-9]{9}$', v) | |
def gender(v): | |
return v in ('male', 'female') | |
def datetime(v): | |
return bool(arrow.get(v)) | |
@instantiate | |
class length(object): | |
def __getitem__(self, sl): | |
def _length_checker(v): | |
if sl.start is not None and not len(v) >= sl.start: | |
return False | |
if sl.stop is not None and not len(v) <= sl.stop: | |
return False | |
return True | |
return _length_checker | |
def maybe(cls): | |
def _maybe_checker(obj): | |
if obj in (_check_key_not_exists, None): | |
return True | |
return isinstance(obj, cls) | |
return _maybe_checker |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
from __future__ import absolute_import, division, print_function, unicode_literals | |
# -- stdlib -- | |
# -- third party -- | |
from nose.tools import eq_ | |
# -- own -- | |
# -- code -- | |
class TestUtilsMisc(object): | |
def test_check_type(self): | |
from utils.misc import check_type | |
eq_(check_type(int, 1), None) | |
eq_(check_type(str, 'asdf'), None) | |
eq_(check_type([int, str], [1, 'foo']), None) | |
eq_(check_type([int, ...], [1, 2, 3, 4]), None) | |
eq_(check_type([[int, str], ...], [[1, 'foo'], [2, 'bar']]), None) | |
eq_(check_type([[int, lambda v: v == 1], ...], [[1, 1], [2, 1]]), None) | |
eq_(check_type({'foo': int, 'bar': str, ...: '='}, {'foo': 1, 'bar': 'asdf'}), None) | |
eq_(check_type({'foo': int, 'bar': str}, {'foo': 1, 'bar': 'asdf', 'baz': 1}), None) | |
eq_(check_type({'foo': int, 'bar': str, ...: '?'}, {'foo': 1, 'baz': 1}), None) | |
eq_(check_type({str: int, ...: ...}, {'foo': 1, 'baz': 1}), None) | |
eq_(check_type({'foo': 1, 'baz': 2}, {'foo': 1, 'baz': 2}), None) | |
eq_(check_type(int, 'asdf'), '') | |
eq_(check_type(str, 1), '') | |
eq_(check_type([int, str], ['foo', 1]), '[0]') | |
eq_(check_type([int, ...], [1, 2, None, 4]), '[2]') | |
eq_(check_type([[int, str], ...], [[1, 'foo'], [2, None]]), '[1][1]') | |
eq_(check_type([[int, lambda v: v == 1], ...], [[1, 2], [2, 1]]), '[0][1]') | |
eq_(check_type({'foo': int, 'bar': str, ...: '='}, {'foo': 1, 'bar': 1}), '.bar') | |
eq_(check_type({'foo': int, 'bar': str}, {'foo': 1, 'baz': 1}), '.bar') | |
eq_(check_type({'foo': int, 'bar': str, ...: '?'}, {'foo': None, 'baz': 1}), '.foo') | |
eq_(check_type({str: int, ...: ...}, {'foo': 1, 'baz': 'meh'}), '.baz') # UGC! DANGER! | |
eq_(check_type({str: int, ...: ...}, {'foo': 1, 2: 1}), '.<str>') | |
eq_(check_type({'foo': 1, 'baz': 1}, {'foo': 1, 'baz': 2}), '.baz') | |
eq_(check_type({'foo': lambda v: 1 / 0}, {'foo': 1}), '.foo') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment