Reference¶
Quick schema validation.
-
nil= <object object>¶ Used to signify nonexistance.
-
exception
Error(code, *info, message=None)¶ Thrown when something goes wrong.
Variables:
-
class
Nex¶ Bases:
OpRepresents the
ORoperator.Values will be checked in order. If none pass, the last error is raised.
First data passing will be used for tracing.
>>> def less(value): >>> return value < 5: >>> def more(value): >>> return value > 9 >>> fig = Nex(int, less, more) >>> check(fig, 12)
The above will pass, since
12is greater than9.
-
class
And¶ Bases:
OpRepresents the
ANDoperator.Values will be checked in order. If one fails, its error is raised.
Last data passing will be used for tracing.
>>> def less(value): >>> return value < 5: >>> def more(value): >>> return value > 9 >>> fig = And(int, less, more) >>> check(fig, 12)
The above will fail, since
12is not less than5.
-
class
Opt(value)¶ Signals an optional value.
>>> fig = {Opt('one'): int, 'two': int} >>> check(fig, {'two': 5})
The above will pass since
"one"is not required but"two"is.
-
class
Con(change, figure)¶ Signals a conversion to the data before checking.
Data will not be used for tracing.
>>> def less(value): >>> return value < 8 >>> fig = (str, Con(len, less)) >>> check(fig, 'ganglioneuralgia')
The above will fail since the length of… that is greater than
8.
-
class
Rou(figure, success, failure=<object object>)¶ Routes validation according to a condition.
Data passing will be used for tracing.
>>> fig = And( >>> str, >>> If( >>> lambda data: '@' in data, >>> email_fig, # true >>> Con(int, phone_fig) # false >>> ) >>> ) >>> check(fig, 'admin@domain.com') >>> check(fig, '0123456789')
-
class
Not(figure)¶ Represents the
NOToperator.Data will be used for tracing.
fig = Not(And(str, Con(len, lambda v: v > 5))) check(fig, 'pass1234')
-
class
Exc(figure, exceptions)¶ Fail when exceptions are raised.
Data will be used for tracing.
fig = Exc(int, ValueError)
-
check(figure, data, auto=False, extra=[])¶ Validates data against the figure and returns a compliant copy.
Parameters: - figure (any) – Some object or class to validate against.
- data (any) – Some object or class to validate.
- auto (bool) – Whether to validate types.
- extra (list[func]) – Called with
figureand should return :var:`.nil` or a figure.
-
wrap(figure, *parts, **kwargs)¶ Use
partswhen an error is raised against thisfigure.kwargsgets passed tocheck()automatically.>>> fig = wrap( >>> shucks.range(4, math.inf, left = False), >>> 'cannot be over 4' >>> ) >>> data = ... >>> check(fig, data)
Errors¶
Type fails raise:
Error('type', expected_cls, given_cls)
Object equality fails raise:
Error('object', expected_val, given_val)
Array value check fails raise:
Error('index', index) from source_error
Arrays that are too small raise:
Error('small', expected_size, actual_size)
Arrays that have at least one additional value raise:
Error('large', exceeded_size)
Note
Array checking is done in an iterator-exhausting fashion.
Upon finishing, one more item is attempted to be generated for this error.
Missing mapping keys raise:
Error('key', expected_key)
Mapping value check fails raise:
Error('value', current_key) from source_error
Callables that don’t return True raise:
Error('call', used_func, current_data)
Validators¶
Some ready-made validators exist to make your life easier:
-
range(a, b=None, left=True, right=True)¶ Check whether the value is between the lower and upper bounds.
Parameters: >>> figure = range(5.5, left = False) # (0, 5.5] >>> check(figure, 0) # fail, not included
-
contain(store, white=True)¶ Check the membership of the value against the store.
Parameters: - store (collections.abc.Container) – The store.
- white (bool) – Whether to check for presence or absence.
>>> import string >>> figure = contain(string.ascii_lowercase) >>> check(figure, 'H') # fail, capital
Examples¶
All snippets assume:
import shucks as s
Basic¶
Check if the data is a int instance:
int
Check if the data is 4.5:
4.5
Check if the data is 4.5 or 8:
s.Or(4.5, 8)
Check if the data is a str instance and 'hello':
s.And(str, 'hello')
Check if the data is not str and 'hello':
s.Not(s.And(str, 'hello'))
Check if the data is str and casting to abs does not raise
TypeError:
s.And(str, s.Exc(abs, TypeError))
Check if the length of the data is 10:
s.Con(len, 10)
Check if the data is between incl 1 and excl 10:
s.range(1, 10, right = False)
Check if the data is a str instance and its length between incl
1 and incl 10:
s.And(str, s.Con(len, s.range(1, 10)))
Check further only if str:
s.If(str, s.Con(len, s.range(1, 10)))
Check if 10 characters when str, or convert and check otherwise.
_c_lr = s.Con(len, s.range(1, 10))
s.If(str, _c_lr, s.Con(str, _c_lr))
Raise an error upon failure:
s.wrap(s.Con(len, s.range(1, 10)), 'phone', 'no more than 10 digits required')
Check if the data is even:
even = lambda data: not data % 2
Note
Refer to the end of Errors for info about failing callables.
Array¶
Check if the data has two values, both being 0 or 1:
[s.Or(0, 1), s.Or(0, 1)]
Check if the data has at least one value of 0 or 1:
[s.Or(0, 1), ...]
Check if the data has any amount of 0 or 1:
s.Or([], [s.Or(0, 1), ...])
Tip
Since our schema will probably remain static, using tuple instead
of list would have the same result and save us some memory.
Check if the data is a list instance with any amount of 0
or 1:
s.And(list, s.Or((), (s.Or(0, 1), ...)))
Note
To enable auto type checks, simply pass auto = True to check().
Check if the data has at least one of 0 or 1, followed by
one value of 2:
[s.Or(0, 1), ..., 2]
Check if the data is empty or has at at least one of 0 or
1, followed by at least one value of 2:
s.Or([], [s.Or(0, 1), ..., 2, ...])
Warning
This does not check if the data has any amount of 0 or 1
followed by any amount of 2
Tip
To get the same effect as with s.Or([], [s.Or(0, 1), ...]), checking
for any amount for both s.Or(0, 1) and 2, have to do:
s.Or([], [s.Or(0, 1), ...], [s.Or(0, 1), ..., 2, ...])
Or offers a way to eliminate this redundancy, the
above is equal to:
s.Or([s.Or(0, 1), ..., 2, ...], any = True)
Nothing else other than one array should be used with with the any option.
To emulate using more arguments for Or with
any, nest them:
s.Or(list, s.Or([s.Or(0, 1), ..., 2, ...], any = True))
Check if the data is a tuple or list instance, with any
amount of 0 or 1 followed by any amount of 2 and
ending with a 3:
s.And(s.Or(tuple, list), s.Or((), s.Or((s.Or(0, 1), ..., 2, ..., 3), any = True)))
Check if every line in a file starts with _:
def read(name):
with open(name) as file:
data = file.read().splitlines()
return data
s.And(
str,
s.Con(
read,
s.wrap(
s.Or((lambda line: line.startswith('_'), ...), any = True),
'missing _'
)
)
)
Mapping¶
Check if the data has at least one foo key against bar:
{'foo': 'bar'}
Check if the data has at most one foo key against bar:
{s.Opt('foo'): 'bar'}
Note
The whole “at least” and “at most” terminology might be confusing in this section.
Consider “required” and “optional” respectively.
Check if the data has a required tic key against tac or
toe:
{'tic': s.Or('tac', 'toe')}
Warning
Using Ops or Sigs other
than Opt for keys will lead to unexpected
behaviour.
Contained data is __getitem__’d from the root data, not checked
sequentially for existence.
Tip
To better validate key-value pairs, do:
s.Con(dict.items, (('key', 'value'), ...))
Extra¶
Consider the follow classes:
class MyClass0:
def __init__(self, v):
self.v = v
class MyClass1:
def __init__(self, n):
self.n = n
Create functions (d_) that conditionally return checkers (c_):
def c_0(figure, obj):
shucks.check(int, obj.v)
def c_0(figure, obj):
shucks.check(str, obj.n)
def d_0(figure):
if isinstance(figure, MyClass0):
return c_0
if isinstance(figure, MyClass1):
return c_1
determinators = [d_0]
Now simply use extra = determinators when your data includes objects of
such classes.
Note
extra accepts a list for adding and removing determinators
accordingly.
Practices¶
There’s a few things you can do to make validation more understandable.
For example, when raising Error, either manually or
through wrap(), you may want to include some sort of “code”
as the first argument, and then assets that can be used to form an explanation,
rather than the explanation itself.
So, having these:
primes = {11, 13, 17, 19, 23, 29, 31, 37}
length = 3
subfig = s.Con(primes.intersection, s.Con(len, 3))
So instead of doing something like this:
s.wrap(subfig, '3 prime numbers over 10 and under 40 required')
Strive to do something like this:
s.wrap(subfig, 'primes', primes, length)
When this fails, the user will programatically be able to handle the error and make a more educated deduction about what went wrong, and better yet, how to fix it.
Another good practice that’s already visible is wrap()ing
all Cons, so as to better communicate the
transformations that occurred during validation.
For example, consider the following figure:
s.And(int, s.Con(str, s.Con(len, s.range(8, 64))))
The ideal data should be an int, and the innermost check that happens
is a range(). However, that range is for the amount of
digits the number has, not the number itself.
Not knowing the conversions that took place can lead to a very confusing error
if this were to be checked against, let’s say, 42 as it’s definitely
between 8 and 64, but an error is raised saying that it’s not.
So a better way of formulating this figure would be:
s.And(int, s.wrap(s.Con(str, s.Con(len, s.range(8, 64))), 'digit amount'))
Notice how not both Cons were
wrap()ed, as they describe consecutive conversions that
cannot easily be described separately.