Example 3 - t
is a custom tuple¶
For example t
is a tuple that contains a float between 0 and 1 and a lowercase trigram (three-letter lowercase string).
1- Example values to validate¶
# Valid
t = (0.2, 'foo')
t = (1.0, 'abc')
t = (0, 'foo') # we accept integers
# Invalid
t = 1.1 # wrong type (not a tuple)
t = (0.2, 'foo', True) # wrong size (size 3)
t = ('1.0', 'foo') # wrong type ('1.0' is not a float)
t = (1.1, 'foo') # wrong value (1.1 is not between 0 and 1)
t = (1.0, False) # wrong type (False is not a string)
t = (0.5, 'a') # wrong value ('a' is not of size 3)
t = (0.5, 'AAA') # wrong value ('AAA' is not lowercase)
2- Inline validation¶
validate
+ built-ins¶
validate
can not perform everything at once without a custom validation function, but you can easily validate each element:
from valid8 import validate
validate('t', t, instance_of=tuple, length=2)
validate('t[0]', t[0], instance_of=Real, min_value=0, max_value=1)
validate('t[1]', t[1], instance_of=str, length=3, custom=str.islower)
Note that we prefer to user custom=str.islower
to check if a string is lowercase, rather than equals=s.lower()
, as explained in previous example 2.
with validator
+ built-ins¶
It is relatively straightforward to validate both t
and its contents
- either with a pure "boolean test" approach (where we use
instance_of
instead ofisinstance
so thatvalid8
can distinguish betweenTypeError
andValueError
):
from valid8 import validator, instance_of
with validator('t', t, instance_of=tuple) as v:
v.alid = len(t) == 2\
and instance_of(t[0], Real) and (0 <= t[0] <= 1)\
and instance_of(t[1], str) and len(t[1]) == 3 and t[1].islower()
- or with a "failure raising" approach, less compact but with more explicit error messages:
from valid8 import validation
with validation('t', t, instance_of=tuple):
# the tuple should be of length 2
if len(t) != 2:
raise ValueError('tuple length should be 2, found ' + str(t))
# the first element is a float between 0 and 1
if not isinstance(t[0], Real):
raise TypeError('first elt should be a Real, found ' + str(t[0]))
if not (0 <= t[0] <= 1):
raise ValueError('first elt should be between 0 and 1,found ' + str(t[0]))
# the second element is a lowercase string of size 3
if not isinstance(t[1], str):
raise TypeError('second elt should be a string, found ' + str(t[1]))
if not (len(t[1]) == 3 and t[1].islower()):
raise ValueError('second elt should be a lowercase string of length 3,'
'found ' + str(t[1]))
3- Functions/classes validation¶
Principles¶
- Type can be checked with built-in
instance_of
- Length can be checked with built-in
has_length
or mini-lambdaLen(t) == 2
or custom functions - There is no built-in function to check that
s
is lowercase yet, but as we saw above we can use the unbound class functionstr.islower
or the mini-lambdas.islower()
.
We will not show all combinations here (please refer to example 1 for all kind of decorators), only a few examples:
Function input¶
With pure built-in and stdlib. Note: we have to use on_each_
to map validation functions to each element of the tuple:
from valid8 import validate_arg, instance_of, has_length, on_each_, and_, between
@validate_arg('t', instance_of(tuple), has_length(2), on_each_(
# the first element is a float between 0 and 1
and_(instance_of(Real), between(0, 1)),
# the 2d element is a lowercase string of len 3
and_(instance_of(str), has_length(3), str.islower),
))
def my_function(t):
pass
With pure mini-lambda. Note that mini-lambda allows you to directly access the inner elements inside the tuple with traditional indexing:
from mini_lambda import InputVar, Len
from valid8 import validate_arg, instance_of
from valid8.validation_lib.mini_lambda import Instance_of
# we need a mini_lambda variable named 't'
t = InputVar('t', tuple)
@validate_arg('t', instance_of(tuple), Len(t) == 2,
# the first element is a float between 0 and 1
Instance_of(t[0], Real), (0 <= t[0]) & (t[0] <= 1),
# the 2d element is a lowercase string of len 3
Instance_of(t[1], str), Len(t[1]) == 3, t[1].islower()
)
def my_function(t):
pass
Also note that here as in all previous examples we want to use instance_of
instead of isinstance
in order to raise the correct TypeError
. But instance_of
is a standard function, it is not composable with mini-lambda expressions such as t[0]
. Therefore we use the mini-lambda version of instance_of
provided in this package, named Instance_of
. This allows us to write type tests for sub-elements in the tuple, for example Instance_of(t[0], Real)
.
Class fields¶
In the examples below the class fields are defined as constructor arguments but this also works if they are defined as class descriptors/properties, and is compliant with autoclass and attrs
with pure built-in and stdlib:
from valid8 import validate_field,
from valid8 import instance_of, has_length, on_each_, and_, between
@validate_field('t', instance_of(tuple), has_length(2), on_each_(
# the first element is a float between 0 and 1
and_(instance_of(Real), between(0, 1)),
# the 2d element is a lowercase string of len 3
and_(instance_of(str), has_length(3), str.islower),
))
class Foo:
def __init__(self, t):
self.t = t
or with pure mini-lambda
from mini_lambda import InputVar, Len
from valid8 import validate_field, instance_of
from valid8.validation_lib.mini_lambda import Instance_of
# we need a mini_lambda variable named 't'
t = InputVar('t', tuple)
@validate_field('t', instance_of(tuple), Len(t) == 2,
# the first element is a float between 0 and 1
Instance_of(t[0], Real), (0 <= t[0]) & (t[0] <= 1),
# the 2d element is a lowercase string of len 3
Instance_of(t[1], str), Len(t[1]) == 3, t[1].islower()
)
class Foo:
def __init__(self, t):
self.t = t
With PEP484¶
In the above code samples the type was checked by instance_of(str)
in the decorator, but you could also rather declare a type hint in the function/class signature and rely on a PEP484 checker library, that makes the decorator much more compact and focused on value validation only:
from typing import Tuple
from pytypes import typechecked
from mini_lambda import InputVar, Len
from valid8 import validate_arg
# we need a mini_lambda variable named 't'
t = InputVar('t', tuple)
@typechecked
@validate_arg('t', # the first element is a float between 0 and 1
(0 <= t[0]) & (t[0] <= 1),
# the 2d element is a lowercase string of len 3
Len(t[1]) == 3, t[1].islower()
)
def my_function(t: Tuple[Real, str]):
pass
However note that in this particular case, a tuple of wrong size will appear as a TypeError
instead of a ValueError
as we obtained previously.
4- Much easier: custom function¶
The examples above demonstrate that even if you can validate complex types directly, at some point you will naturally want to create a dedicated custom validation function for each complex type to validate. Thus separating the validation means (the custom function) from the validation intent (naming, context, error message customization that you provide to validate
or validator
/validation
). That's exactly what valid8
is meant for!
Let's start by creating a custom base validation function. The only constraint is to return True
or nothing/None
in case of success, so we can implement
- a simple boolean checker. Note that we use
instance_of
instead ofisinstance
so thatvalid8
can distinguish betweenTypeError
andValueError
:
from valid8 import instance_of
def is_valid_tuple(t):
""" custom function - 'boolean tester' style (returning a bool) """
return instance_of(t, tuple) and len(t) == 2\
and instance_of(t[0], Real) and (0 <= t[0] <= 1)\
and instance_of(t[1], str) and len(t[1]) == 3 and t[1].islower()
- or, better for our users (and even for our debug sessions), a failure raiser. In this case we do not need to use
instance_of
since we raiseTypeError
andValueError
explicitly:
def check_valid_tuple(t):
""" custom function - 'failure raiser' style (returning nothing) """
# item should be a tuple of length 2
if not isinstance(t, tuple):
raise TypeError('item should be a tuple')
if len(t) != 2:
raise ValueError('tuple length should be 2, found ' + str(t))
# the first element is a float between 0 and 1
if not isinstance(t[0], Real):
raise TypeError('first elt should be a Real, found ' + str(t[0]))
if not (0 <= t[0] <= 1):
raise ValueError('first elt should be between 0 and 1,found ' + str(t[0]))
# the second element is a lowercase string of size 3
if not isinstance(t[1], str):
raise TypeError('second elt should be a string, found ' + str(t[1]))
if not (len(t[1]) == 3 and t[1].islower()):
raise ValueError('second elt should be a lowercase string of length 3,'
'found ' + str(t[1]))
We can then use either function with the valid8
tools.
validate
¶
With is_valid_tuple
(boolean tester):
from valid8 import validate
validate('t', t, custom=is_valid_tuple)
or with check_valid_tuple
(failure raiser):
from valid8 import validate
validate('t', t, custom=check_valid_tuple)
It is exactly the same usage.
validation
/ validator
¶
With is_valid_tuple
(boolean tester) we need to return the flag in the context manager, so it is more natural to use the validator
alias:
from valid8 import validator
with validator('t', t) as v:
v.alid = is_valid_tuple(t)
whereas with check_valid_tuple
(failure raiser) we do not need to return anything, and the validation
alias seems more natural:
from valid8 import validation
with validation('t', t):
check_valid_tuple(t)
function inputs¶
With is_valid_tuple
(boolean tester):
from valid8 import validate_arg
@validate_arg('t', is_valid_tuple)
def my_function(t):
pass
or with check_valid_tuple
(failure raiser):
from valid8 import validate_arg
@validate_arg('t', check_valid_tuple)
def my_function(t):
pass
it is exactly the same usage
classes fields¶
With is_valid_tuple
(boolean tester):
from valid8 import validate_field
@validate_field('t', is_valid_tuple)
class Foo:
def __init__(self, t):
self.t = t
or with check_valid_tuple
(failure raiser):
from valid8 import validate_field
@validate_field('t', check_valid_tuple)
class Foo:
def __init__(self, t):
self.t = t
it is exactly the same usage
more compact with pep484¶
Note that if you choose to use a PEP484 type checker such as pytypes, your custom validation function can be much more compact, as the tuple length and tuple elements types can be already described in the PEP484 type hint Tuple[Real, str]
:
from typing import Tuple
from pytypes import typechecked
@typechecked
def is_valid_tuple_pep(t: Tuple[Real, str]):
""" custom validation function - note the PEP484 type hint above """
return len(t) == 2 and (0 <= t[0] <= 1) and len(t[1]) == 3 and t[1].islower()
or
from typing import Tuple
from pytypes import typechecked
@typechecked
def check_valid_tuple_pep(t: Tuple[Real, str]):
""" custom validation function - note the PEP484 type hint above """
# the first element is a float between 0 and 1
if not (0 <= t[0] <= 1):
raise ValueError('first elt should be between 0 and 1,found ' + str(t[0]))
# the second element is a lowercase string of size 3
if not (len(t[1]) == 3 and t[1].islower()):
raise ValueError('second elt should be a lowercase string of length 3,'
'found ' + str(t[1]))
However note that in this particular case, a tuple of wrong size will appear as a TypeError
instead of a ValueError
as we obtained previously.
4- Variants¶
TODO