_validation, _second_level_validation => validation, second_level_validation

This commit is contained in:
Emmanuel Garette 2019-11-19 08:26:42 +01:00
parent 5c3a133928
commit 74e604478e
15 changed files with 316 additions and 269 deletions

View File

@ -30,9 +30,7 @@ class BoolOption(Option):
_type = 'boolean'
_display_name = _('boolean')
def _validate(self,
value: bool,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
def validate(self,
value: bool) -> None:
if not isinstance(value, bool):
raise ValueError()

View File

@ -31,10 +31,8 @@ class BroadcastOption(Option):
_type = 'broadcast_address'
_display_name = _('broadcast address')
def _validate(self,
value: str,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
def validate(self,
value: str) -> None:
if not isinstance(value, str):
raise ValueError(_('invalid string'))
if value.count('.') != 3:

View File

@ -18,12 +18,12 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
from types import FunctionType
from typing import Any
from ..setting import undefined
from ..setting import undefined, OptionBag
from ..i18n import _
from .option import Option
from ..autolib import carry_out_calculation, Calculation
from ..autolib import Calculation
from ..error import ConfigError, display_list
@ -40,12 +40,8 @@ class ChoiceOption(Option):
name,
doc,
values,
default=None,
default_multi=None,
multi=False,
validators=None,
properties=None,
warnings_only=False):
*args,
**kwargs):
"""
:param values: is a list of values the option can possibly take
@ -56,12 +52,8 @@ class ChoiceOption(Option):
self._choice_values = values
super(ChoiceOption, self).__init__(name,
doc,
default=default,
default_multi=default_multi,
multi=multi,
validators=validators,
properties=properties,
warnings_only=warnings_only)
*args,
**kwargs)
def impl_get_values(self,
option_bag):
@ -74,15 +66,17 @@ class ChoiceOption(Option):
values = self._choice_values
return values
def _validate(self,
value,
option_bag,
current_opt=undefined):
def validate(self,
value: Any) -> None:
pass
def validate_with_option(self,
value: Any,
option_bag: OptionBag) -> None:
values = self.impl_get_values(option_bag)
if values is not undefined and value not in values:
if len(values) == 1:
raise ValueError(_('only "{0}" is allowed'
'').format(values[0]))
else:
raise ValueError(_('only {0} are allowed'
'').format(display_list(values, add_quote=True)))

View File

@ -22,20 +22,17 @@ from datetime import datetime
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _
from .option import Option
from .stroption import StrOption
class DateOption(Option):
class DateOption(StrOption):
__slots__ = tuple()
_type = 'date'
_display_name = _('date')
def _validate(self,
value: str,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
if not isinstance(value, str):
raise ValueError(_('invalid string'))
def validate(self,
value: str) -> None:
super().validate(value)
try:
datetime.strptime(value, "%Y-%m-%d")
except ValueError:

View File

@ -19,14 +19,18 @@
# the whole pypy projet is under MIT licence
# ____________________________________________________________
import re
from ipaddress import ip_address
from ..setting import undefined, Undefined, OptionBag
from ipaddress import ip_interface
from typing import Any, Optional, List
from ..i18n import _
from .option import Option
from ..setting import undefined
from .ipoption import IPOption
from .stroption import StrOption
from .networkoption import NetworkOption
from .option import Calculation
class DomainnameOption(IPOption):
class DomainnameOption(StrOption):
"""represents the choice of a domain name
netbios: for MS domain
hostname: to identify the device
@ -38,53 +42,63 @@ class DomainnameOption(IPOption):
_display_name = _('domain name')
def __init__(self,
name,
doc,
default=None,
default_multi=None,
name: str,
doc: str,
default: Any=undefined,
default_multi: Any=None,
multi: bool=False,
validators=None,
properties=None,
allow_ip: bool=False,
cidr: bool=False,
type_: str='domainname',
validators: Optional[List[Calculation]]=None,
properties: Optional[List[str]]=None,
warnings_only: bool=False,
allow_without_dot=False) -> None:
allow_ip: bool=False,
allow_network: bool=False,
network_cidr: bool=False,
type: str='domainname',
allow_without_dot: bool=False,
allow_startswith_dot: bool=False) -> None:
if type_ not in ['netbios', 'hostname', 'domainname']:
raise ValueError(_('unknown type_ {0} for hostname').format(type_))
extra = {'_dom_type': type_}
if allow_ip not in [True, False]:
if type not in ['netbios', 'hostname', 'domainname']:
raise ValueError(_('unknown type {0} for hostname').format(type))
extra = {'_dom_type': type}
if not isinstance(allow_ip, bool):
raise ValueError(_('allow_ip must be a boolean'))
if allow_without_dot not in [True, False]:
if not isinstance(allow_network, bool):
raise ValueError(_('allow_network must be a boolean'))
if not isinstance(allow_without_dot, bool):
raise ValueError(_('allow_without_dot must be a boolean'))
extra['_allow_ip'] = allow_ip
if not isinstance(allow_startswith_dot, bool):
raise ValueError(_('allow_startswith_dot must be a boolean'))
extra['_allow_without_dot'] = allow_without_dot
if type_ == 'domainname':
if type == 'domainname':
if allow_without_dot:
min_time = 0
else:
min_time = 1
regexp = r'((?!-)[a-z0-9-]{{{1},{0}}}\.){{{1},}}[a-z0-9-]{{1,{0}}}'.format(self._get_len(type_), min_time)
msg = _('only lowercase, number, "-" and "." are characters are allowed')
msg_warning = _('only lowercase, number, "-" and "." are characters are recommanded')
regexp = r'((?!-)[a-z0-9-]{{{1},{0}}}\.){{{1},}}[a-z0-9-]{{1,{0}}}'.format(self._get_len(type), min_time)
msg = _('only lowercase, number, "-" and "." characters are allowed')
msg_warning = _('only lowercase, number, "-" and "." characters are recommanded')
else:
regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type_))
msg = _('only lowercase, number and - are characters are allowed')
msg_warning = _('only lowercase, number and "-" are characters are recommanded')
regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type))
msg = _('only lowercase, number and "-" characters are allowed')
msg_warning = _('only lowercase, number and "-" characters are recommanded')
if allow_ip:
msg = _('could be a IP, otherwise {}').format(msg)
msg_warning = _('could be a IP, otherwise {}').format(msg_warning)
if not cidr:
regexp = r'^(?:{0}|(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){{3}}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))$'.format(regexp)
else:
regexp = r'^(?:{0}|(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){{3}}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/[0-9][0-9]))$'.format(regexp)
else:
regexp = r'^{0}$'.format(regexp)
extra['_domain_re'] = re.compile(regexp)
extra['_domain_re_message'] = msg
extra['_domain_re_message_warning'] = msg_warning
extra['_has_upper'] = re.compile('[A-Z]')
if allow_ip:
extra['_ip'] = IPOption(name,
doc)
extra['_allow_ip'] = allow_ip
if allow_network:
extra['_network'] = NetworkOption(name,
doc,
cidr=network_cidr)
extra['_allow_network'] = allow_network
extra['_allow_startswith_dot'] = allow_startswith_dot
super().__init__(name,
doc,
@ -94,19 +108,16 @@ class DomainnameOption(IPOption):
validators=validators,
properties=properties,
warnings_only=warnings_only,
cidr=cidr,
_extra=extra)
extra=extra)
def _get_len(self, type_):
if type_ == 'netbios':
def _get_len(self, type):
if type == 'netbios':
return 15
else:
return 63
def _validate(self,
value: str,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
def _validate_domain(self,
value: str) -> None:
def _valid_length(val):
if len(val) < 1:
raise ValueError(_("invalid length (min 1)"))
@ -114,33 +125,83 @@ class DomainnameOption(IPOption):
raise ValueError(_("invalid length (max {0})"
"").format(part_name_length))
if not isinstance(value, str):
raise ValueError(_('invalid string'))
try:
ip_address(value)
except ValueError:
pass
else:
if self.impl_get_extra('_allow_ip') is False:
raise ValueError(_('must not be an IP'))
# it's an IP so validate with IPOption
return super()._validate(value, option_bag, current_opt)
part_name_length = self._get_len(self.impl_get_extra('_dom_type'))
if self.impl_get_extra('_dom_type') == 'domainname':
if not self.impl_get_extra('_allow_without_dot') and not "." in value:
raise ValueError(_("must have dot"))
if len(value) > 255:
raise ValueError(_("invalid length (max 255)"))
for dom in value.split('.'):
if self.impl_get_extra('_allow_startswith_dot') and value.startswith('.'):
val = value[1:]
else:
val = value
for dom in val.split('.'):
_valid_length(dom)
else:
_valid_length(value)
def _second_level_validation(self, value, warnings_only):
def _validate_ip_network(self,
value: str) -> None:
allow_ip = self.impl_get_extra('_allow_ip')
allow_network = self.impl_get_extra('_allow_network')
if allow_ip is False and allow_network is False:
raise ValueError(_('must not be an IP'))
if allow_ip is True:
try:
self.impl_get_extra('_ip').validate(value)
return
except ValueError as err:
if allow_network is False:
raise err
if allow_network is True:
self.impl_get_extra('_network').validate(value)
def validate(self,
value: str) -> None:
super().validate(value)
try:
# check if it's an IP or network
ip_interface(value)
except ValueError:
self._validate_domain(value)
else:
self._validate_ip_network(value)
def _second_level_validation_domain(self,
value: str,
warnings_only: bool) -> None:
if self.impl_get_extra('_has_upper').search(value):
raise ValueError(_('some characters are uppercase'))
if not self.impl_get_extra('_domain_re').search(value):
if warnings_only:
raise ValueError(self.impl_get_extra('_domain_re_message_warning'))
else:
raise ValueError(self.impl_get_extra('_domain_re_message'))
def _second_level_validation_ip_network(self,
value: str,
warnings_only: bool) -> None:
allow_ip = self.impl_get_extra('_allow_ip')
allow_network = self.impl_get_extra('_allow_network')
# it's an IP so validate with IPOption
if allow_ip is False and allow_network is False:
raise ValueError(_('must not be an IP'))
if allow_ip is True:
try:
self.impl_get_extra('_ip').second_level_validation(value, warnings_only)
return
except ValueError as err:
if allow_network is False:
raise err
if allow_network is True:
self.impl_get_extra('_network').second_level_validation(value, warnings_only)
def second_level_validation(self,
value: str,
warnings_only: bool) -> None:
try:
# check if it's an IP or network
ip_interface(value)
except ValueError:
self._second_level_validation_domain(value, warnings_only)
else:
self._second_level_validation_ip_network(value, warnings_only)

View File

@ -26,7 +26,6 @@ from .stroption import RegexpOption
class EmailOption(RegexpOption):
__slots__ = tuple()
#https://www.w3.org/TR/html-markup/input.email.html#input.email.attrs.value.single.
_regexp = re.compile(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
_type = 'email'
_display_name = _('email address')

View File

@ -30,9 +30,7 @@ class FloatOption(Option):
_type = 'float'
_display_name = _('float')
def _validate(self,
value: float,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
def validate(self,
value: float) -> None:
if not isinstance(value, float):
raise ValueError()

View File

@ -31,7 +31,6 @@ class IntOption(Option):
_display_name = _('integer')
def __init__(self,
name,
*args,
min_number=None,
max_number=None,
@ -41,18 +40,27 @@ class IntOption(Option):
extra['min_number'] = min_number
if max_number is not None:
extra['max_number'] = max_number
super().__init__(name, extra=extra, *args, **kwargs)
super().__init__(*args, extra=extra, **kwargs)
def _validate(self,
value: int,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
def validate(self,
value: int) -> None:
if not isinstance(value, int):
raise ValueError()
def second_level_validation(self,
value,
warnings_only):
min_number = self.impl_get_extra('min_number')
if min_number is not None and value < min_number:
raise ValueError(_('value must be greater than "{0}"').format(min_number))
if warnings_only:
msg = 'value should be greater than "{0}"'
else:
msg = 'value must be greater than "{0}"'
raise ValueError(_(msg).format(min_number))
max_number = self.impl_get_extra('max_number')
if max_number is not None and value > max_number:
raise ValueError(_('value must be less than "{0}"').format(max_number))
if warnings_only:
msg = 'value should be less than "{0}"'
else:
msg = 'value must be less than "{0}"'
raise ValueError(_(msg).format(max_number))

View File

@ -35,69 +35,48 @@ class IPOption(StrOption):
_display_name = _('IP')
def __init__(self,
name,
doc,
default=None,
default_multi=None,
multi=False,
validators=None,
properties=None,
*args,
private_only=False,
allow_reserved=False,
warnings_only=False,
cidr=False,
_extra=None):
if _extra is None:
extra=None,
**kwargs):
if extra is None:
extra = {}
else:
extra = _extra
extra['_private_only'] = private_only
extra['_allow_reserved'] = allow_reserved
extra['_cidr'] = cidr
super().__init__(name,
doc,
default=default,
default_multi=default_multi,
multi=multi,
validators=validators,
properties=properties,
warnings_only=warnings_only,
extra=extra)
super().__init__(*args,
extra=extra,
**kwargs)
def _validate(self,
value: str,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
# sometimes an ip term starts with a zero
# but this does not fit in some case, for example bind does not like it
if not isinstance(value, str):
raise ValueError(_('invalid string'))
if value.count('.') != 3:
raise ValueError()
cidr = self.impl_get_extra('_cidr')
if cidr:
if '/' not in value:
raise ValueError(_('must use CIDR notation'))
value_ = value.split('/')[0]
else:
value_ = value
for val in value_.split('.'):
if val.startswith("0") and len(val) > 1:
raise ValueError()
# 'standard' validation
def _validate_cidr(self, value):
try:
if not cidr:
ip_address(value)
else:
ip = ip_interface(value)
except ValueError:
raise ValueError()
if cidr:
valid_ip_netmask(str(ip.ip), str(ip.netmask))
if ip.ip == ip.network.network_address:
raise ValueError(_("it's in fact a network address"))
elif ip.ip == ip.network.broadcast_address:
raise ValueError(_("it's in fact a broacast address"))
def _second_level_validation(self,
value,
warnings_only):
def _validate_ip(self, value):
try:
ip_address(value)
except ValueError:
raise ValueError()
def validate(self,
value: str) -> None:
super().validate(value)
if self.impl_get_extra('_cidr'):
self._validate_cidr(value)
else:
self._validate_ip(value)
def second_level_validation(self,
value: str,
warnings_only: bool) -> None:
ip = ip_interface(value)
if not self.impl_get_extra('_allow_reserved') and ip.is_reserved:
if warnings_only:

View File

@ -34,14 +34,9 @@ class NetmaskOption(StrOption):
_type = 'netmask'
_display_name = _('netmask address')
def _validate(self,
value: str,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
if not isinstance(value, str):
raise ValueError(_('invalid string'))
if value.count('.') != 3:
raise ValueError()
def validate(self,
value: str) -> None:
super().validate(value)
for val in value.split('.'):
if val.startswith("0") and len(val) > 1:
raise ValueError()

View File

@ -20,44 +20,28 @@
# ____________________________________________________________
from ipaddress import ip_address, ip_network
from ..setting import undefined
from ..i18n import _
from .option import Option
from .stroption import StrOption
class NetworkOption(Option):
class NetworkOption(StrOption):
"represents the choice of a network"
__slots__ = tuple()
_type = 'network'
_display_name = _('network address')
def __init__(self,
name,
doc,
default=None,
default_multi=None,
multi=False,
validators=None,
properties=None,
warnings_only=False,
cidr=False):
extra = {'_cidr': cidr}
super().__init__(name,
doc,
default=default,
default_multi=default_multi,
multi=multi,
validators=validators,
properties=properties,
warnings_only=warnings_only,
extra=extra)
def _validate(self,
value,
*args,
cidr=False,
**kwargs):
if not isinstance(value, str):
raise ValueError(_('invalid string'))
extra = {'_cidr': cidr}
super().__init__(*args,
extra=extra,
**kwargs)
def validate(self,
value: str) -> None:
super().validate(value)
if value.count('.') != 3:
raise ValueError()
cidr = self.impl_get_extra('_cidr')
@ -75,9 +59,9 @@ class NetworkOption(Option):
except ValueError:
raise ValueError()
def _second_level_validation(self,
value,
warnings_only):
def second_level_validation(self,
value: str,
warnings_only: bool) -> None:
if ip_network(value).network_address.is_reserved:
if warnings_only:
msg = _("shouldn't be reserved network")

View File

@ -30,10 +30,3 @@ class PasswordOption(StrOption):
__slots__ = tuple()
_type = 'password'
_display_name = _('password')
def _validate(self,
value: str,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
if not isinstance(value, str):
raise ValueError(_('invalid string'))

View File

@ -20,7 +20,6 @@
# ____________________________________________________________
import re
import sys
from typing import Union
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _
@ -44,19 +43,13 @@ class PortOption(StrOption):
_display_name = _('port')
def __init__(self,
name,
doc,
default=None,
default_multi=None,
multi=False,
validators=None,
properties=None,
allow_range=False,
allow_zero=False,
allow_wellknown=True,
allow_registred=True,
allow_private=False,
warnings_only=False):
*args,
allow_range: bool=False,
allow_zero: bool=False,
allow_wellknown: bool=True,
allow_registred: bool=True,
allow_private: bool=False,
**kwargs) -> None:
extra = {'_allow_range': allow_range,
'_min_value': None,
@ -81,24 +74,15 @@ class PortOption(StrOption):
if extra['_max_value'] is None:
raise ValueError(_('max value is empty'))
super(PortOption, self).__init__(name,
doc,
default=default,
default_multi=default_multi,
multi=multi,
validators=validators,
properties=properties,
warnings_only=warnings_only,
extra=extra)
super().__init__(*args,
extra=extra,
**kwargs)
def _validate(self,
value: Union[int,str],
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
if not isinstance(value, str):
raise ValueError(_('invalid string'))
def validate(self,
value: str) -> None:
super().validate(value)
if self.impl_get_extra('_allow_range') and ":" in str(value):
value = str(value).split(':')
value = value.split(':')
if len(value) != 2:
raise ValueError(_('range must have two values only'))
if not value[0] < value[1]:
@ -110,8 +94,16 @@ class PortOption(StrOption):
for val in value:
if not self.port_re.search(val):
raise ValueError()
def second_level_validation(self,
value: str,
warnings_only: bool) -> None:
for val in value.split(':'):
val = int(val)
if not self.impl_get_extra('_min_value') <= val <= self.impl_get_extra('_max_value'):
raise ValueError(_('must be an integer between {0} '
'and {1}').format(self.impl_get_extra('_min_value'),
if warnings_only:
msg = 'should be between {0} and {1}'
else:
msg = 'must be between {0} and {1}'
raise ValueError(_(msg).format(self.impl_get_extra('_min_value'),
self.impl_get_extra('_max_value')))

View File

@ -32,10 +32,8 @@ class StrOption(Option):
_type = 'string'
_display_name = _('string')
def _validate(self,
value: str,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
def validate(self,
value: str) -> None:
if not isinstance(value, str):
raise ValueError()
@ -47,11 +45,9 @@ UnicodeOption = StrOption
class RegexpOption(StrOption):
__slots__ = tuple()
def _validate(self,
value: Any,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
super()._validate(value, option_bag, current_opt)
def validate(self,
value: Any) -> None:
super().validate(value)
match = self._regexp.search(value)
if not match:
raise ValueError()

View File

@ -19,31 +19,75 @@
# the whole pypy projet is under MIT licence
# ____________________________________________________________
import re
from typing import Any, Optional, List, Dict
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _
from .option import Option
from .option import Option, Calculation
from .stroption import StrOption
from .domainnameoption import DomainnameOption
from .portoption import PortOption
class URLOption(DomainnameOption):
class URLOption(StrOption):
__slots__ = tuple()
proto_re = re.compile(r'(http|https)://')
path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
_type = 'url'
_display_name = _('URL')
def _validate(self,
value: str,
option_bag: OptionBag,
current_opt: Option=Undefined) -> None:
if not isinstance(value, str):
raise ValueError(_('invalid string'))
match = self.proto_re.search(value)
if not match:
def __init__(self,
name: str,
doc: str,
default: Any=undefined,
default_multi: Any=None,
multi: bool=False,
validators: Optional[List[Calculation]]=None,
properties: Optional[List[str]]=None,
warnings_only: bool=False,
extra: Optional[Dict]=None,
allow_ip: bool=False,
type: str='domainname',
allow_without_dot=False,
allow_range: bool=False,
allow_zero: bool=False,
allow_wellknown: bool=True,
allow_registred: bool=True,
allow_private: bool=False) -> None:
extra = {'_domainname': DomainnameOption(name,
doc,
allow_ip=allow_ip,
type=type,
allow_without_dot=allow_without_dot),
'_port': PortOption(name,
doc,
allow_range=allow_range,
allow_zero=allow_zero,
allow_wellknown=allow_wellknown,
allow_registred=allow_registred,
allow_private=allow_private)}
super().__init__(name,
doc,
default=default,
default_multi=default_multi,
multi=multi,
validators=validators,
properties=properties,
warnings_only=warnings_only,
extra=extra)
def _get_domain_port_files(self,
value: str) -> (str, str):
if value.startswith('http://'):
type = 'http'
value = value[7:]
elif value.startswith('https://'):
type = 'https'
value = value[8:]
else:
raise ValueError(_('must start with http:// or '
'https://'))
value = value[len(match.group(0)):]
# get domain/files
splitted = value.split('/', 1)
if len(splitted) == 1:
@ -55,20 +99,31 @@ class URLOption(DomainnameOption):
splitted = domain.split(':', 1)
if len(splitted) == 1:
domain = splitted[0]
port = 0
port = {'http': '80',
'https': '443'}[type]
else:
domain, port = splitted
if not 0 <= int(port) <= 65535:
raise ValueError(_('port must be an between 0 and '
'65536'))
return domain, port, files
def validate(self,
value: str) -> None:
super().validate(value)
domain, port, files = self._get_domain_port_files(value)
# validate port
portoption = self.impl_get_extra('_port')
portoption.validate(port)
# validate domainname
super(URLOption, self)._validate(domain,
option_bag,
current_opt)
super(URLOption, self)._second_level_validation(domain, False)
# validate file
domainnameoption = self.impl_get_extra('_domainname')
domainnameoption.validate(domain)
# validate files
if files is not None and files != '' and not self.path_re.search(files):
raise ValueError(_('must ends with a valid resource name'))
def _second_level_validation(self, value, warnings_only):
pass
def second_level_validation(self, value, warnings_only):
domain, port, files = self._get_domain_port_files(value)
# validate port
portoption = self.impl_get_extra('_port')
portoption.second_level_validation(port, warnings_only)
# validate domainname
domainnameoption = self.impl_get_extra('_domainname')
domainnameoption.second_level_validation(domain, warnings_only)