No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

validator.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. #-*- coding: utf-8 -*-
  2. import sys
  3. import os.path
  4. import re
  5. import socket
  6. import inspect
  7. import copy
  8. ## @package lodel.settings.validator Lodel2 settings validators/cast module
  9. #
  10. # Validator are registered in the SettingValidator class.
  11. # @note to get a list of registered default validators just run
  12. # <pre>$ python scripts/settings_validator.py</pre>
  13. ##@brief Exception class that should be raised when a validation fails
  14. class SettingsValidationError(Exception):
  15. pass
  16. ##@brief Handles settings validators
  17. #
  18. # Class instance are callable objects that takes a value argument (the value to validate). It raises
  19. # a SettingsValidationError if validation fails, else it returns a properly
  20. # casted value.
  21. class SettingValidator(object):
  22. _validators = dict()
  23. _description = dict()
  24. ##@brief Instanciate a validator
  25. def __init__(self, name, none_is_valid = False):
  26. if name is not None and name not in self._validators:
  27. raise NameError("No validator named '%s'" % name)
  28. self.__name = name
  29. ##@brief Call the validator
  30. # @param value *
  31. # @return properly casted value
  32. # @throw SettingsValidationError
  33. def __call__(self, value):
  34. if self.__name is None:
  35. return value
  36. try:
  37. return self._validators[self.__name](value)
  38. except Exception as e:
  39. raise SettingsValidationError(e)
  40. ##@brief Register a new validator
  41. # @param name str : validator name
  42. # @param callback callable : the function that will validate a value
  43. @classmethod
  44. def register_validator(cls, name, callback, description=None):
  45. if name in cls._validators:
  46. raise NameError("A validator named '%s' allready exists" % name)
  47. # Broken test for callable
  48. if not inspect.isfunction(callback) and not inspect.ismethod(callback) and not hasattr(callback, '__call__'):
  49. raise TypeError("Callable expected but got %s" % type(callback))
  50. cls._validators[name] = callback
  51. cls._description[name] = description
  52. ##@brief Get the validator list associated with description
  53. @classmethod
  54. def validators_list(cls):
  55. return copy.copy(cls._description)
  56. ##@brief Create and register a list validator
  57. # @param elt_validator callable : The validator that will be used for validate each elt value
  58. # @param validator_name str
  59. # @param description None | str
  60. # @param separator str : The element separator
  61. # @return A SettingValidator instance
  62. @classmethod
  63. def create_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
  64. def list_validator(value):
  65. res = list()
  66. errors = list()
  67. for elt in value.split(separator):
  68. elt = elt_validator(elt)
  69. if len(elt) > 0:
  70. res.append(elt)
  71. return res
  72. description = "Convert value to an array" if description is None else description
  73. cls.register_validator(
  74. validator_name,
  75. list_validator,
  76. description)
  77. return cls(validator_name)
  78. ##@brief Create and register a list validator which reads an array and returns a string
  79. # @param elt_validator callable : The validator that will be used for validate each elt value
  80. # @param validator_name str
  81. # @param description None | str
  82. # @param separator str : The element separator
  83. # @return A SettingValidator instance
  84. @classmethod
  85. def create_write_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
  86. def write_list_validator(value):
  87. res = ''
  88. errors = list()
  89. for elt in value:
  90. res += elt_validator(elt) + ','
  91. return res[:len(res)-1]
  92. description = "Convert value to a string" if description is None else description
  93. cls.register_validator(
  94. validator_name,
  95. write_list_validator,
  96. description)
  97. return cls(validator_name)
  98. ##@brief Create and register a regular expression validator
  99. # @param pattern str : regex pattern
  100. # @param validator_name str : The validator name
  101. # @param description str : Validator description
  102. # @return a SettingValidator instance
  103. @classmethod
  104. def create_re_validator(cls, pattern, validator_name, description = None):
  105. def re_validator(value):
  106. if not re.match(pattern, value):
  107. raise SettingsValidationError("The value '%s' doesn't match the following pattern '%s'" % pattern)
  108. return value
  109. #registering the validator
  110. cls.register_validator(
  111. validator_name,
  112. re_validator,
  113. ("Match value to '%s'" % pattern) if description is None else description)
  114. return cls(validator_name)
  115. ## @return a list of registered validators
  116. @classmethod
  117. def validators_list_str(cls):
  118. result = ''
  119. for name in sorted(cls._validators.keys()):
  120. result += "\t%016s" % name
  121. if name in cls._description and cls._description[name] is not None:
  122. result += ": %s" % cls._description[name]
  123. result += "\n"
  124. return result
  125. ##@brief Integer value validator callback
  126. def int_val(value):
  127. return int(value)
  128. ##@brief Output file validator callback
  129. # @return A file object (if filename is '-' return sys.stderr)
  130. def file_err_output(value):
  131. if not isinstance(value, str):
  132. raise SettingsValidationError("A string was expected but got '%s' " % value)
  133. if value == '-':
  134. return None
  135. return value
  136. ##@brief Boolean value validator callback
  137. def boolean_val(value):
  138. if value.strip().lower() == 'true' or value.strip() == '1':
  139. value = True
  140. elif value.strip().lower() == 'false' or value.strip() == '0':
  141. value = False
  142. else:
  143. raise SettingsValidationError("A boolean was expected but got '%s' " % value)
  144. return bool(value)
  145. def directory_val(value):
  146. res = SettingValidator('strip')(value)
  147. if not os.path.isdir(res):
  148. raise SettingsValidationError("Folowing path don't exists or is not a directory : '%s'"%res)
  149. return res
  150. def loglevel_val(value):
  151. valids = ['DEBUG', 'INFO', 'SECURITY', 'ERROR', 'CRITICAL']
  152. if value.upper() not in valids:
  153. raise SettingsValidationError(
  154. "The value '%s' is not a valid loglevel" % value)
  155. return value.upper()
  156. def path_val(value):
  157. if not os.path.exists(value):
  158. raise SettingsValidationError(
  159. "path '%s' doesn't exists" % value)
  160. return value
  161. def none_val(value):
  162. if value is None:
  163. return None
  164. raise SettingsValidationError("This settings cannot be set in configuration file")
  165. def str_val(value):
  166. try:
  167. return str(value)
  168. except Exception as e:
  169. raise SettingsValidationError("Not able to convert value to string : " + str(e))
  170. def host_val(value):
  171. if value == 'localhost':
  172. return value
  173. ok = False
  174. try:
  175. socket.inet_aton(value)
  176. return value
  177. except (TypeError,OSError):
  178. pass
  179. try:
  180. socket.inet_pton(socket.AF_INET6, value)
  181. return value
  182. except (TypeError,OSError):
  183. pass
  184. try:
  185. socket.getaddrinfo(value, 80)
  186. return value
  187. except (TypeError,socket.gaierrror):
  188. msg = "The value '%s' is not a valid host"
  189. raise SettingsValidationError(msg % value)
  190. #
  191. # Default validators registration
  192. #
  193. SettingValidator.register_validator(
  194. 'dummy',
  195. lambda value:value,
  196. 'Validate anything')
  197. SettingValidator.register_validator(
  198. 'none',
  199. none_val,
  200. 'Validate None')
  201. SettingValidator.register_validator(
  202. 'string',
  203. str_val,
  204. 'Validate string values')
  205. SettingValidator.register_validator(
  206. 'strip',
  207. str.strip,
  208. 'String trim')
  209. SettingValidator.register_validator(
  210. 'int',
  211. int_val,
  212. 'Integer value validator')
  213. SettingValidator.register_validator(
  214. 'bool',
  215. boolean_val,
  216. 'Boolean value validator')
  217. SettingValidator.register_validator(
  218. 'errfile',
  219. file_err_output,
  220. 'Error output file validator (return stderr if filename is "-")')
  221. SettingValidator.register_validator(
  222. 'directory',
  223. directory_val,
  224. 'Directory path validator')
  225. SettingValidator.register_validator(
  226. 'loglevel',
  227. loglevel_val,
  228. 'Loglevel validator')
  229. SettingValidator.register_validator(
  230. 'path',
  231. path_val,
  232. 'path validator')
  233. SettingValidator.register_validator(
  234. 'host',
  235. host_val,
  236. 'host validator')
  237. SettingValidator.create_list_validator(
  238. 'list',
  239. SettingValidator('strip'),
  240. description = "Simple list validator. Validate a list of values separated by ','",
  241. separator = ',')
  242. SettingValidator.create_list_validator(
  243. 'directory_list',
  244. SettingValidator('directory'),
  245. description = "Validator for a list of directory path separated with ','",
  246. separator = ',')
  247. SettingValidator.create_write_list_validator(
  248. 'write_list',
  249. SettingValidator('directory'),
  250. description = "Validator for an array of values which will be set in a string, separated by ','",
  251. separator = ',')
  252. SettingValidator.create_re_validator(
  253. r'^https?://[^\./]+.[^\./]+/?.*$',
  254. 'http_url',
  255. 'Url validator')
  256. #
  257. # Lodel 2 configuration specification
  258. #
  259. ##@brief Global specifications for lodel2 settings
  260. LODEL2_CONF_SPECS = {
  261. 'lodel2': {
  262. 'debug': ( True,
  263. SettingValidator('bool')),
  264. 'plugins_path': ( None,
  265. SettingValidator('list')),
  266. 'plugins': ( "",
  267. SettingValidator('list')),
  268. 'sitename': ( 'noname',
  269. SettingValidator('strip')),
  270. 'lib_path': ( None,
  271. SettingValidator('path')),
  272. },
  273. 'lodel2.logging.*' : {
  274. 'level': ( 'ERROR',
  275. SettingValidator('loglevel')),
  276. 'context': ( False,
  277. SettingValidator('bool')),
  278. 'filename': ( None,
  279. SettingValidator('errfile', none_is_valid = True)),
  280. 'backupcount': ( None,
  281. SettingValidator('int', none_is_valid = True)),
  282. 'maxbytes': ( None,
  283. SettingValidator('int', none_is_valid = True)),
  284. },
  285. 'lodel2.editorialmodel': {
  286. 'emfile': ( 'em.pickle', SettingValidator('strip')),
  287. 'emtranslator': ( 'picklefile', SettingValidator('strip')),
  288. 'dyncode': ( 'leapi_dyncode.py', SettingValidator('strip')),
  289. 'groups': ( '', SettingValidator('list')),
  290. 'editormode': ( False, SettingValidator('bool')),
  291. },
  292. 'lodel2.datasources.*': {
  293. 'read_only': (False, SettingValidator('bool')),
  294. 'identifier': ( None, SettingValidator('string'))}
  295. }