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:
Op
Represents the
OR
operator.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
12
is greater than9
.
-
class
And
¶ Bases:
Op
Represents the
AND
operator.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
12
is 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
NOT
operator.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
figure
and should return :var:`.nil` or a figure.
-
wrap
(figure, *parts, **kwargs)¶ Use
parts
when an error is raised against thisfigure
.kwargs
gets 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 Op
s or Sig
s 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 Con
s, 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 Con
s were
wrap()
ed, as they describe consecutive conversions that
cannot easily be described separately.