Validator
What are validator?
Validator is a functionnality that allow you to call a function to determine if an option is valid.
To define validator we have to use Calculation object.
The function have to raise a ValueError object if the value is not valid. It could emit a warning when raises a ValueWarning.
Validator with options
Here an example, where we want to ask a new password to an user. This password should not be weak. The password will be asked twice and must match.
First of all, import necessary object:
1from tiramisu import StrOption, IntOption, OptionDescription, Config, \
2 Calculation, Params, ParamOption, ParamSelfOption, ParamValue
3from tiramisu.error import ValueWarning
4import warnings
5from re import match
Create a first function to valid that the password is not weak:
1# Creation differents function
2def is_password_conform(password):
3 # password must containe at least a number, a lowercase letter, an uppercase letter and a symbol
4 if not match(r'(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)', password):
5 raise ValueError('please choose a stronger password, try a mix of letters, numbers and symbols')
Secondly create a function to valid password length. The password must be longer than the value of min_len and should be longer than the value of recommand_len.
In first case, function raise ValueError, this value is incorrect.
In second case, function raise ValueWarning, the value is valid but discouraged:
1# Password must have at least min_len characters
2def password_correct_len(min_len, recommand_len, password):
3 if len(password) < min_len:
4 raise ValueError(f'use {min_len} characters or more for your password')
5 # password should have at least recommand_len characters
6 if len(password) < recommand_len:
7 raise ValueWarning(f'it would be better to use more than {recommand_len} characters for your password')
Thirdly create a function that verify that the login name is not a part of password (password foo2aZ$ if not valid for user foo):
1def user_not_in_password(login, password):
2 if login in password:
3 raise ValueError('the login must not be part of the password')
Now we can creation an option to ask user login:
1# Create first option to ask user's login
2login = StrOption('login', 'Login', properties=('mandatory',))
Create a calculation to launch is_password_conform. This function will be use in a new option and must validate this new option. So we use the object ParamSelfOption has parameter to retrieve the value of current option:
1# Creation calculatin for first password
2calc1 = Calculation(is_password_conform,
3 Params(ParamSelfOption()))
Create a second calculation to launch password_correct_len function. We want set 8 as min_len value and 12 as recommand_len value:
1calc2 = Calculation(password_correct_len,
2 Params((ParamValue(8),
3 ParamValue(12),
4 ParamSelfOption())))
Create a third calculation to launch user_not_in_password function. For this function, we use keyword argument. This function normaly raise ValueError but in this case we want demoting this error as a simple warning. So we add warnings_only parameter:
1calc3 = Calculation(user_not_in_password,
2 Params(kwargs={'login': ParamOption(login),
3 'password': ParamSelfOption()}),
4 warnings_only=True)
So now we can create first password option that use those calculations:
1# Create second option to ask user's password
2password1 = StrOption('password1',
3 'Password',
4 properties=('mandatory',),
5 validators=[calc1, calc2, calc3])
A new function is created to conform that password1 and password2 match:
1def password_match(password1, password2):
2 if password1 != password2:
3 raise ValueError("those passwords didn't match, try again")
And now we can create second password option that use this function:
1# Create third option to confirm user's password
2password2 = StrOption('password2',
3 'Confirm',
4 properties=('mandatory',),
5 validators=[Calculation(password_match, Params((ParamOption(password1), ParamSelfOption())))])
Finally we create optiondescription and config:
1# Creation optiondescription and config
2od = OptionDescription('password', 'Define your password', [password1, password2])
3root = OptionDescription('root', '', [login, od])
4config = Config(root)
5config.property.read_write()
Now we can test this Config:
1# no number and no symbol (with prefix)
2config.option('login').value.set('user')
3try:
4 config.option('password.password1').value.set('aAbBc')
5except ValueError as err:
6 print(f'Error: {err}')
The tested password is too weak, so value is not set.
The error is: Error: "aAbBc" is an invalid string for "Password", please choose a stronger password, try a mix of letters, numbers and symbols.
The password is part of error message. In this case it’s a bad idea. So we have to remove prefix to the error message:
1# no number and no symbol
2config.option('login').value.set('user')
3try:
4 config.option('password.password1').value.set('aAbBc')
5except ValueError as err:
6 err.prefix = ''
7 print(f'Error: {err}')
Now the error is: Error: please choose a stronger password, try a mix of letters, numbers and symbols.
Let’s try with a password not weak but too short:
1# too short password
2config.option('login').value.set('user')
3try:
4 config.option('password.password1').value.set('aZ$1')
5except ValueError as err:
6 err.prefix = ''
7 print(f'Error: {err}')
The error is: Error: use 8 characters or more for your password.
Now try a password with 8 characters:
1# warnings too short password
2warnings.simplefilter('always', ValueWarning)
3config.option('login').value.set('user')
4with warnings.catch_warnings(record=True) as warn:
5 config.option('password.password1').value.set('aZ$1bN:2')
6 if warn:
7 warn[0].message.prefix = ''
8 print(f'Warning: {warn[0].message}')
9 password = config.option('password.password1').value.get()
10print(f'The password is "{password}"')
Warning is display but password is store:
Warning: it would be better to use more than 12 characters for your password
The password is "aZ$1bN:2"
Try a password with the login as part of it:
1# password with login
2warnings.simplefilter('always', ValueWarning)
3config.option('login').value.set('user')
4with warnings.catch_warnings(record=True) as warn:
5 config.option('password.password1').value.set('aZ$1bN:2u@1Bjuser')
6 if warn:
7 warn[0].message.prefix = ''
8 print(f'Warning: {warn[0].message}')
9 password = config.option('password.password1').value.get()
10print(f'The password is "{password}"')
Warning is display but password is store:
Warning: the login must not be part of the password
The password is "aZ$1bN:2u@1Bjuser"
Now try with a valid password but that doesn’t match:
1# password1 not matching password2
2config.option('login').value.set('user')
3config.option('password.password1').value.set('aZ$1bN:2u@1Bj')
4try:
5 config.option('password.password2').value.set('aZ$1aaaa')
6except ValueError as err:
7 err.prefix = ''
8 print(f'Error: {err}')
An error is displayed: Error: those passwords didn't match, try again.
Finally try a valid password:
1# and finaly passwod match
2config.option('login').value.set('user')
3config.option('password.password1').value.set('aZ$1bN:2u@1Bj')
4config.option('password.password2').value.set('aZ$1bN:2u@1Bj')
5config.property.read_only()
6user_login = config.option('login').value.get()
7password = config.option('password.password2').value.get()
8print(f'The password for "{user_login}" is "{password}"')
As expected, we have The password for "user" is "aZ$1bN:2u@1Bj".
Validator with a multi option
Assume we ask percentage value to an user. The sum of values mustn’t be higher than 100% and shouldn’t be lower than 100%.
Let’s start by importing the objects:
1from tiramisu import IntOption, OptionDescription, Config, \
2 Calculation, Params, ParamSelfOption
3from tiramisu.error import ValueWarning
4import warnings
Continue by writing the validation function:
1def valid_pourcent(option):
2 total = sum(option)
3 if total > 100:
4 raise ValueError(f'the total {total}% is bigger than 100%')
5 if total < 100:
6 raise ValueWarning(f'the total {total}% is lower than 100%')
And create a simple config:
1percent = IntOption('percent',
2 'Percent',
3 multi=True,
4 validators=[Calculation(valid_pourcent, Params(ParamSelfOption()))])
5config = Config(OptionDescription('root', 'root', [percent]))
Now try with bigger sum:
1# too big
2try:
3 config.option('percent').value.set([20, 90])
4except ValueError as err:
5 err.prefix = ''
6 print(f'Error: {err}')
7percent_value = config.option('percent').value.get()
8print(f'The value is "{percent_value}"')
The result is:
Error: the total 110% is bigger than 100%
The value is "[]"
Let’s try with lower sum:
1# too short
2warnings.simplefilter('always', ValueWarning)
3with warnings.catch_warnings(record=True) as warn:
4 config.option('percent').value.set([20, 70])
5 if warn:
6 warn[0].message.prefix = ''
7 print(f'Warning: {warn[0].message}')
8 percent_value = config.option('percent').value.get()
9print(f'The value is "{percent_value}"')
The result is:
Warning: the total 90% is lower than 100%
The value is "[20, 70]"
Finally with correct value:
1# correct
2config.option('percent').value.set([20, 80])
3percent_value = config.option('percent').value.get()
4print(f'The value is "{percent_value}"')
The result is:
The value is "[20, 80]"
Validator with a follower option
Assume we want distribute something to differents users. The sum of values mustn’t be higher than 100%.
First, import all needed objects:
1from tiramisu import StrOption, IntOption, Leadership, OptionDescription, Config, \
2 Calculation, Params, ParamSelfOption, ParamIndex
3from tiramisu.error import ValueWarning
4import warnings
Let’s start to write a function with three arguments:
the first argument will have all values set for the follower
the second argument will have only last value set for the follower
the third argument will have the index
1def valid_pourcent(option, current_option, index):
2 if None in option:
3 return
4 total = sum(option)
5 if total > 100:
6 raise ValueError(f'the value {current_option} (at index {index}) is too big, the total is {total}%')
Continue by creating a calculation:
1calculation = Calculation(valid_pourcent, Params((ParamSelfOption(whole=True),
2 ParamSelfOption(),
3 ParamIndex())))
And instanciate differents option and config:
1user = StrOption('user', 'User', multi=True)
2percent = IntOption('percent',
3 'Distribution',
4 multi=True,
5 validators=[calculation])
6od = Leadership('percent', 'Percent', [user, percent])
7config = Config(OptionDescription('root', 'root', [od]))
Add two value to the leader:
1config.option('percent.user').value.set(['user1', 'user2'])
The user user1 will have 20%:
1config.option('percent.percent', 0).value.set(20)
If we try to set 90% to user2:
1# too big
2try:
3 config.option('percent.percent', 1).value.set(90)
4except ValueError as err:
5 err.prefix = ''
6 print(f'Error: {err}')
This error occured: Error: the value 90 (at index 1) is too big, the total is 110%
No problem with 80%:
1# correct
2config.option('percent.percent', 1).value.set(80)