parameter.py 11 KB

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