__init__.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Thu Jul 12 10:23:15 2012
  4. @author: pietro
  5. """
  6. from __future__ import print_function
  7. import subprocess
  8. try:
  9. from collections import OrderedDict
  10. except:
  11. from pygrass.orderdict import OrderedDict
  12. from itertools import izip_longest
  13. from xml.etree.ElementTree import fromstring
  14. import numpy as np
  15. import grass
  16. #
  17. # this dictionary is used to extract the value of interest from the xml
  18. # the lambda experssion is used to define small simple functions,
  19. # is equivalent to: ::
  20. #
  21. # def f(p):
  22. # return p.text.strip()
  23. #
  24. # and then we call f(p)
  25. #
  26. _GETFROMTAG = {
  27. 'description': lambda p: p.text.strip(),
  28. 'keydesc': lambda p: p.text.strip(),
  29. 'gisprompt': lambda p: dict(p.items()),
  30. 'default': lambda p: p.text.strip(),
  31. 'values': lambda p: [e.text.strip() for e in p.findall('value/name')],
  32. 'value': lambda p: None,
  33. 'guisection': lambda p: p.text.strip(),
  34. 'label': lambda p: p.text.strip(),
  35. 'suppress_required': lambda p: None,
  36. 'keywords': lambda p: p.text.strip(),
  37. }
  38. _GETTYPE = {
  39. 'string': str,
  40. 'integer': int,
  41. 'float': float,
  42. 'double': float,
  43. }
  44. def stdout2dict(stdout, sep='=', default=None, val_type=None, vsep=None):
  45. """Return a dictionary where entries are separated
  46. by newlines and the key and value are separated by `sep' (default: `=').
  47. Use the grass.core.parse_key_val function
  48. sep: key/value separator
  49. default: default value to be used
  50. val_type: value type (None for no cast)
  51. vsep: vertical separator (default os.linesep)
  52. """
  53. return grass.script.core.parse_key_val(stdout, sep, default,
  54. val_type, vsep)
  55. class ParameterError(Exception):
  56. pass
  57. class FlagError(Exception):
  58. pass
  59. def _element2dict(xparameter):
  60. diz = dict(xparameter.items())
  61. for p in xparameter:
  62. if p.tag in _GETFROMTAG:
  63. diz[p.tag] = _GETFROMTAG[p.tag](p)
  64. else:
  65. print('New tag: %s, ignored' % p.tag)
  66. return diz
  67. # dictionary used to create docstring for the objects
  68. _DOC = {
  69. #------------------------------------------------------------
  70. # head
  71. 'head': """{cmd_name}({cmd_params})
  72. Parameters
  73. ----------
  74. """,
  75. #------------------------------------------------------------
  76. # param
  77. 'param': """{name}: {default}{required}{multi}{ptype}
  78. {description}{values}""",
  79. #------------------------------------------------------------
  80. # flag_head
  81. 'flag_head': """
  82. Flags
  83. ------
  84. """,
  85. #------------------------------------------------------------
  86. # flag
  87. 'flag': """{name}: {default}
  88. {description}""",
  89. #------------------------------------------------------------
  90. # foot
  91. 'foot': """
  92. Special Parameters
  93. ------------------
  94. The Module class have some optional parameters which are distinct using a final
  95. underscore.
  96. run_: True, optional
  97. If True execute the module.
  98. finish_: True, optional
  99. If True wait untill the end of the module execution, and store the module
  100. outputs into stdout, stderr attributes of the class.
  101. stdin_: PIPE,
  102. Set the standard input
  103. """}
  104. class Parameter(object):
  105. def __init__(self, xparameter=None, diz=None):
  106. self._value = 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. self._type = _GETTYPE[diz['type']]
  118. else:
  119. raise TypeError('New type: %s, ignored' % diz['type'])
  120. self.description = diz['description']
  121. self.keydesc = diz['keydesc'] if 'keydesc' in diz else None
  122. self.values = [self._type(
  123. i) for i in diz['values']] if 'values' in diz else None
  124. self.default = self._type(
  125. diz['default']) if 'default' in diz else None
  126. self.guisection = diz['guisection'] if 'guisection' in diz else None
  127. if 'gisprompt' in diz:
  128. self.type = diz['gisprompt']['prompt']
  129. self.input = False if diz['gisprompt']['age'] == 'new' else True
  130. else:
  131. self.input = True
  132. def _get_value(self):
  133. return self._value
  134. def _set_value(self, value):
  135. if isinstance(value, list) or isinstance(value, tuple):
  136. if self.multiple:
  137. # check each value
  138. self._value = [
  139. val for val in value if isinstance(value, self._type)]
  140. else:
  141. str_err = 'The Parameter <%s>, not support multiple inputs'
  142. raise TypeError(str_err % self.name)
  143. elif isinstance(value, self._type):
  144. if self.values:
  145. if value in self.values:
  146. self._value = value
  147. else:
  148. raise ValueError('The Parameter <%s>, must be one of: %r' %
  149. (self.name, self.values))
  150. else:
  151. self._value = value
  152. else:
  153. str_err = 'The Parameter <%s>, require: %s, get: %s instead'
  154. raise TypeError(str_err % (self.name, self.typedesc, type(value)))
  155. # here the property function is used to transform value in an attribute
  156. # in this case we define which function must be use to get/set the value
  157. value = property(fget=_get_value, fset=_set_value)
  158. def __str__(self):
  159. if isinstance(self._value, list) or isinstance(self._value, tuple):
  160. value = ','.join([str(v) for v in self._value])
  161. else:
  162. value = str(self._value)
  163. return """%s=%s""" % (self.name, value)
  164. def __repr__(self):
  165. str_repr = "Parameter <%s> (required:%s, type:%s, multiple:%s)"
  166. return str_repr % (self.name,
  167. "yes" if self.required else "no",
  168. self.type if self.type in (
  169. 'raster', 'vector') else self.typedesc,
  170. "yes" if self.multiple else "no")
  171. # here we use property with a decorator, in this way we mask a method as
  172. # a class attribute
  173. @property
  174. def __doc__(self):
  175. """Return the docstring of the parameter
  176. {name}: {default}{required}{multi}{ptype}
  177. {description}{values}"","""
  178. return _DOC['param'].format(name=self.name,
  179. default=repr(self.default) + ', ' if self.default else '',
  180. required='required, ' if self.required else 'optional, ',
  181. multi='multi' if self.multiple else '',
  182. ptype=self.typedesc, description=self.description,
  183. values='\n Values: {0}'.format(', '.join([repr(val)
  184. for val in self.values]))
  185. if self.values else '')
  186. class TypeDict(OrderedDict):
  187. def __init__(self, dict_type, *args, **kargs):
  188. self.type = dict_type
  189. super(TypeDict, self).__init__(*args, **kargs)
  190. def __setitem__(self, key, value):
  191. if isinstance(value, self.type):
  192. super(TypeDict, self).__setitem__(key, value)
  193. else:
  194. cl = repr(self.type).translate(None, "'<> ").split('.')
  195. str_err = 'The value: %r is not a %s object'
  196. raise TypeError(str_err % (value, cl[-1].title()))
  197. @property
  198. def __doc__(self):
  199. return '\n'.join([self.__getitem__(obj).__doc__
  200. for obj in self.__iter__()])
  201. def __call__(self):
  202. return [self.__getitem__(obj) for obj in self.__iter__()]
  203. class Flag(object):
  204. def __init__(self, xflag=None, diz=None):
  205. self.value = None
  206. diz = _element2dict(xflag) if xflag is not None else diz
  207. self.name = diz['name']
  208. self.special = True if self.name in (
  209. 'verbose', 'overwrite', 'quiet', 'run') else False
  210. self.description = diz['description']
  211. self.default = diz['default'] if 'default' in diz else None
  212. self.guisection = diz['guisection'] if 'guisection' in diz else None
  213. def __str__(self):
  214. if self.value:
  215. if self.special:
  216. return '--%s' % self.name[0]
  217. else:
  218. return '-%s' % self.name
  219. else:
  220. return ''
  221. def __repr__(self):
  222. return "Flag <%s> (%s)" % (self.name, self.description)
  223. @property
  224. def __doc__(self):
  225. """
  226. {name}: {default}
  227. {description}"""
  228. return _DOC['flag'].format(name=self.name,
  229. default=repr(self.default),
  230. description=self.description)
  231. class Module(object):
  232. """
  233. Python allow developers to not specify all the arguments and
  234. keyword arguments of a method or function.
  235. ::
  236. def f(*args):
  237. for arg in args:
  238. print arg
  239. therefore if we call the function like: ::
  240. >>> f('grass', 'gis', 'modules')
  241. grass
  242. gis
  243. modules
  244. or we can define a new list: ::
  245. >>> words = ['grass', 'gis', 'modules']
  246. >>> f(*words)
  247. grass
  248. gis
  249. modules
  250. we can do the same with keyword arguments, rewrite the above function: ::
  251. def f(*args, **kargs):
  252. for arg in args:
  253. print arg
  254. for key, value in kargs.items():
  255. print "%s = %r" % (key, value)
  256. now we can use the new function, with: ::
  257. >>> f('grass', 'gis', 'modules', os = 'linux', language = 'python')
  258. grass
  259. gis
  260. modules
  261. os = 'linux'
  262. language = 'python'
  263. or, as before we can, define a dictionary and give the dictionary to
  264. the function, like: ::
  265. >>> keywords = {'os' : 'linux', 'language' : 'python'}
  266. >>> f(*words, **keywords)
  267. grass
  268. gis
  269. modules
  270. os = 'linux'
  271. language = 'python'
  272. In the Module class we heavily use this language feature to pass arguments
  273. and keyword arguments to the grass module.
  274. """
  275. def __init__(self, cmd, *args, **kargs):
  276. self.name = cmd
  277. # call the command with --interface-description
  278. get_cmd_xml = subprocess.Popen([cmd, "--interface-description"],
  279. stdout=subprocess.PIPE)
  280. # get the xml of the module
  281. self.xml = get_cmd_xml.communicate()[0]
  282. # transform and parse the xml into an Element class:
  283. # http://docs.python.org/library/xml.etree.elementtree.html
  284. tree = fromstring(self.xml)
  285. for e in tree:
  286. if e.tag not in ('parameter', 'flag'):
  287. self.__setattr__(e.tag, _GETFROMTAG[e.tag](e))
  288. #
  289. # extract parameters from the xml
  290. #
  291. self.params_list = [Parameter(p) for p in tree.findall("parameter")]
  292. self.inputs = TypeDict(Parameter)
  293. self.outputs = TypeDict(Parameter)
  294. self.required = []
  295. # Insert parameters into input/output and required
  296. for par in self.params_list:
  297. if par.input:
  298. self.inputs[par.name] = par
  299. else:
  300. self.outputs[par.name] = par
  301. if par.required:
  302. self.required.append(par)
  303. #
  304. # extract flags from the xml
  305. #
  306. flags_list = [Flag(f) for f in tree.findall("flag")]
  307. self.flags_dict = TypeDict(Flag)
  308. for flag in flags_list:
  309. self.flags_dict[flag.name] = flag
  310. #
  311. # Add new attributes to the class
  312. #
  313. self._flags = ''
  314. self.run_ = True
  315. self.finish_ = True
  316. self.stdin_ = None
  317. self.stdout_ = None
  318. self.stderr_ = None
  319. self.popen = None
  320. if args or kargs:
  321. self.__call__(*args, **kargs)
  322. def _get_flags(self):
  323. return self._flags
  324. def _set_flags(self, value):
  325. if isinstance(value, str):
  326. flgs = [flg for flg in self.flags_dict
  327. if not self.flags_dict[flg].special]
  328. # we need to check if the flag is valid, special flags are not
  329. # allow
  330. if value in flgs:
  331. self._flags = value
  332. else:
  333. raise ValueError('Flag not valid, valid flag are: %r' % flgs)
  334. else:
  335. raise TypeError('The flags attribute must be a string')
  336. flags = property(fget=_get_flags, fset=_set_flags)
  337. def __call__(self, *args, **kargs):
  338. if not args and not kargs:
  339. self.run()
  340. return
  341. #
  342. # check for extra kargs, set attribute and remove from dictionary
  343. #
  344. if 'flags' in kargs:
  345. self.flags = kargs['flags']
  346. del(kargs['flags'])
  347. if 'run_' in kargs:
  348. self.run_ = kargs['run_']
  349. del(kargs['run_'])
  350. if 'stdin_' in kargs:
  351. self.stdin_ = kargs['stdin_']
  352. del(kargs['stdin_'])
  353. if 'stdout_' in kargs:
  354. self.stdout_ = kargs['stdout_']
  355. del(kargs['stdout_'])
  356. if 'stderr_' in kargs:
  357. self.stderr_ = kargs['stderr_']
  358. del(kargs['stderr_'])
  359. if 'finish_' in kargs:
  360. self.finish_ = kargs['finish_']
  361. del(kargs['finish_'])
  362. #
  363. # check args
  364. #
  365. for param, arg in zip(self.params_list, args):
  366. param.value = arg
  367. for key, val in kargs.items():
  368. if key in self.inputs:
  369. self.inputs[key].value = val
  370. elif key in self.outputs:
  371. self.outputs[key].value = val
  372. elif key in self.flags_dict:
  373. # we need to add this, because some parameters (overwrite,
  374. # verbose and quiet) work like parameters
  375. self.flags_dict[key].value = val
  376. else:
  377. raise ParameterError('Parameter not found')
  378. #
  379. # check reqire parameters
  380. #
  381. for par in self.required:
  382. if par.value is None:
  383. raise ParameterError(
  384. "Required parameter <%s> not set." % par.name)
  385. #
  386. # check flags parameters
  387. #
  388. if self.flags:
  389. # check each character in flags
  390. for flag in self.flags:
  391. if flag in self.flags_dict:
  392. self.flags_dict[flag].value = True
  393. else:
  394. raise FlagError('Flag "%s" not valid.')
  395. #
  396. # check if execute
  397. #
  398. if self.run_:
  399. self.run()
  400. def __str__(self):
  401. return ' '.join(self.make_cmd())
  402. @property
  403. def __doc__(self):
  404. """{cmd_name}({cmd_params})
  405. """
  406. head = _DOC['head'].format(cmd_name=self.name,
  407. cmd_params=('\n' + # go to a new line
  408. # give space under the function name
  409. (' ' * (len(self.name) + 1))).join([', '.join(
  410. # transform each parameter in string
  411. [str(param) for param in line if param is not None])
  412. # make a list of parameters with only 3 param per line
  413. for line in izip_longest(*[iter(self.params_list)] * 3)]),)
  414. params = '\n'.join([par.__doc__ for par in self.params_list])
  415. flags = self.flags_dict.__doc__
  416. return '\n'.join([head, params, _DOC['flag_head'], flags])
  417. def make_cmd(self):
  418. args = [self.name, ]
  419. for par in self.params_list:
  420. if par.value is not None:
  421. args.append(str(par))
  422. for flg in self.flags_dict:
  423. if self.flags_dict[flg].value is not None:
  424. args.append(str(self.flags_dict[flg]))
  425. return args
  426. def run(self, node=None):
  427. cmd = self.make_cmd()
  428. #print(repr(cmd))
  429. self.popen = subprocess.Popen(cmd, stdin=self.stdin_,
  430. stdout=self.stdout_,
  431. stderr=self.stderr_)
  432. if self.finish_:
  433. self.popen.wait()
  434. self.stdout, self.stderr = self.popen.communicate()