module.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Tue Apr 2 18:41:27 2013
  4. @author: pietro
  5. @code
  6. >>> import grass.pygrass.modules as pymod
  7. >>> import copy
  8. >>> region = pymod.Module("g.region")
  9. >>> region.flags["p"].value = True
  10. >>> region.flags["3"].value = True
  11. >>> region.get_bash()
  12. 'g.region -p -3'
  13. >>> new_region = copy.deepcopy(region)
  14. >>> new_region.inputs["res"].value = "10"
  15. >>> new_region.get_bash()
  16. 'g.region res=10 -p -3'
  17. >>> neighbors = pymod.Module("r.neighbors")
  18. >>> neighbors.inputs["input"].value = "mapA"
  19. >>> neighbors.outputs["output"].value = "mapB"
  20. >>> neighbors.inputs["size"].value = 5
  21. >>> neighbors.get_bash()
  22. 'r.neighbors input=mapA method=average size=5 quantile=0.5 output=mapB'
  23. >>> new_neighbors1 = copy.deepcopy(neighbors)
  24. >>> new_neighbors1.inputs["input"].value = "mapD"
  25. >>> new_neighbors1.inputs["size"].value = 3
  26. >>> new_neighbors1.get_bash()
  27. 'r.neighbors input=mapD method=average size=3 quantile=0.5 output=mapB'
  28. >>> new_neighbors2 = copy.deepcopy(neighbors)
  29. >>> new_neighbors2(input="mapD", size=3, run_=False)
  30. >>> new_neighbors2.get_bash()
  31. 'r.neighbors input=mapD method=average size=3 quantile=0.5 output=mapB'
  32. @endcode
  33. """
  34. from __future__ import print_function
  35. import subprocess
  36. from itertools import izip_longest
  37. from xml.etree.ElementTree import fromstring
  38. from grass.pygrass.errors import GrassError, ParameterError
  39. from parameter import Parameter
  40. from flag import Flag
  41. from typedict import TypeDict
  42. from read import GETFROMTAG, DOC
  43. class Module(object):
  44. """
  45. Python allow developers to not specify all the arguments and
  46. keyword arguments of a method or function.
  47. ::
  48. def f(*args):
  49. for arg in args:
  50. print arg
  51. therefore if we call the function like: ::
  52. >>> f('grass', 'gis', 'modules')
  53. grass
  54. gis
  55. modules
  56. or we can define a new list: ::
  57. >>> words = ['grass', 'gis', 'modules']
  58. >>> f(*words)
  59. grass
  60. gis
  61. modules
  62. we can do the same with keyword arguments, rewrite the above function: ::
  63. def f(*args, **kargs):
  64. for arg in args:
  65. print arg
  66. for key, value in kargs.items():
  67. print "%s = %r" % (key, value)
  68. now we can use the new function, with: ::
  69. >>> f('grass', 'gis', 'modules', os = 'linux', language = 'python')
  70. grass
  71. gis
  72. modules
  73. os = 'linux'
  74. language = 'python'
  75. or, as before we can, define a dictionary and give the dictionary to
  76. the function, like: ::
  77. >>> keywords = {'os' : 'linux', 'language' : 'python'}
  78. >>> f(*words, **keywords)
  79. grass
  80. gis
  81. modules
  82. os = 'linux'
  83. language = 'python'
  84. In the Module class we heavily use this language feature to pass arguments
  85. and keyword arguments to the grass module.
  86. """
  87. def __init__(self, cmd, *args, **kargs):
  88. self.name = cmd
  89. try:
  90. # call the command with --interface-description
  91. get_cmd_xml = subprocess.Popen([cmd, "--interface-description"],
  92. stdout=subprocess.PIPE)
  93. except OSError:
  94. str_err = "Module %r not found, please check that the module exist"
  95. raise GrassError(str_err % self.name)
  96. # get the xml of the module
  97. self.xml = get_cmd_xml.communicate()[0]
  98. # transform and parse the xml into an Element class:
  99. # http://docs.python.org/library/xml.etree.elementtree.html
  100. tree = fromstring(self.xml)
  101. for e in tree:
  102. if e.tag not in ('parameter', 'flag'):
  103. self.__setattr__(e.tag, GETFROMTAG[e.tag](e))
  104. #
  105. # extract parameters from the xml
  106. #
  107. self.params_list = [Parameter(p) for p in tree.findall("parameter")]
  108. self.inputs = TypeDict(Parameter)
  109. self.outputs = TypeDict(Parameter)
  110. self.required = []
  111. # Insert parameters into input/output and required
  112. for par in self.params_list:
  113. if par.input:
  114. self.inputs[par.name] = par
  115. else:
  116. self.outputs[par.name] = par
  117. if par.required:
  118. self.required.append(par)
  119. #
  120. # extract flags from the xml
  121. #
  122. flags_list = [Flag(f) for f in tree.findall("flag")]
  123. self.flags = TypeDict(Flag)
  124. for flag in flags_list:
  125. self.flags[flag.name] = flag
  126. #
  127. # Add new attributes to the class
  128. #
  129. self.run_ = True
  130. self.finish_ = True
  131. self.env_ = None
  132. self.stdin_ = None
  133. self.stdin = None
  134. self.stdout_ = None
  135. self.stderr_ = None
  136. diz = {'name': 'stdin', 'required': False,
  137. 'multiple': False, 'type': 'all',
  138. 'value': None}
  139. self.inputs['stdin'] = Parameter(diz=diz)
  140. diz['name'] = 'stdout'
  141. self.outputs['stdout'] = Parameter(diz=diz)
  142. diz['name'] = 'stderr'
  143. self.outputs['stderr'] = Parameter(diz=diz)
  144. self.popen = None
  145. if args or kargs:
  146. self.__call__(*args, **kargs)
  147. def __call__(self, *args, **kargs):
  148. if not args and not kargs:
  149. self.run()
  150. return
  151. #
  152. # check for extra kargs, set attribute and remove from dictionary
  153. #
  154. if 'flags' in kargs:
  155. for flg in kargs['flags']:
  156. self.flags[flg].value = True
  157. del(kargs['flags'])
  158. if 'run_' in kargs:
  159. self.run_ = kargs['run_']
  160. del(kargs['run_'])
  161. if 'stdin_' in kargs:
  162. self.inputs['stdin'].value = kargs['stdin_']
  163. del(kargs['stdin_'])
  164. if 'stdout_' in kargs:
  165. self.outputs['stdout'].value = kargs['stdout_']
  166. del(kargs['stdout_'])
  167. if 'stderr_' in kargs:
  168. self.outputs['stderr'].value = kargs['stderr_']
  169. del(kargs['stderr_'])
  170. if 'env_' in kargs:
  171. self.env_ = kargs['env_']
  172. del(kargs['env_'])
  173. if 'finish_' in kargs:
  174. self.finish_ = kargs['finish_']
  175. del(kargs['finish_'])
  176. #
  177. # check args
  178. #
  179. for param, arg in zip(self.params_list, args):
  180. param.value = arg
  181. for key, val in kargs.items():
  182. if key in self.inputs:
  183. self.inputs[key].value = val
  184. elif key in self.outputs:
  185. self.outputs[key].value = val
  186. elif key in self.flags:
  187. # we need to add this, because some parameters (overwrite,
  188. # verbose and quiet) work like parameters
  189. self.flags[key].value = val
  190. else:
  191. raise ParameterError('%s is not a valid parameter.' % key)
  192. #
  193. # check reqire parameters
  194. #
  195. for par in self.required:
  196. if par.value is None:
  197. raise ParameterError(
  198. "Required parameter <%s> not set." % par.name)
  199. #
  200. # check if execute
  201. #
  202. if self.run_:
  203. self.run()
  204. def get_bash(self):
  205. return ' '.join(self.make_cmd())
  206. def get_python(self):
  207. prefix = self.name.split('.')[0]
  208. name = '_'.join(self.name.split('.')[1:])
  209. params = ', '.join([par.get_python() for par in self.params_list
  210. if par.get_python() != ''])
  211. special = ', '.join([flg.get_python()
  212. for flg in self.flags.values()
  213. if flg.special and flg.get_python() != ''])
  214. # pre name par flg special
  215. if self.flags and special:
  216. return "%s.%s(%s, flags=%r, %s)" % (prefix, name, params,
  217. self.flags, special)
  218. elif self.flags:
  219. return "%s.%s(%s, flags=%r)" % (prefix, name, params, self.flags)
  220. elif special:
  221. return "%s.%s(%s, %s)" % (prefix, name, params, special)
  222. else:
  223. return "%s.%s(%s)" % (prefix, name, params)
  224. def __str__(self):
  225. """!Return the command string that can be executed in a shell
  226. """
  227. return ' '.join(self.make_cmd())
  228. def __repr__(self):
  229. return "Module(%r)" % self.name
  230. @property
  231. def __doc__(self):
  232. """{cmd_name}({cmd_params})
  233. """
  234. head = DOC['head'].format(cmd_name=self.name,
  235. cmd_params=('\n' + # go to a new line
  236. # give space under the function name
  237. (' ' * (len(self.name) + 1))).join([', '.join(
  238. # transform each parameter in string
  239. [str(param) for param in line if param is not None])
  240. # make a list of parameters with only 3 param per line
  241. for line in izip_longest(*[iter(self.params_list)] * 3)]),)
  242. params = '\n'.join([par.__doc__ for par in self.params_list])
  243. flags = self.flags.__doc__
  244. return '\n'.join([head, params, DOC['flag_head'], flags, DOC['foot']])
  245. def get_dict(self):
  246. """!Return a dictionary that includes the name, all valid
  247. inputs, outputs and flags
  248. """
  249. dic = {}
  250. dic['name'] = self.name
  251. dic['inputs'] = [(k, v.value) for k, v in self.inputs.items()
  252. if v.value]
  253. dic['outputs'] = [(k, v.value) for k, v in self.outputs.items()
  254. if v.value]
  255. dic['flags'] = [flg for flg in self.flags if self.flags[flg].value]
  256. return dic
  257. def make_cmd(self):
  258. """!Create the commdn string that can be exceuted in a shell
  259. @return The command string
  260. """
  261. args = [self.name, ]
  262. for key in self.inputs:
  263. if self.inputs[key].value:
  264. args.append(self.inputs[key].get_bash())
  265. for key in self.outputs:
  266. if self.outputs[key].value:
  267. args.append(self.outputs[key].get_bash())
  268. for flg in self.flags:
  269. if self.flags[flg].value:
  270. args.append(str(self.flags[flg]))
  271. return args
  272. def run(self, node=None):
  273. """!Run the module
  274. This function will wait for the process to terminate
  275. in case finish_==True and sets up stdout and stderr.
  276. If finish_==False this function will return after starting
  277. the process. Use self.popen.communicate() of self.popen.wait()
  278. to wait for the process termination. The handling
  279. of stdout and stderr must then be done outside of this
  280. function.
  281. """
  282. if self.inputs['stdin'].value:
  283. self.stdin = self.inputs['stdin'].value
  284. self.stdin_ = subprocess.PIPE
  285. if self.outputs['stdout'].value:
  286. self.stdout_ = self.outputs['stdout'].value
  287. if self.outputs['stderr'].value:
  288. self.stderr_ = self.outputs['stderr'].value
  289. cmd = self.make_cmd()
  290. self.popen = subprocess.Popen(cmd,
  291. stdin=self.stdin_,
  292. stdout=self.stdout_,
  293. stderr=self.stderr_,
  294. env=self.env_)
  295. if self.finish_:
  296. stdout, stderr = self.popen.communicate(input=self.stdin)
  297. self.outputs['stdout'].value = stdout if stdout else ''
  298. self.outputs['stderr'].value = stderr if stderr else ''
  299. ###############################################################################
  300. if __name__ == "__main__":
  301. import doctest
  302. doctest.testmod()