__init__.py 21 KB

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