Upload
-
View
106
Download
0
Embed Size (px)
Citation preview
About meMikhail Krivushin
tech lead at AppCraft from Samara
github: deepwalker [email protected]
• “Don’t fear” talk author • Can tie bootlaces
Trafaret
• one from tens of validation libs, but with nuance
• trafaret not only validates but converts data
• loves simple functions and very easy to extend
Trafaret Base
• DataError, Trafaret base class, trafaret.Call
• Int, Float, String, Any, Null, Bool, Type, Subclass
• StrBool, Atom, URL, Email
• List, Tuple, Enum
• Dict, Key
show me the code
import trafaret as t
name = t.RegexpRaw(‘name=(\w+)’) & (lambda match: match.groups()[0]) name(‘name=Joe’) == ‘Joe’
APP_CONFIG = t.Dict({‘kill_them_all’: t.StrBool}) >> json.dumps APP = t.Dict({ ‘app_id’: t.Int, ‘name’: t.String, ‘config’: APP_CONFIG, })
app = App(**APP(request.args)) db.session.add(app)
show me the code
def trafaret_error(meth): @wraps(meth) def wrapper(*a, **kw): try: return meth(*a, **kw) except t.DataError as de: return ( ujson.dumps({‘error': de.as_dict()}), 400, {'Content-Type': ‘application/json’}, ) return wrapper
The beginningAndrey Vlasovskikh: funcparserlib https://github.com/vlasovskikh/
funcparserlib
null = n('null') >> const(None) true = n('true') >> const(True) false = n('false') >> const(False) string = toktype('String') >> make_string value = forward_decl() member = string + op_(':') + value >> tuple
The beginningVictor Kotseruba: Contract
https://github.com/barbuza/contract
from contract import ( IntC, ListC, DictC, StringC, ContractValidationError,
)
list_of_ints = ListC[IntC] foobar = DictC(foo=IntC, bar=StringC)
try: foobar(data) except ContractValidationError: print “AAAAAAaaaaaAA”
Simple functiondef check_int(value: Any) -> bool: return ( isinstance(value, int) or (isinstance(value, str) and value.isdigit()) )
def ensure_int(value: Any) -> Union[int, None]: if ( isinstance(value, int) or (isinstance(value, str) and value.isdigit()) ): return int(value) return None
Function evolutiondef try_int(value: Any) -> Union[int, DataError]: if ( isinstance(value, int) or (isinstance(value, str) and value.isdigit()) ): return int(value) return DataError(‘Not an int’)
def less_then_500(value: int) -> Union[int, DataError]: if value < 500: return value return DataError(‘value too big’)
Go Style go go go
try: value = check_int(arg) except t.DataError as data_error: return str(data_error) try: value = less_then_500(value) except t.DataError as data_error: return str(data_error) …
Legacy: The Dark Times
When Trafaret was young
class Trafaret: def __init__(self): self.converters = []
def append(self, converter): self.converters.append(converter)
def __rshift__(self, converter): self.append(converter)
int_less_500 = t.Int() int_less_500.append(less_then_500)
int_less_500 = t.Int() >> less_then_500
Legacy: The Dark Times
When Trafaret was young
def int_less_500_producer(): return t.Int() >> less_then_500
int_less_500_producer() >> but_bigger_then_5
PythonNo, this is not what we like to do
@do(Maybe) def with_maybe(first_divisor): val1 = yield mdiv(2.0, 2.0) val2 = yield mdiv(3.0, 0.0) val3 = yield mdiv(val1, val2) mreturn(val3)
Just a whimsical compose for a weird functions
simple_func::simple_type -> simple_type simple_func1 • simple_func2
weird1::simple_type -> complex_type weird1 >>= weird2
Trafaret typemypy notation:
TrafaretRes = Union[Any, DataError] Trafaret = Callable[[Any], TrafaretRes]
in haskell you will name it Either
Whimsical Compose
Lets compose weird trafaret functions
def t_and(t1, t2): def composed(value): res = t1(value) if isinstance(res, DataError): return res return t2(res) return composed
int_less_500 = t_and(try_int, less_then_500) int_less_500 = t.Int & less_then_500
Go Style no no no
int_less_then_500 = t.Int & less_then_500
try: value = int_less_then_500(arg) except t.DataError as data_error: return str(data_error) …
Operations
check = t.Int | t.Bool check(True) == True
import trafaret as t check = t.Bool & int check(True) == 1
check = (t.Int | t.Bool) & int check(True) == 1
compose aka and
good old `or`
all together now
import arrow
def check_date(value): try: return arrow.get(value) except arrow.parser.ParserError: return t.DataError(‘Bad datetime format’)
import ujson
def check_json(value): try: return ujson.loads(value) except ValueError: return t.DataError(‘Bad JSON value’) except TypeError: return t.DataError(‘Bad JSON value’)
Enlarge your T
Dict and Key
• everyone needs to check dicts
• keys can be optional, can have default
• dict can ban extra values or ignore them
• do you want to check only dicts actually? What about MultiDict?
Trafaret Dict Solution
• Key are the item getters and trafaret callers
• Dict collects keys output
• you can implement your own Key like a function or subclass Key
• keys touches value instance, so be careful with quantum dicts
What Key Type Signature Is?KeyT = Callable[ [Mapping], Sequence[ Tuple[ str, Union[Any, t.DataError], Sequence[str] ] ] ]
def some_key(name, trafaret): trafaret = t.ensure_trafaret(trafaret) def check(value): if name in value: yield name, t.catch_error(trafaret, value.get(name)), (name,) else: yield name, t.DataError(‘is required’), (name,) return check
Dict Usagecheck_request = check_json & t.Dict(some_key(‘count’, t.Int)) check_request(‘{“count”: 5}’) == {‘count’: 5}
check_comma_str = t.String() & (lambda s: s.split(‘,’))
t.Dict({ ‘count’: t.Int(), t.Key(‘UserName’) >> ‘username’: t.String(min_length=5), ‘groups’: check_comma_string & t.List(t.Enum(‘by_device’, ‘by_city’)), })
MultiDict Key
class MultiDictKey(t.Key): def get_data(self, value): return value.get_all(self.name)
check_request = t.Dict( t.Key(‘username’, t.String), MultiDictKey(‘group’, t.List(check_comma_string)), )
Keys Insanity
def xor_key(first, second, trafaret): trafaret = t.Trafaret._trafaret(trafaret) def check_(value): if (first in value) ^ (second in value): key = first if first in value else second yield first, t.catch_error(trafaret, value[key]), (key,) elif first in value and second in value: yield first, t.DataError(error=f'correct only if {second} is not defined'), (first,) yield second, t.DataError(error=f'correct only if {first} is not defined'), (second,) else: yield first, t.DataError(error=f'is required if {second} is not defined'), (first,) yield second, t.DataError(error=f'is required if {first} is not defined'), (second,) return check_
Sugar
from trafaret.constructor import construct, C
construct({ ‘a’: int, ‘b’: [bool], ‘c’: (str, str), })
C & int & bool
Form Helpers
fold
unfold
>>> unfold({'a': [1,2], 'b': {'c': 'bla'}}) {'a__0': 1, 'a__1': 2, 'b__c': 'bla'}
>>> fold({'a__0': 1, 'a__1': 2, 'b__c': 'bla'}) {'a': [1, 2], 'b': {'c': 'bla'}}