parameter.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Tue Apr 2 18:31:47 2013
  4. @author: pietro
  5. """
  6. from __future__ import (nested_scopes, generators, division, absolute_import,
  7. with_statement, print_function, unicode_literals)
  8. import re
  9. try:
  10. from builtins import unicode
  11. except ImportError:
  12. unicode = str
  13. from grass.pygrass.modules.interface.docstring import docstring_property
  14. from grass.pygrass.modules.interface.read import GETTYPE, element2dict, DOC
  15. def _check_value(param, value):
  16. """Function to check the correctness of a value and
  17. return the checked value and the original.
  18. """
  19. must_val = 'The Parameter <%s>, must be one of the following values: %r'
  20. req = 'The Parameter <%s>, require: %s, get: %s instead: %r\n%s'
  21. def raiseexcpet(exc, param, ptype, value):
  22. """Function to modifa the error message"""
  23. msg = req % (param.name, param.typedesc, ptype, value, exc.message)
  24. if isinstance(exc, ValueError):
  25. raise ValueError(msg)
  26. elif isinstance(exc, TypeError):
  27. raise TypeError(msg)
  28. else:
  29. exc.message = msg
  30. raise exc
  31. def check_string(value):
  32. """Function to check that a string parameter is already a string"""
  33. string = (str, unicode)
  34. if param.type in string:
  35. if type(value) in (int, float):
  36. value = str(value)
  37. if type(value) not in string:
  38. msg = ("The Parameter <%s> require a string,"
  39. " %s instead is provided: %r")
  40. raise ValueError(msg % (param.name, type(value), value))
  41. return value
  42. # return None if None
  43. if value is None:
  44. return param.default, param.default
  45. # find errors with multiple parmeters
  46. if isinstance(value, (list, tuple)):
  47. if param.keydescvalues:
  48. return (([value, ], value) if isinstance(value, tuple)
  49. else (value, value))
  50. if param.multiple:
  51. # everything looks fine, so check each value
  52. try:
  53. return [param.type(check_string(val)) for val in value], value
  54. except Exception as exc:
  55. raiseexcpet(exc, param, param.type, value)
  56. else:
  57. msg = 'The Parameter <%s> does not accept multiple inputs'
  58. raise TypeError(msg % param.name)
  59. if param.keydescvalues:
  60. msg = 'The Parameter <%s> require multiple inputs in the form: %s'
  61. raise TypeError(msg % (param.name, param.keydescvalues))
  62. if param.typedesc == 'all':
  63. return value, value
  64. # check string before trying to convert value to the correct type
  65. check_string(value)
  66. # the value is a scalar
  67. try:
  68. newvalue = param.type(value)
  69. except Exception as exc:
  70. raiseexcpet(exc, param, type(value), value)
  71. # check values
  72. if hasattr(param, 'values'):
  73. if param.type in (float, int):
  74. # check for value in range
  75. if ((param.min is not None and newvalue < param.min) or
  76. (param.max is not None and newvalue > param.max)):
  77. err_str = ('The Parameter <%s>, must be between: '
  78. '%g<=value<=%g, %r is outside.')
  79. raise ValueError(err_str % (param.name, param.min,
  80. param.max, newvalue))
  81. # check if value is in the list of valid values
  82. if param.values is not None and newvalue not in param.values:
  83. raise ValueError(must_val % (param.name, param.values))
  84. return (([newvalue, ] if (param.multiple or param.keydescvalues)
  85. else newvalue), value)
  86. # TODO add documentation
  87. class Parameter(object):
  88. """The Parameter object store all information about a parameter of a
  89. GRASS GIS module. ::
  90. >>> param = Parameter(diz=dict(name='int_number', required='yes',
  91. ... multiple='no', type='integer',
  92. ... values=[2, 4, 6, 8]))
  93. >>> param.value = 2
  94. >>> param.value
  95. 2
  96. >>> param.value = 3
  97. Traceback (most recent call last):
  98. ...
  99. ValueError: The Parameter <int_number>, must be one of the following values: [2, 4, 6, 8]
  100. ...
  101. """
  102. def __init__(self, xparameter=None, diz=None):
  103. self._value = None
  104. self._rawvalue = None
  105. self.min = None
  106. self.max = None
  107. diz = element2dict(xparameter) if xparameter is not None else diz
  108. if diz is None:
  109. raise TypeError('Xparameter or diz are required')
  110. self.name = diz['name']
  111. self.required = True if diz['required'] == 'yes' else False
  112. self.multiple = True if diz['multiple'] == 'yes' else False
  113. # check the type
  114. if diz['type'] in GETTYPE:
  115. self.type = GETTYPE[diz['type']]
  116. self.typedesc = diz['type']
  117. else:
  118. raise TypeError('New type: %s, ignored' % diz['type'])
  119. self.description = diz.get('description', None)
  120. self.keydesc, self.keydescvalues = diz.get('keydesc', (None, None))
  121. #
  122. # values
  123. #
  124. if 'values' in diz:
  125. try:
  126. # Check for integer ranges: "3-30" or float ranges: "0.0-1.0"
  127. isrange = re.match("(?P<min>-*\d+.*\d*)*-(?P<max>\d+.*\d*)*",
  128. diz['values'][0])
  129. if isrange:
  130. mn, mx = isrange.groups()
  131. self.min = None if mn is None else float(mn)
  132. self.max = None if mx is None else float(mx)
  133. self.values = None
  134. self.isrange = diz['values'][0]
  135. # No range was found
  136. else:
  137. self.values = [self.type(i) for i in diz['values']]
  138. self.isrange = False
  139. except TypeError:
  140. self.values = [self.type(i) for i in diz['values']]
  141. self.isrange = False
  142. #
  143. # default
  144. #
  145. if 'default' in diz and diz['default']:
  146. if self.multiple or self.keydescvalues:
  147. self.default = [self.type(v)
  148. for v in diz['default'].split(',')]
  149. else:
  150. self.default = self.type(diz['default'])
  151. else:
  152. self.default = None
  153. self._value, self._rawvalue = self.default, self.default
  154. self.guisection = diz.get('guisection', None)
  155. #
  156. # gisprompt
  157. #
  158. if 'gisprompt' in diz and diz['gisprompt']:
  159. self.typedesc = diz['gisprompt'].get('prompt', '')
  160. self.input = False if diz['gisprompt']['age'] == 'new' else True
  161. else:
  162. self.input = True
  163. def _get_value(self):
  164. return self._value
  165. def _set_value(self, value):
  166. self._value, self._rawvalue = _check_value(self, value)
  167. # here the property function is used to transform value in an attribute
  168. # in this case we define which function must be use to get/set the value
  169. value = property(fget=_get_value, fset=_set_value,
  170. doc="Parameter value transformed and validated.")
  171. @property
  172. def rawvalue(self):
  173. """Parameter value as insert by user without transformation"""
  174. return self._rawvalue
  175. def get_bash(self):
  176. """Return the BASH representation of the parameter. ::
  177. >>> param = Parameter(diz=dict(name='int_number', required='yes',
  178. ... multiple='no', type='integer',
  179. ... values=[2, 4, 6, 8], default=8))
  180. >>> param.get_bash()
  181. u'int_number=8'
  182. ..
  183. """
  184. sep = ','
  185. if isinstance(self.rawvalue, (list, tuple)):
  186. value = sep.join([sep.join([str(v) for v in val])
  187. if isinstance(val, tuple) else str(val)
  188. for val in self.rawvalue])
  189. else:
  190. value = str(self.rawvalue)
  191. return "%s=%s" % (self.name, value)
  192. def get_python(self):
  193. """Return a string with the Python representation of the parameter. ::
  194. >>> param = Parameter(diz=dict(name='int_number', required='yes',
  195. ... multiple='no', type='integer',
  196. ... values=[2, 4, 6, 8], default=8))
  197. >>> param.get_python()
  198. u'int_number=8'
  199. ..
  200. """
  201. if self.value is None:
  202. return ''
  203. return """%s=%r""" % (self.name, self.value)
  204. def __str__(self):
  205. """Return the BASH representation of the GRASS module parameter."""
  206. return self.get_bash()
  207. def __repr__(self):
  208. """Return the python representation of the GRASS module parameter."""
  209. str_repr = "Parameter <%s> (required:%s, type:%s, multiple:%s)"
  210. mtype = ('raster', 'vector') # map type
  211. return str_repr % (self.name,
  212. "yes" if self.required else "no",
  213. self.type if self.type in mtype else self.typedesc,
  214. "yes" if self.multiple else "no")
  215. @docstring_property(__doc__)
  216. def __doc__(self):
  217. """Return the docstring of the parameter
  218. {name}: {default}{required}{multi}{ptype}
  219. {description}{values}"",
  220. ::
  221. >>> param = Parameter(diz=dict(name='int_number',
  222. ... description="Set an number",
  223. ... required='yes',
  224. ... multiple='no', type='integer',
  225. ... values=[2, 4, 6, 8], default=8))
  226. >>> print(param.__doc__)
  227. int_number: 8, required, integer
  228. Set an number
  229. Values: 2, 4, 6, 8
  230. ..
  231. """
  232. if hasattr(self, 'values'):
  233. if self.isrange:
  234. vals = self.isrange
  235. else:
  236. vals = ', '.join([repr(val) for val in self.values])
  237. else:
  238. vals = False
  239. if self.keydescvalues:
  240. keydescvals = "\n (%s)" % ', '.join(self.keydescvalues)
  241. return DOC['param'].format(name=self.name,
  242. default=repr(self.default) + ', ' if self.default else '',
  243. required='required, ' if self.required else 'optional, ',
  244. multi='multi' if self.multiple else '',
  245. ptype=self.typedesc, description=self.description,
  246. values='\n Values: {0}'.format(vals) if vals else '',
  247. keydescvalues= keydescvals if self.keydescvalues else '')