__init__.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  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. import fnmatch
  9. import re
  10. try:
  11. from collections import OrderedDict
  12. except ImportError:
  13. from pygrass.orderdict import OrderedDict
  14. from itertools import izip_longest
  15. from xml.etree.ElementTree import fromstring
  16. import grass
  17. from pygrass.errors import GrassError
  18. #
  19. # this dictionary is used to extract the value of interest from the xml
  20. # the lambda experssion is used to define small simple functions,
  21. # is equivalent to: ::
  22. #
  23. # def f(p):
  24. # return p.text.strip()
  25. #
  26. # and then we call f(p)
  27. #
  28. _GETFROMTAG = {
  29. 'description': lambda p: p.text.strip(),
  30. 'keydesc': lambda p: p.text.strip(),
  31. 'gisprompt': lambda p: dict(p.items()),
  32. 'default': lambda p: p.text.strip(),
  33. 'values': lambda p: [e.text.strip() for e in p.findall('value/name')],
  34. 'value': lambda p: None,
  35. 'guisection': lambda p: p.text.strip(),
  36. 'label': lambda p: p.text.strip(),
  37. 'suppress_required': lambda p: None,
  38. 'keywords': lambda p: p.text.strip(),
  39. }
  40. _GETTYPE = {
  41. 'string': str,
  42. 'integer': int,
  43. 'float': float,
  44. 'double': float,
  45. 'all': lambda x: x,
  46. }
  47. class ParameterError(Exception):
  48. pass
  49. class FlagError(Exception):
  50. pass
  51. def _element2dict(xparameter):
  52. diz = dict(xparameter.items())
  53. for p in xparameter:
  54. if p.tag in _GETFROMTAG:
  55. diz[p.tag] = _GETFROMTAG[p.tag](p)
  56. else:
  57. print('New tag: %s, ignored' % p.tag)
  58. return diz
  59. # dictionary used to create docstring for the objects
  60. _DOC = {
  61. #------------------------------------------------------------
  62. # head
  63. 'head': """{cmd_name}({cmd_params})
  64. Parameters
  65. ----------
  66. """,
  67. #------------------------------------------------------------
  68. # param
  69. 'param': """{name}: {default}{required}{multi}{ptype}
  70. {description}{values}""",
  71. #------------------------------------------------------------
  72. # flag_head
  73. 'flag_head': """
  74. Flags
  75. ------
  76. """,
  77. #------------------------------------------------------------
  78. # flag
  79. 'flag': """{name}: {default}
  80. {description}""",
  81. #------------------------------------------------------------
  82. # foot
  83. 'foot': """
  84. Special Parameters
  85. ------------------
  86. The Module class have some optional parameters which are distinct using a final
  87. underscore.
  88. run_: True, optional
  89. If True execute the module.
  90. finish_: True, optional
  91. If True wait untill the end of the module execution, and store the module
  92. outputs into stdout, stderr attributes of the class.
  93. stdin_: PIPE,
  94. Set the standard input
  95. """}
  96. class Parameter(object):
  97. def __init__(self, xparameter=None, diz=None):
  98. self._value = None
  99. diz = _element2dict(xparameter) if xparameter is not None else diz
  100. if diz is None:
  101. raise TypeError('Xparameter or diz are required')
  102. self.name = diz['name']
  103. self.required = True if diz['required'] == 'yes' else False
  104. self.multiple = True if diz['multiple'] == 'yes' else False
  105. # check the type
  106. if diz['type'] in _GETTYPE:
  107. self.type = _GETTYPE[diz['type']]
  108. self.typedesc = diz['type']
  109. self._type = _GETTYPE[diz['type']]
  110. else:
  111. raise TypeError('New type: %s, ignored' % diz['type'])
  112. self.description = diz.get('description', None)
  113. self.keydesc = diz.get('keydesc', None)
  114. #
  115. # values
  116. #
  117. if 'values' in diz:
  118. try:
  119. # chek if it's a range string: "3-30"
  120. isrange = re.match("(?P<min>\d+)-(?P<max>\d+)",
  121. diz['values'][0])
  122. if isrange:
  123. range_min, range_max = isrange.groups()
  124. self.values = range(int(range_min), int(range_max) + 1)
  125. self.isrange = diz['values'][0]
  126. else:
  127. self.values = [self._type(i) for i in diz['values']]
  128. self.isrange = False
  129. except TypeError:
  130. self.values = [self._type(i) for i in diz['values']]
  131. self.isrange = False
  132. #
  133. # default
  134. #
  135. self.default = self._type(
  136. diz['default']) if 'default' in diz else None
  137. if self.default is not None:
  138. self._value = self.default
  139. self.guisection = diz.get('guisection', None)
  140. #
  141. # gisprompt
  142. #
  143. if 'gisprompt' in diz:
  144. self.type = diz['gisprompt']['prompt']
  145. self.input = False if diz['gisprompt']['age'] == 'new' else True
  146. else:
  147. self.input = True
  148. def _get_value(self):
  149. return self._value
  150. def _set_value(self, value):
  151. if isinstance(value, list) or isinstance(value, tuple):
  152. if self.multiple:
  153. # check each value
  154. self._value = [self._type(val) for val in value]
  155. else:
  156. str_err = 'The Parameter <%s> does not accept multiple inputs'
  157. raise TypeError(str_err % self.name)
  158. elif self.typedesc == 'all':
  159. self._value = value
  160. elif isinstance(value, self._type):
  161. if hasattr(self, 'values'):
  162. if value in self.values:
  163. self._value = value
  164. else:
  165. raise ValueError('The Parameter <%s>, must be one of: %r' %
  166. (self.name, self.values))
  167. else:
  168. self._value = value
  169. else:
  170. str_err = 'The Parameter <%s>, require: %s, get: %s instead'
  171. raise TypeError(str_err % (self.name, self.typedesc, type(value)))
  172. # here the property function is used to transform value in an attribute
  173. # in this case we define which function must be use to get/set the value
  174. value = property(fget=_get_value, fset=_set_value)
  175. def get_bash(self):
  176. if isinstance(self._value, list) or isinstance(self._value, tuple):
  177. value = ','.join([str(v) for v in self._value])
  178. else:
  179. value = str(self._value)
  180. return """%s=%s""" % (self.name, value)
  181. def get_python(self):
  182. if not self.value:
  183. return ''
  184. return """%s=%r""" % (self.name, self._value)
  185. def __str__(self):
  186. return self.get_bash()
  187. def __repr__(self):
  188. str_repr = "Parameter <%s> (required:%s, type:%s, multiple:%s)"
  189. return str_repr % (self.name,
  190. "yes" if self.required else "no",
  191. self.type if self.type in (
  192. 'raster', 'vector') else self.typedesc,
  193. "yes" if self.multiple else "no")
  194. # here we use property with a decorator, in this way we mask a method as
  195. # a class attribute
  196. @property
  197. def __doc__(self):
  198. """Return the docstring of the parameter
  199. {name}: {default}{required}{multi}{ptype}
  200. {description}{values}"","""
  201. if hasattr(self, 'values'):
  202. if self.isrange:
  203. vals = self.isrange
  204. else:
  205. vals = ', '.join([repr(val) for val in self.values])
  206. else:
  207. vals = False
  208. return _DOC['param'].format(name=self.name,
  209. default=repr(self.default) + ', ' if self.default else '',
  210. required='required, ' if self.required else 'optional, ',
  211. multi='multi' if self.multiple else '',
  212. ptype=self.typedesc, description=self.description,
  213. values='\n Values: {0}'.format(vals) if vals else '')
  214. class TypeDict(OrderedDict):
  215. def __init__(self, dict_type, *args, **kargs):
  216. self.type = dict_type
  217. super(TypeDict, self).__init__(*args, **kargs)
  218. def __setitem__(self, key, value):
  219. if isinstance(value, self.type):
  220. super(TypeDict, self).__setitem__(key, value)
  221. else:
  222. cl = repr(self.type).translate(None, "'<> ").split('.')
  223. str_err = 'The value: %r is not a %s object'
  224. raise TypeError(str_err % (value, cl[-1].title()))
  225. @property
  226. def __doc__(self):
  227. return '\n'.join([self.__getitem__(obj).__doc__
  228. for obj in self.__iter__()])
  229. def __call__(self):
  230. return [self.__getitem__(obj) for obj in self.__iter__()]
  231. class Flag(object):
  232. def __init__(self, xflag=None, diz=None):
  233. self.value = None
  234. diz = _element2dict(xflag) if xflag is not None else diz
  235. self.name = diz['name']
  236. self.special = True if self.name in (
  237. 'verbose', 'overwrite', 'quiet', 'run') else False
  238. self.description = diz['description']
  239. self.default = diz.get('default', None)
  240. self.guisection = diz.get('guisection', None)
  241. def get_bash(self):
  242. if self.value:
  243. if self.special:
  244. return '--%s' % self.name[0]
  245. else:
  246. return '-%s' % self.name
  247. else:
  248. return ''
  249. def get_python(self):
  250. if self.value:
  251. if self.special:
  252. return '%s=True' % self.name
  253. else:
  254. return self.name
  255. else:
  256. return ''
  257. def __str__(self):
  258. return self.get_bash()
  259. def __repr__(self):
  260. return "Flag <%s> (%s)" % (self.name, self.description)
  261. @property
  262. def __doc__(self):
  263. """
  264. {name}: {default}
  265. {description}"""
  266. return _DOC['flag'].format(name=self.name,
  267. default=repr(self.default),
  268. description=self.description)
  269. class Module(object):
  270. """
  271. Python allow developers to not specify all the arguments and
  272. keyword arguments of a method or function.
  273. ::
  274. def f(*args):
  275. for arg in args:
  276. print arg
  277. therefore if we call the function like: ::
  278. >>> f('grass', 'gis', 'modules')
  279. grass
  280. gis
  281. modules
  282. or we can define a new list: ::
  283. >>> words = ['grass', 'gis', 'modules']
  284. >>> f(*words)
  285. grass
  286. gis
  287. modules
  288. we can do the same with keyword arguments, rewrite the above function: ::
  289. def f(*args, **kargs):
  290. for arg in args:
  291. print arg
  292. for key, value in kargs.items():
  293. print "%s = %r" % (key, value)
  294. now we can use the new function, with: ::
  295. >>> f('grass', 'gis', 'modules', os = 'linux', language = 'python')
  296. grass
  297. gis
  298. modules
  299. os = 'linux'
  300. language = 'python'
  301. or, as before we can, define a dictionary and give the dictionary to
  302. the function, like: ::
  303. >>> keywords = {'os' : 'linux', 'language' : 'python'}
  304. >>> f(*words, **keywords)
  305. grass
  306. gis
  307. modules
  308. os = 'linux'
  309. language = 'python'
  310. In the Module class we heavily use this language feature to pass arguments
  311. and keyword arguments to the grass module.
  312. """
  313. def __init__(self, cmd, *args, **kargs):
  314. self.name = cmd
  315. try:
  316. # call the command with --interface-description
  317. get_cmd_xml = subprocess.Popen([cmd, "--interface-description"],
  318. stdout=subprocess.PIPE)
  319. except OSError:
  320. str_err = "Module %r not found, please check that the module exist"
  321. raise GrassError(str_err % self.name)
  322. # get the xml of the module
  323. self.xml = get_cmd_xml.communicate()[0]
  324. # transform and parse the xml into an Element class:
  325. # http://docs.python.org/library/xml.etree.elementtree.html
  326. tree = fromstring(self.xml)
  327. for e in tree:
  328. if e.tag not in ('parameter', 'flag'):
  329. self.__setattr__(e.tag, _GETFROMTAG[e.tag](e))
  330. #
  331. # extract parameters from the xml
  332. #
  333. self.params_list = [Parameter(p) for p in tree.findall("parameter")]
  334. self.inputs = TypeDict(Parameter)
  335. self.outputs = TypeDict(Parameter)
  336. self.required = []
  337. # Insert parameters into input/output and required
  338. for par in self.params_list:
  339. if par.input:
  340. self.inputs[par.name] = par
  341. else:
  342. self.outputs[par.name] = par
  343. if par.required:
  344. self.required.append(par)
  345. #
  346. # extract flags from the xml
  347. #
  348. flags_list = [Flag(f) for f in tree.findall("flag")]
  349. self.flags_dict = TypeDict(Flag)
  350. for flag in flags_list:
  351. self.flags_dict[flag.name] = flag
  352. #
  353. # Add new attributes to the class
  354. #
  355. self.run_ = True
  356. self.finish_ = True
  357. self.stdin_ = None
  358. self.stdin = None
  359. self.stdout_ = None
  360. self.stderr_ = None
  361. diz = {'name': 'stdin', 'required': False,
  362. 'multiple': False, 'type': 'all',
  363. 'value': None}
  364. self.inputs['stdin'] = Parameter(diz=diz)
  365. diz['name'] = 'stdout'
  366. self.outputs['stdout'] = Parameter(diz=diz)
  367. diz['name'] = 'stderr'
  368. self.outputs['stderr'] = Parameter(diz=diz)
  369. self.popen = None
  370. if args or kargs:
  371. self.__call__(*args, **kargs)
  372. def _get_flags(self):
  373. return ''.join([flg.get_python() for flg in self.flags_dict.values()
  374. if not flg.special])
  375. def _set_flags(self, value):
  376. if isinstance(value, str):
  377. if value == '':
  378. for flg in self.flags_dict.values():
  379. if not flg.special:
  380. flg.value = False
  381. else:
  382. flgs = [flg.name for flg in self.flags_dict.values()
  383. if not flg.special]
  384. # we need to check if the flag is valid, special flags are not
  385. # allow
  386. for val in value:
  387. if val in flgs:
  388. self.flags_dict[val].value = True
  389. else:
  390. str_err = 'Flag not valid: %r, valid flag are: %r'
  391. raise ValueError(str_err % (val, flgs))
  392. else:
  393. raise TypeError('The flags attribute must be a string')
  394. flags = property(fget=_get_flags, fset=_set_flags)
  395. def __call__(self, *args, **kargs):
  396. if not args and not kargs:
  397. self.run()
  398. return
  399. #
  400. # check for extra kargs, set attribute and remove from dictionary
  401. #
  402. if 'flags' in kargs:
  403. self.flags = kargs['flags']
  404. del(kargs['flags'])
  405. if 'run_' in kargs:
  406. self.run_ = kargs['run_']
  407. del(kargs['run_'])
  408. if 'stdin_' in kargs:
  409. self.inputs['stdin'].value = kargs['stdin_']
  410. del(kargs['stdin_'])
  411. if 'stdout_' in kargs:
  412. self.outputs['stdout'].value = kargs['stdout_']
  413. del(kargs['stdout_'])
  414. if 'stderr_' in kargs:
  415. self.outputs['stdout'].value = kargs['stderr_']
  416. del(kargs['stderr_'])
  417. if 'finish_' in kargs:
  418. self.finish_ = kargs['finish_']
  419. del(kargs['finish_'])
  420. #
  421. # check args
  422. #
  423. for param, arg in zip(self.params_list, args):
  424. param.value = arg
  425. for key, val in kargs.items():
  426. if key in self.inputs:
  427. self.inputs[key].value = val
  428. elif key in self.outputs:
  429. self.outputs[key].value = val
  430. elif key in self.flags_dict:
  431. # we need to add this, because some parameters (overwrite,
  432. # verbose and quiet) work like parameters
  433. self.flags_dict[key].value = val
  434. else:
  435. raise ParameterError('%s is not a valid parameter.' % key)
  436. #
  437. # check reqire parameters
  438. #
  439. for par in self.required:
  440. if par.value is None:
  441. raise ParameterError(
  442. "Required parameter <%s> not set." % par.name)
  443. #
  444. # check flags parameters
  445. #
  446. if self.flags:
  447. # check each character in flags
  448. for flag in self.flags:
  449. if flag in self.flags_dict:
  450. self.flags_dict[flag].value = True
  451. else:
  452. raise FlagError('Flag "%s" not valid.' % flag)
  453. #
  454. # check if execute
  455. #
  456. if self.run_:
  457. self.run()
  458. def get_bash(self):
  459. return ' '.join(self.make_cmd())
  460. def get_python(self):
  461. prefix = self.name.split('.')[0]
  462. name = '_'.join(self.name.split('.')[1:])
  463. params = ', '.join([par.get_python() for par in self.params_list
  464. if par.get_python() != ''])
  465. special = ', '.join([flg.get_python()
  466. for flg in self.flags_dict.values()
  467. if flg.special and flg.get_python() != ''])
  468. # pre name par flg special
  469. if self.flags and special:
  470. return "%s.%s(%s, flags=%r, %s)" % (prefix, name, params,
  471. self.flags, special)
  472. elif self.flags:
  473. return "%s.%s(%s, flags=%r)" % (prefix, name, params, self.flags)
  474. elif special:
  475. return "%s.%s(%s, %s)" % (prefix, name, params, special)
  476. else:
  477. return "%s.%s(%s)" % (prefix, name, params)
  478. def __str__(self):
  479. return ' '.join(self.make_cmd())
  480. def __repr__(self):
  481. return "Module(%r)" % self.name
  482. @property
  483. def __doc__(self):
  484. """{cmd_name}({cmd_params})
  485. """
  486. head = _DOC['head'].format(cmd_name=self.name,
  487. cmd_params=('\n' + # go to a new line
  488. # give space under the function name
  489. (' ' * (len(self.name) + 1))).join([', '.join(
  490. # transform each parameter in string
  491. [str(param) for param in line if param is not None])
  492. # make a list of parameters with only 3 param per line
  493. for line in izip_longest(*[iter(self.params_list)] * 3)]),)
  494. params = '\n'.join([par.__doc__ for par in self.params_list])
  495. flags = self.flags_dict.__doc__
  496. return '\n'.join([head, params, _DOC['flag_head'], flags])
  497. def make_cmd(self):
  498. args = [self.name, ]
  499. for par in self.params_list:
  500. if par.value is not None:
  501. args.append(str(par))
  502. for flg in self.flags_dict:
  503. if self.flags_dict[flg].value:
  504. args.append(str(self.flags_dict[flg]))
  505. return args
  506. def run(self, node=None):
  507. if self.inputs['stdin'].value:
  508. self.stdin = self.inputs['stdin'].value
  509. self.stdin_ = subprocess.PIPE
  510. if self.outputs['stdout'].value:
  511. self.stdout_ = self.outputs['stdout'].value
  512. if self.outputs['stderr'].value:
  513. self.stderr_ = self.outputs['stderr'].value
  514. cmd = self.make_cmd()
  515. self.popen = subprocess.Popen(cmd,
  516. stdin=self.stdin_,
  517. stdout=self.stdout_,
  518. stderr=self.stderr_)
  519. if self.finish_:
  520. self.popen.wait()
  521. stdout, stderr = self.popen.communicate(input=self.stdin)
  522. self.outputs['stdout'].value = stdout if stdout else ''
  523. self.outputs['stderr'].value = stderr if stderr else ''
  524. _CMDS = list(grass.script.core.get_commands()[0])
  525. _CMDS.sort()
  526. class MetaModule(object):
  527. def __init__(self, prefix):
  528. self.prefix = prefix
  529. def __dir__(self):
  530. return [mod[(len(self.prefix) + 1):].replace('.', '_')
  531. for mod in fnmatch.filter(_CMDS, "%s.*" % self.prefix)]
  532. def __getattr__(self, name):
  533. return Module('%s.%s' % (self.prefix, name.replace('_', '.')))
  534. # http://grass.osgeo.org/grass70/manuals/html70_user/full_index.html
  535. #[ d.* | db.* | g.* | i.* | m.* | ps.* | r.* | r3.* | t.* | v.* ]
  536. #
  537. # d.* display commands
  538. # db.* database commands
  539. # g.* general commands
  540. # i.* imagery commands
  541. # m.* miscellaneous commands
  542. # ps.* postscript commands
  543. # r.* raster commands
  544. # r3.* raster3D commands
  545. # t.* temporal commands
  546. # v.* vector commands
  547. display = MetaModule('d')
  548. database = MetaModule('db')
  549. general = MetaModule('g')
  550. imagery = MetaModule('i')
  551. miscellaneous = MetaModule('m')
  552. postscript = MetaModule('ps')
  553. raster = MetaModule('r')
  554. raster3D = MetaModule('r3')
  555. temporal = MetaModule('t')
  556. vector = MetaModule('v')