1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2025-10-31 03:29:03 +01:00

Validator documentation

This commit is contained in:
Quentin Bonaventure 2017-03-29 14:39:26 +02:00
commit ab03bbf7c8

View file

@ -6,42 +6,50 @@ import re
import socket import socket
import inspect import inspect
import copy import copy
from lodel.context import LodelContext from lodel.context import LodelContext
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'], 'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
'lodel.exceptions': ['LodelException', 'LodelExceptions', 'lodel.exceptions': ['LodelException', 'LodelExceptions',
'LodelFatalError', 'FieldValidationError']}) 'LodelFatalError', 'FieldValidationError']})
# @package lodel.settings.validator Lodel2 settings validators/cast module
##
# @package lodel.settings.validator Lodel2 settings validators/cast module.
# #
# Validator are registered in the Validator class. # Validator are registered in the Validator class.
# @note to get a list of registered default validators just run # @note to get a list of registered default validators just run
# <pre>$ python scripts/settings_validator.py</pre> # <pre>$ python scripts/settings_validator.py</pre>
#
# @remarks Should we reconsider specifying conf right in this module?
##
# @brief Exception class that should be raised when a validation fails # @brief Exception class that should be raised when a validation fails
class ValidationError(Exception): class ValidationError(Exception):
pass pass
##
# @brief Handles settings validators # @brief Handles settings validators
# #
# Class instance are callable objects that takes a value argument (the value to validate). It raises # Class instance are callable objects that takes a value argument (the value
# a ValidationError if validation fails, else it returns a properly # to validate). It raises a ValidationError if validation fails, else it returns
# casted value. # a properly cast value.
#@todo implement an IP validator and use it in multisite confspec #
#@todo Implement an IP validator for use in the multisite confspec
class Validator(MlNamedObject): class Validator(MlNamedObject):
_validators = dict() _validators = dict()
_description = dict() _description = dict()
# @brief Instanciate a validator ##
#@param name str : validator name # @brief Instantiate a validator
#@param none_is_valid bool : if True None will be validated #
#@param **kwargs : more arguement for the validator #@param name str: validator name
#@param none_is_valid bool: if True None will be validated
#@param **kwargs: more arguments for the validator
def __init__(self, name, none_is_valid=False, display_name=None, help_text=None, **kwargs): def __init__(self, name, none_is_valid=False, display_name=None, help_text=None, **kwargs):
if name is not None and name not in self._validators: if name is not None and name not in self._validators:
raise LodelFatalError("No validator named '%s'" % name) raise LodelFatalError("No validator named '%s'" % name)
@ -52,9 +60,11 @@ class Validator(MlNamedObject):
display_name = name display_name = name
super().__init__(display_name, help_text) super().__init__(display_name, help_text)
# @brief Call the validator ##
# @param value * # @brief Calls the validator.
# @return properly casted value #
# @param value mixed:
# @return mixed: The properly casted value
# @throw ValidationError # @throw ValidationError
def __call__(self, value): def __call__(self, value):
if value is None: if value is None:
@ -70,31 +80,38 @@ class Validator(MlNamedObject):
except Exception as exp: except Exception as exp:
raise ValidationError(exp) raise ValidationError(exp)
##
# @brief Register a new validator # @brief Register a new validator
# @param name str : validator name #
# @param callback callable : the function that will validate a value # @param name string: validator name
# @param description str # @param callback callable: the function that will validate a value
# @param description string:
@classmethod @classmethod
def register_validator(cls, name, callback, description=None): def register_validator(cls, name, callback, description=None):
if name in cls._validators: if name in cls._validators:
raise NameError("A validator named '%s' allready exists" % name) raise NameError("A validator named '%s' allready exists" % name)
# Broken test for callable ##
# @todo Broken test for callable.
if not inspect.isfunction(callback) and not inspect.ismethod(callback) and not hasattr(callback, '__call__'): if not inspect.isfunction(callback) and not inspect.ismethod(callback) and not hasattr(callback, '__call__'):
raise TypeError("Callable expected but got %s" % type(callback)) raise TypeError("Callable expected but got %s" % type(callback))
cls._validators[name] = callback cls._validators[name] = callback
cls._description[name] = description cls._description[name] = description
##
# @brief Get the validator list associated with description # @brief Get the validator list associated with description
@classmethod @classmethod
def validators_list(cls): def validators_list(cls):
return copy.copy(cls._description) return copy.copy(cls._description)
# @brief Create and register a list validator ##
# @param elt_validator callable : The validator that will be used for validate each elt value # @brief Creates and registers an iterative list validator
# @param validator_name str #
# @param description None | str # @param elt_validator callable: The validator that will be used to validate
# @param separator str : The element separator # each of the list values.
# @return A Validator instance # @param validator_name string:
# @param description None | string:
# @param separator string: The element separator.
# @return A Validator instance.
@classmethod @classmethod
def create_list_validator(cls, validator_name, elt_validator, description=None, separator=','): def create_list_validator(cls, validator_name, elt_validator, description=None, separator=','):
def list_validator(value): def list_validator(value):
@ -111,11 +128,14 @@ class Validator(MlNamedObject):
cls.register_validator(validator_name, list_validator, description) cls.register_validator(validator_name, list_validator, description)
return cls(validator_name) return cls(validator_name)
# @brief Create and register a list validator which reads an array and returns a string ##
# @param elt_validator callable : The validator that will be used for validate each elt value # @brief Creates and registers a list validator that reads an array
# @param validator_name str # and returns a string
# @param description None | str # @param elt_validator callable: The validator that will be used to validate
# @param separator str : The element separator # each elt value
# @param validator_name string:
# @param description None | string:
# @param separator string: The element separator
# @return A Validator instance # @return A Validator instance
@classmethod @classmethod
def create_write_list_validator(cls, validator_name, elt_validator, description=None, separator=','): def create_write_list_validator(cls, validator_name, elt_validator, description=None, separator=','):
@ -128,10 +148,12 @@ class Validator(MlNamedObject):
cls.register_validator(validator_name, write_list_validator, description) cls.register_validator(validator_name, write_list_validator, description)
return cls(validator_name) return cls(validator_name)
##
# @brief Create and register a regular expression validator # @brief Create and register a regular expression validator
# @param pattern str : regex pattern #
# @param validator_name str : The validator name # @param pattern str: regex pattern
# @param description str : Validator description # @param validator_name str: The validator name
# @param description str: Validator description
# @return a Validator instance # @return a Validator instance
@classmethod @classmethod
def create_re_validator(cls, pattern, validator_name, description=None): def create_re_validator(cls, pattern, validator_name, description=None):
@ -147,7 +169,8 @@ class Validator(MlNamedObject):
if description is None else description) if description is None else description)
return cls(validator_name) return cls(validator_name)
#  @return a list of registered validators ##
#  @return The list of registered validators.
@classmethod @classmethod
def validators_list_str(cls): def validators_list_str(cls):
result = '' result = ''
@ -158,16 +181,20 @@ class Validator(MlNamedObject):
result += "\n" result += "\n"
return result return result
##
# @brief Integer value validator callback # @brief Integer value validator callback
#
# @remarks Is it intended that this function simply tries casting to an integer?
# Is it also intended that it does not use any try/catch block?
def int_val(value): def int_val(value):
return int(value) return int(value)
##
# @brief Output file validator callback # @brief Output file validator callback
#
# @return A file object (if filename is '-' return sys.stderr) # @return A file object (if filename is '-' return sys.stderr)
def file_err_output(value): def file_err_output(value):
if not isinstance(value, str): if not isinstance(value, str):
raise ValidationError("A string was expected but got '%s' " % value) raise ValidationError("A string was expected but got '%s' " % value)
@ -175,9 +202,9 @@ def file_err_output(value):
return None return None
return value return value
# @brief Boolean value validator callback
##
# @brief Boolean validator callback
def boolean_val(value): def boolean_val(value):
if isinstance(value, bool): if isinstance(value, bool):
return value return value
@ -189,18 +216,18 @@ def boolean_val(value):
raise ValidationError("A boolean was expected but got '%s' " % value) raise ValidationError("A boolean was expected but got '%s' " % value)
return bool(value) return bool(value)
##
# @brief Validate a directory path # @brief Validate a directory path
def directory_val(value): def directory_val(value):
res = Validator('strip')(value) res = Validator('strip')(value)
if not os.path.isdir(res): if not os.path.isdir(res):
raise ValidationError("Following path don't exists or is not a directory : '%s'" % res) raise ValidationError("Following path don't exists or is not a directory : '%s'" % res)
return res return res
# @brief Validate a loglevel value
##
# @brief Validates a loglevel value
def loglevel_val(value): def loglevel_val(value):
valids = ['DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL'] valids = ['DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL']
if value.upper() not in valids: if value.upper() not in valids:
@ -208,44 +235,49 @@ def loglevel_val(value):
"The value '%s' is not a valid loglevel" % value) "The value '%s' is not a valid loglevel" % value)
return value.upper() return value.upper()
# @brief Validate a path
##
# @brief Validates a path
#
# @remarks is it intended to have both @ref directory_val and this function
# right here?
def path_val(value): def path_val(value):
if value is None or not os.path.exists(value): if value is None or not os.path.exists(value):
raise ValidationError( raise ValidationError(
"path '%s' doesn't exists" % value) "path '%s' doesn't exists" % value)
return value return value
# @brief Validate None
##
# @brief Validates None
#
# @remarks Purpose?
def none_val(value): def none_val(value):
if value is None: if value is None:
return None return None
raise ValidationError("This settings cannot be set in configuration file") raise ValidationError("This settings cannot be set in configuration file")
# @brief Validate a string
##
# @brief Validates a string
def str_val(value): def str_val(value):
try: try:
return str(value) return str(value)
except Exception as exp: except Exception as exp:
raise ValidationError("Can't to convert value to string: " + str(exp)) raise ValidationError("Can't to convert value to string: " + str(exp))
##
# @brief Validate using a regex # @brief Validate using a regex
def regex_val(value, pattern): def regex_val(value, pattern):
if re.match(pattern, value) is None: if re.match(pattern, value) is None:
raise ValidationError("The value '%s' is not validated by : \ raise ValidationError("The value '%s' is not validated by : \
r\"%s\"" % (value, pattern)) r\"%s\"" % (value, pattern))
return value return value
##
# @brief Validate a hostname (ipv4 or ipv6) # @brief Validate a hostname (ipv4 or ipv6)
def host_val(value): def host_val(value):
if value == 'localhost': if value == 'localhost':
return value return value
@ -275,67 +307,52 @@ def custom_list_validator(value, validator_name, validator_kwargs=None):
validator(item) validator(item)
return value.split() return value.split()
# #
# Default validators registration # Default validators registration
# #
Validator.register_validator('custom_list', custom_list_validator, Validator.register_validator('custom_list', custom_list_validator,
'A list validator that takes a "validator_name" as argument') 'A list validator that takes a "validator_name" as argument')
Validator.register_validator('dummy', lambda value: value, 'Validate anything') Validator.register_validator('dummy', lambda value: value, 'Validate anything')
Validator.register_validator('none', none_val, 'Validate None') Validator.register_validator('none', none_val, 'Validate None')
Validator.register_validator('string', str_val, 'Validate string values') Validator.register_validator('string', str_val, 'Validate string values')
Validator.register_validator('strip', str.strip, 'String trim') Validator.register_validator('strip', str.strip, 'String trim')
Validator.register_validator('int', int_val, 'Integer value validator') Validator.register_validator('int', int_val, 'Integer value validator')
Validator.register_validator('bool', boolean_val, 'Boolean value validator') Validator.register_validator('bool', boolean_val, 'Boolean value validator')
Validator.register_validator('errfile', file_err_output, Validator.register_validator('errfile', file_err_output,
'Error output file validator (return stderr if filename is "-")') 'Error output file validator (return stderr if filename is "-")')
Validator.register_validator('directory', directory_val, Validator.register_validator('directory', directory_val,
'Directory path validator') 'Directory path validator')
Validator.register_validator('loglevel', loglevel_val, 'Loglevel validator') Validator.register_validator('loglevel', loglevel_val, 'Loglevel validator')
Validator.register_validator('path', path_val, 'path validator') Validator.register_validator('path', path_val, 'path validator')
Validator.register_validator('host', host_val, 'host validator') Validator.register_validator('host', host_val, 'host validator')
Validator.register_validator('regex', regex_val, Validator.register_validator('regex', regex_val,
'RegEx name validator (take re as argument)') 'RegEx name validator (take re as argument)')
Validator.create_list_validator('list', Validator('strip'), description="Simple list validator. Validate a list of values separated by ','", Validator.create_list_validator('list', Validator('strip'), description="Simple list validator. Validate a list of values separated by ','",
separator=',') separator=',')
Validator.create_list_validator( Validator.create_list_validator(
'directory_list', 'directory_list',
Validator('directory'), Validator('directory'),
description="Validator for a list of directory path separated with ','", description="Validator for a list of directory path separated with ','",
separator=',') separator=',')
Validator.create_write_list_validator( Validator.create_write_list_validator(
'write_list', 'write_list',
Validator('directory'), Validator('directory'),
description="Validator for an array of values \ description="Validator for an array of values \
which will be set in a string, separated by ','", which will be set in a string, separated by ','",
separator=',') separator=',')
Validator.create_re_validator( Validator.create_re_validator(
r'^https?://[^\./]+.[^\./]+/?.*$', r'^https?://[^\./]+.[^\./]+/?.*$',
'http_url', 'http_url',
'Url validator') 'Url validator')
# @brief Validator for Editorial model component
##
# @brief Validator for Editorial Model components.
# #
# Designed to validate a conf that indicate a class.field in an EM # Designed to validate a conf that indicates a class.field in an EM
#@todo modified the hardcoded dyncode import (it's a warning) #
# @todo modify the hardcoded dyncode import (it's a warning)
def emfield_val(value): def emfield_val(value):
LodelContext.expose_modules(globals(), LodelContext.expose_modules(globals(),
{'lodel.plugin.hooks': ['LodelHook']}) {'lodel.plugin.hooks': ['LodelHook']})
@ -359,11 +376,11 @@ def emfield_val(value):
raise ValidationError(msg % value) raise ValidationError(msg % value)
return value return value
# @brief Validator for plugin name & optionnaly type
##
# @brief Validator for plugin name & its type optionally
# #
# Able to check that the value is a plugin and if it is of a specific type # Able to check that the value is a plugin and if it is of a specific type
def plugin_validator(value, ptype=None): def plugin_validator(value, ptype=None):
if value: if value:
LodelContext.expose_modules(globals(), { LodelContext.expose_modules(globals(), {
@ -396,26 +413,25 @@ Validator.register_validator(
'plugin', 'plugin',
plugin_validator, plugin_validator,
'plugin name & type validator') 'plugin name & type validator')
Validator.register_validator( Validator.register_validator(
'emfield', 'emfield',
emfield_val, emfield_val,
'EmField name validator') 'EmField name validator')
#
##
# Lodel 2 configuration specification # Lodel 2 configuration specification
# #
# @brief Append a piece of confspec # @brief Append a piece of confspec
#@note orig is modified during the process #
#@param orig dict : the confspec to update # @param orig dict : the confspec to update
#@param section str : section name # @param section str : section name
#@param key str # @param key str
#@param validator Validator : the validator to use to check this configuration key's value # @param validator Validator : the validator to use to check this configuration key's value
#@param default # @param default
#@return new confspec # @return new confspec
#
# @note orig is modified during the process
def confspec_append(orig, section, key, validator, default): def confspec_append(orig, section, key, validator, default):
if section not in orig: if section not in orig:
orig[section] = dict() orig[section] = dict()
@ -423,6 +439,8 @@ def confspec_append(orig, section, key, validator, default):
orig[section][key] = (default, validator) orig[section][key] = (default, validator)
return orig return orig
##
# @brief Global specifications for lodel2 settings # @brief Global specifications for lodel2 settings
LODEL2_CONF_SPECS = { LODEL2_CONF_SPECS = {
'lodel2': { 'lodel2': {