1 # -*- encoding: utf-8 -*-
3 import time # used to eval time.strftime expressions
4 from datetime import datetime, timedelta
7 import openerp.pooler as pooler
9 from config import config
13 # YAML import needs both safe and unsafe eval, but let's
16 from safe_eval import safe_eval as eval
18 logger_channel = 'tests'
20 class YamlImportException(Exception):
23 class YamlImportAbortion(Exception):
26 def _is_yaml_mapping(node, tag_constructor):
27 value = isinstance(node, types.DictionaryType) \
28 and len(node.keys()) == 1 \
29 and isinstance(node.keys()[0], tag_constructor)
33 return isinstance(node, types.StringTypes)
36 return isinstance(node, yaml_tag.Assert) \
37 or _is_yaml_mapping(node, yaml_tag.Assert)
40 return _is_yaml_mapping(node, yaml_tag.Record)
43 return _is_yaml_mapping(node, yaml_tag.Python)
45 def is_menuitem(node):
46 return isinstance(node, yaml_tag.Menuitem) \
47 or _is_yaml_mapping(node, yaml_tag.Menuitem)
49 def is_function(node):
50 return isinstance(node, yaml_tag.Function) \
51 or _is_yaml_mapping(node, yaml_tag.Function)
54 return isinstance(node, yaml_tag.Report)
56 def is_workflow(node):
57 return isinstance(node, yaml_tag.Workflow)
59 def is_act_window(node):
60 return isinstance(node, yaml_tag.ActWindow)
63 return isinstance(node, yaml_tag.Delete)
66 return isinstance(node, yaml_tag.Context)
69 return isinstance(node, yaml_tag.Url)
72 return isinstance(node, yaml_tag.Eval)
75 return isinstance(node, yaml_tag.Ref) \
76 or _is_yaml_mapping(node, yaml_tag.Ref)
79 return _is_yaml_mapping(node, yaml_tag.IrSet)
82 return isinstance(node, basestring)
84 class TestReport(object):
88 def record(self, success, severity):
90 Records the result of an assertion for the failed/success count.
93 if severity in self._report:
94 self._report[severity][success] += 1
96 self._report[severity] = {success: 1, not success: 0}
101 res.append('\nAssertions report:\nLevel\tsuccess\tfailure')
102 success = failure = 0
103 for severity in self._report:
104 res.append("%s\t%s\t%s" % (severity, self._report[severity][True], self._report[severity][False]))
105 success += self._report[severity][True]
106 failure += self._report[severity][False]
107 res.append("total\t%s\t%s" % (success, failure))
108 res.append("end of report (%s assertion(s) checked)" % success + failure)
109 return "\n".join(res)
111 class RecordDictWrapper(dict):
113 Used to pass a record as locals in eval:
114 records do not strictly behave like dict, so we force them to.
116 def __init__(self, record):
118 def __getitem__(self, key):
119 if key in self.record:
120 return self.record[key]
121 return dict.__getitem__(self, key)
123 class YamlInterpreter(object):
124 def __init__(self, cr, module, id_map, mode, filename, noupdate=False):
129 self.filename = filename
130 self.assert_report = TestReport()
131 self.noupdate = noupdate
132 self.logger = logging.getLogger("%s.%s" % (logger_channel, self.module))
133 self.pool = pooler.get_pool(cr.dbname)
135 self.context = {} # opererp context
136 self.eval_context = {'ref': self._ref(),
137 '_ref': self._ref(), # added '_ref' so that record['ref'] is possible
139 'datetime': datetime,
140 'timedelta': timedelta}
143 return lambda xml_id: self.get_id(xml_id)
145 def get_model(self, model_name):
146 model = self.pool.get(model_name)
147 assert model, "The model %s does not exist." % (model_name,)
150 def validate_xml_id(self, xml_id):
153 module, id = xml_id.split('.', 1)
154 assert '.' not in id, "The ID reference '%s' must contains maximum one dot.\n" \
155 "It is used to refer to other modules ID, in the form: module.record_id" \
157 if module != self.module:
158 module_count = self.pool.get('ir.module.module').search_count(self.cr, self.uid, \
159 ['&', ('name', '=', module), ('state', 'in', ['installed'])])
160 assert module_count == 1, 'The ID "%s" refers to an uninstalled module.' % (xml_id,)
161 if len(id) > 64: # TODO where does 64 come from (DB is 128)? should be a constant or loaded form DB
162 self.logger.log(logging.ERROR, 'id: %s is to long (max: 64)', id)
164 def get_id(self, xml_id):
166 raise YamlImportException("The xml_id should be a non empty string.")
167 if isinstance(xml_id, types.IntType):
169 elif xml_id in self.id_map:
170 id = self.id_map[xml_id]
173 module, checked_xml_id = xml_id.split('.', 1)
176 checked_xml_id = xml_id
178 _, id = self.pool.get('ir.model.data').get_object_reference(self.cr, self.uid, module, checked_xml_id)
179 self.id_map[xml_id] = id
180 except ValueError, e:
181 raise YamlImportException(""" This Yaml file appears to depend on data that is missing. This often happens for
182 tests that belong to a module's test suite and depend on each other
183 (they are supposed to be executed in batch, not standalone).
184 You might solve the issue by first forcing the other tests to commit
185 their changes using --test-commit during a module update.""")
186 self.logger.exception(e)
190 def get_context(self, node, eval_dict):
191 context = self.context.copy()
193 context.update(eval(node.context, eval_dict))
196 def isnoupdate(self, node):
197 return self.noupdate or node.noupdate or False
199 def _get_first_result(self, results, default=False):
202 if isinstance(value, types.TupleType):
208 def process_comment(self, node):
211 def _log_assert_failure(self, severity, msg, *args):
212 if isinstance(severity, types.StringTypes):
213 levelname = severity.strip().upper()
214 level = logging.getLevelName(levelname)
217 levelname = logging.getLevelName(level)
218 self.assert_report.record(False, levelname)
219 self.logger.log(level, msg, *args)
220 if level >= config['assert_exit_level']:
221 raise YamlImportAbortion('Severe assertion failure (%s), aborting.' % levelname)
224 def _get_assertion_id(self, assertion):
226 ids = [self.get_id(assertion.id)]
227 elif assertion.search:
228 q = eval(assertion.search, self.eval_context)
229 ids = self.pool.get(assertion.model).search(self.cr, self.uid, q, context=assertion.context)
231 raise YamlImportException('Nothing to assert: you must give either an id or a search criteria.')
234 def process_assert(self, node):
235 if isinstance(node, dict):
236 assertion, expressions = node.items()[0]
238 assertion, expressions = node, []
240 if self.isnoupdate(assertion) and self.mode != 'init':
241 self.logger.warn('This assertion was not evaluated ("%s").' % assertion.string)
243 model = self.get_model(assertion.model)
244 ids = self._get_assertion_id(assertion)
245 if assertion.count is not None and len(ids) != assertion.count:
246 msg = 'assertion "%s" failed!\n' \
247 ' Incorrect search count:\n' \
248 ' expected count: %d\n' \
249 ' obtained count: %d\n'
250 args = (assertion.string, assertion.count, len(ids))
251 self._log_assert_failure(assertion.severity, msg, *args)
253 context = self.get_context(assertion, self.eval_context)
255 record = model.browse(self.cr, self.uid, id, context)
256 for test in expressions:
258 success = unsafe_eval(test, self.eval_context, RecordDictWrapper(record))
260 self.logger.debug('Exception during evaluation of !assert block in yaml_file %s.', self.filename, exc_info=True)
261 raise YamlImportAbortion(e)
263 msg = 'Assertion "%s" FAILED\ntest: %s\n'
264 args = (assertion.string, test)
265 for aop in ('==', '!=', '<>', 'in', 'not in', '>=', '<=', '>', '<'):
267 left, right = test.split(aop,1)
271 lmsg = unsafe_eval(left, self.eval_context, RecordDictWrapper(record))
276 rmsg = unsafe_eval(right, self.eval_context, RecordDictWrapper(record))
280 msg += 'values: ! %s %s %s'
281 args += ( lmsg, aop, rmsg )
284 self._log_assert_failure(assertion.severity, msg, *args)
286 else: # all tests were successful for this assertion tag (no break)
287 self.assert_report.record(True, assertion.severity)
289 def _coerce_bool(self, value, default=False):
290 if isinstance(value, types.BooleanType):
292 if isinstance(value, types.StringTypes):
293 b = value.strip().lower() not in ('0', 'false', 'off', 'no')
294 elif isinstance(value, types.IntType):
300 def create_osv_memory_record(self, record, fields):
301 model = self.get_model(record.model)
302 record_dict = self._create_record(model, fields)
303 id_new=model.create(self.cr, self.uid, record_dict, context=self.context)
304 self.id_map[record.id] = int(id_new)
307 def process_record(self, node):
308 import openerp.osv as osv
309 record, fields = node.items()[0]
310 model = self.get_model(record.model)
311 if isinstance(model, osv.osv.osv_memory):
312 record_dict=self.create_osv_memory_record(record, fields)
314 self.validate_xml_id(record.id)
315 if self.isnoupdate(record) and self.mode != 'init':
316 id = self.pool.get('ir.model.data')._update_dummy(self.cr, 1, record.model, self.module, record.id)
317 # check if the resource already existed at the last update
319 self.id_map[record] = int(id)
322 if not self._coerce_bool(record.forcecreate):
325 record_dict = self._create_record(model, fields)
326 self.logger.debug("RECORD_DICT %s" % record_dict)
327 #context = self.get_context(record, self.eval_context)
328 context = record.context #TOFIX: record.context like {'withoutemployee':True} should pass from self.eval_context. example: test_project.yml in project module
329 id = self.pool.get('ir.model.data')._update(self.cr, 1, record.model, \
330 self.module, record_dict, record.id, noupdate=self.isnoupdate(record), mode=self.mode, context=context)
331 self.id_map[record.id] = int(id)
332 if config.get('import_partial'):
335 def _create_record(self, model, fields):
337 for field_name, expression in fields.items():
338 field_value = self._eval_field(model, field_name, expression)
339 record_dict[field_name] = field_value
342 def process_ref(self, node, column=None):
345 model_name = node.model
347 model_name = column._obj
349 raise YamlImportException('You need to give a model for the search, or a column to infer it.')
350 model = self.get_model(model_name)
351 q = eval(node.search, self.eval_context)
352 ids = model.search(self.cr, self.uid, q)
354 instances = model.browse(self.cr, self.uid, ids)
355 value = [inst[node.use] for inst in instances]
359 value = self.get_id(node.id)
364 def process_eval(self, node):
365 return eval(node.expression, self.eval_context)
367 def _eval_field(self, model, field_name, expression):
368 # TODO this should be refactored as something like model.get_field() in bin/osv
369 if field_name in model._columns:
370 column = model._columns[field_name]
371 elif field_name in model._inherit_fields:
372 column = model._inherit_fields[field_name][2]
374 raise KeyError("Object '%s' does not contain field '%s'" % (model, field_name))
375 if is_ref(expression):
376 elements = self.process_ref(expression, column)
377 if column._type in ("many2many", "one2many"):
378 value = [(6, 0, elements)]
380 value = self._get_first_result(elements)
381 elif column._type == "many2one":
382 value = self.get_id(expression)
383 elif column._type == "one2many":
384 other_model = self.get_model(column._obj)
385 value = [(0, 0, self._create_record(other_model, fields)) for fields in expression]
386 elif column._type == "many2many":
387 ids = [self.get_id(xml_id) for xml_id in expression]
388 value = [(6, 0, ids)]
389 elif column._type == "date" and is_string(expression):
390 # enforce ISO format for string date values, to be locale-agnostic during tests
391 time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT)
393 elif column._type == "datetime" and is_string(expression):
394 # enforce ISO format for string datetime values, to be locale-agnostic during tests
395 time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT)
398 if is_eval(expression):
399 value = self.process_eval(expression)
402 # raise YamlImportException('Unsupported column "%s" or value %s:%s' % (field_name, type(expression), expression))
405 def process_context(self, node):
406 self.context = node.__dict__
408 self.uid = self.get_id(node.uid)
410 self.noupdate = node.noupdate
412 def process_python(self, node):
414 self.logger.log(logging.TEST, msg, *args)
415 python, statements = node.items()[0]
416 model = self.get_model(python.model)
417 statements = statements.replace("\r\n", "\n")
418 code_context = {'model': model, 'cr': self.cr, 'uid': self.uid, 'log': log, 'context': self.context}
419 code_context.update({'self': model}) # remove me when no !python block test uses 'self' anymore
421 code_obj = compile(statements, self.filename, 'exec')
422 unsafe_eval(code_obj, {'ref': self.get_id}, code_context)
423 except AssertionError, e:
424 self._log_assert_failure(python.severity, 'AssertionError in Python code %s: %s', python.name, e)
427 self.logger.debug('Exception during evaluation of !python block in yaml_file %s.', self.filename, exc_info=True)
430 self.assert_report.record(True, python.severity)
432 def process_workflow(self, node):
433 workflow, values = node.items()[0]
434 if self.isnoupdate(workflow) and self.mode != 'init':
437 id = self.get_id(workflow.ref)
440 raise YamlImportException('You must define a child node if you do not give a ref.')
441 if not len(values) == 1:
442 raise YamlImportException('Only one child node is accepted (%d given).' % len(values))
444 if not 'model' in value and (not 'eval' in value or not 'search' in value):
445 raise YamlImportException('You must provide a "model" and an "eval" or "search" to evaluate.')
446 value_model = self.get_model(value['model'])
447 local_context = {'obj': lambda x: value_model.browse(self.cr, self.uid, x, context=self.context)}
448 local_context.update(self.id_map)
449 id = eval(value['eval'], self.eval_context, local_context)
451 if workflow.uid is not None:
455 self.cr.execute('select distinct signal from wkf_transition')
456 signals=[x['signal'] for x in self.cr.dictfetchall()]
457 if workflow.action not in signals:
458 raise YamlImportException('Incorrect action %s. No such action defined' % workflow.action)
459 import openerp.netsvc as netsvc
460 wf_service = netsvc.LocalService("workflow")
461 wf_service.trg_validate(uid, workflow.model, id, workflow.action, self.cr)
463 def _eval_params(self, model, params):
465 for i, param in enumerate(params):
466 if isinstance(param, types.ListType):
467 value = self._eval_params(model, param)
469 value = self.process_ref(param)
471 value = self.process_eval(param)
472 elif isinstance(param, types.DictionaryType): # supports XML syntax
473 param_model = self.get_model(param.get('model', model))
474 if 'search' in param:
475 q = eval(param['search'], self.eval_context)
476 ids = param_model.search(self.cr, self.uid, q)
477 value = self._get_first_result(ids)
478 elif 'eval' in param:
479 local_context = {'obj': lambda x: param_model.browse(self.cr, self.uid, x, self.context)}
480 local_context.update(self.id_map)
481 value = eval(param['eval'], self.eval_context, local_context)
483 raise YamlImportException('You must provide either a !ref or at least a "eval" or a "search" to function parameter #%d.' % i)
485 value = param # scalar value
489 def process_function(self, node):
490 function, params = node.items()[0]
491 if self.isnoupdate(function) and self.mode != 'init':
493 model = self.get_model(function.model)
494 context = self.get_context(function, self.eval_context)
496 args = self.process_eval(function.eval)
498 args = self._eval_params(function.model, params)
499 method = function.name
500 getattr(model, method)(self.cr, self.uid, *args)
502 def _set_group_values(self, node, values):
504 group_names = node.groups.split(',')
506 for group in group_names:
507 if group.startswith('-'):
508 group_id = self.get_id(group[1:])
509 groups_value.append((3, group_id))
511 group_id = self.get_id(group)
512 groups_value.append((4, group_id))
513 values['groups_id'] = groups_value
515 def process_menuitem(self, node):
516 self.validate_xml_id(node.id)
520 self.cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (node.name,))
521 res = self.cr.fetchone()
522 values = {'parent_id': parent_id, 'name': node.name}
524 parent_id = self.get_id(node.parent)
525 values = {'parent_id': parent_id}
527 values['name'] = node.name
529 res = [ self.get_id(node.id) ]
530 except: # which exception ?
534 action_type = node.type or 'act_window'
536 "act_window": 'STOCK_NEW',
537 "report.xml": 'STOCK_PASTE',
538 "wizard": 'STOCK_EXECUTE',
539 "url": 'STOCK_JUMP_TO',
541 values['icon'] = icons.get(action_type, 'STOCK_NEW')
542 if action_type == 'act_window':
543 action_id = self.get_id(node.action)
544 self.cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (action_id,))
545 ir_act_window_result = self.cr.fetchone()
546 assert ir_act_window_result, "No window action defined for this id %s !\n" \
547 "Verify that this is a window action or add a type argument." % (node.action,)
548 action_type, action_mode, action_name, view_id, target = ir_act_window_result
550 self.cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (view_id,))
551 # TODO guess why action_mode is ir_act_window.view_mode above and ir_ui_view.type here
552 action_mode = self.cr.fetchone()
553 self.cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (action_id,))
555 action_mode = self.cr.fetchone()
556 if action_type == 'tree':
557 values['icon'] = 'STOCK_INDENT'
558 elif action_mode and action_mode.startswith('tree'):
559 values['icon'] = 'STOCK_JUSTIFY_FILL'
560 elif action_mode and action_mode.startswith('graph'):
561 values['icon'] = 'terp-graph'
562 elif action_mode and action_mode.startswith('calendar'):
563 values['icon'] = 'terp-calendar'
565 values['icon'] = 'STOCK_EXECUTE'
566 if not values.get('name', False):
567 values['name'] = action_name
568 elif action_type == 'wizard':
569 action_id = self.get_id(node.action)
570 self.cr.execute('select name from ir_act_wizard where id=%s', (action_id,))
571 ir_act_wizard_result = self.cr.fetchone()
572 if (not values.get('name', False)) and ir_act_wizard_result:
573 values['name'] = ir_act_wizard_result[0]
575 raise YamlImportException("Unsupported type '%s' in menuitem tag." % action_type)
577 values['sequence'] = node.sequence
579 values['icon'] = node.icon
581 self._set_group_values(node, values)
583 pid = self.pool.get('ir.model.data')._update(self.cr, 1, \
584 'ir.ui.menu', self.module, values, node.id, mode=self.mode, \
585 noupdate=self.isnoupdate(node), res_id=res and res[0] or False)
587 if node.id and parent_id:
588 self.id_map[node.id] = int(parent_id)
590 if node.action and pid:
591 action_type = node.type or 'act_window'
592 action_id = self.get_id(node.action)
593 action = "ir.actions.%s,%d" % (action_type, action_id)
594 self.pool.get('ir.model.data').ir_set(self.cr, 1, 'action', \
595 'tree_but_open', 'Menuitem', [('ir.ui.menu', int(parent_id))], action, True, True, xml_id=node.id)
597 def process_act_window(self, node):
598 assert getattr(node, 'id'), "Attribute %s of act_window is empty !" % ('id',)
599 assert getattr(node, 'name'), "Attribute %s of act_window is empty !" % ('name',)
600 assert getattr(node, 'res_model'), "Attribute %s of act_window is empty !" % ('res_model',)
601 self.validate_xml_id(node.id)
604 view_id = self.get_id(node.view)
607 context = eval(str(node.context), self.eval_context)
610 'type': node.type or 'ir.actions.act_window',
612 'domain': node.domain,
614 'res_model': node.res_model,
615 'src_model': node.src_model,
616 'view_type': node.view_type or 'form',
617 'view_mode': node.view_mode or 'tree,form',
620 'auto_refresh': node.auto_refresh,
621 'multi': getattr(node, 'multi', False),
624 self._set_group_values(node, values)
627 values['target'] = node.target
628 id = self.pool.get('ir.model.data')._update(self.cr, 1, \
629 'ir.actions.act_window', self.module, values, node.id, mode=self.mode)
630 self.id_map[node.id] = int(id)
633 keyword = 'client_action_relate'
634 value = 'ir.actions.act_window,%s' % id
635 replace = node.replace or True
636 self.pool.get('ir.model.data').ir_set(self.cr, 1, 'action', keyword, \
637 node.id, [node.src_model], value, replace=replace, noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id)
638 # TODO add remove ir.model.data
640 def process_delete(self, node):
641 assert getattr(node, 'model'), "Attribute %s of delete tag is empty !" % ('model',)
642 if self.pool.get(node.model):
644 ids = self.pool.get(node.model).search(self.cr, self.uid, eval(node.search, self.eval_context))
646 ids = [self.get_id(node.id)]
648 self.pool.get(node.model).unlink(self.cr, self.uid, ids)
649 self.pool.get('ir.model.data')._unlink(self.cr, 1, node.model, ids)
651 self.logger.log(logging.TEST, "Record not deleted.")
653 def process_url(self, node):
654 self.validate_xml_id(node.id)
656 res = {'name': node.name, 'url': node.url, 'target': node.target}
658 id = self.pool.get('ir.model.data')._update(self.cr, 1, \
659 "ir.actions.url", self.module, res, node.id, mode=self.mode)
660 self.id_map[node.id] = int(id)
662 if (not node.menu or eval(node.menu)) and id:
663 keyword = node.keyword or 'client_action_multi'
664 value = 'ir.actions.url,%s' % id
665 replace = node.replace or True
666 self.pool.get('ir.model.data').ir_set(self.cr, 1, 'action', \
667 keyword, node.url, ["ir.actions.url"], value, replace=replace, \
668 noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id)
670 def process_ir_set(self, node):
671 if not self.mode == 'init':
673 _, fields = node.items()[0]
675 for fieldname, expression in fields.items():
676 if is_eval(expression):
677 value = eval(expression.expression, self.eval_context)
680 res[fieldname] = value
681 self.pool.get('ir.model.data').ir_set(self.cr, 1, res['key'], res['key2'], \
682 res['name'], res['models'], res['value'], replace=res.get('replace',True), \
683 isobject=res.get('isobject', False), meta=res.get('meta',None))
685 def process_report(self, node):
687 for dest, f in (('name','string'), ('model','model'), ('report_name','name')):
688 values[dest] = getattr(node, f)
689 assert values[dest], "Attribute %s of report is empty !" % (f,)
690 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
691 if getattr(node, field):
692 values[dest] = getattr(node, field)
694 values['auto'] = eval(node.auto)
696 sxw_file = misc.file_open(node.sxw)
698 sxw_content = sxw_file.read()
699 values['report_sxw_content'] = sxw_content
703 values['header'] = eval(node.header)
704 values['multi'] = node.multi and eval(node.multi)
706 self.validate_xml_id(xml_id)
708 self._set_group_values(node, values)
710 id = self.pool.get('ir.model.data')._update(self.cr, 1, "ir.actions.report.xml", \
711 self.module, values, xml_id, noupdate=self.isnoupdate(node), mode=self.mode)
712 self.id_map[xml_id] = int(id)
714 if not node.menu or eval(node.menu):
715 keyword = node.keyword or 'client_print_multi'
716 value = 'ir.actions.report.xml,%s' % id
717 replace = node.replace or True
718 self.pool.get('ir.model.data').ir_set(self.cr, 1, 'action', \
719 keyword, values['name'], [values['model']], value, replace=replace, isobject=True, xml_id=xml_id)
721 def process_none(self):
723 Empty node or commented node should not pass silently.
725 self._log_assert_failure(logging.WARNING, "You have an empty block in your tests.")
728 def process(self, yaml_string):
730 Processes a Yaml string. Custom tags are interpreted by 'process_' instance methods.
732 yaml_tag.add_constructors()
734 is_preceded_by_comment = False
735 for node in yaml.load(yaml_string):
736 is_preceded_by_comment = self._log(node, is_preceded_by_comment)
738 self._process_node(node)
739 except YamlImportException, e:
740 self.logger.exception(e)
742 self.logger.exception(e)
745 def _process_node(self, node):
747 self.process_comment(node)
748 elif is_assert(node):
749 self.process_assert(node)
750 elif is_record(node):
751 self.process_record(node)
752 elif is_python(node):
753 self.process_python(node)
754 elif is_menuitem(node):
755 self.process_menuitem(node)
756 elif is_delete(node):
757 self.process_delete(node)
759 self.process_url(node)
760 elif is_context(node):
761 self.process_context(node)
762 elif is_ir_set(node):
763 self.process_ir_set(node)
764 elif is_act_window(node):
765 self.process_act_window(node)
766 elif is_report(node):
767 self.process_report(node)
768 elif is_workflow(node):
769 if isinstance(node, types.DictionaryType):
770 self.process_workflow(node)
772 self.process_workflow({node: []})
773 elif is_function(node):
774 if isinstance(node, types.DictionaryType):
775 self.process_function(node)
777 self.process_function({node: []})
781 raise YamlImportException("Can not process YAML block: %s" % node)
783 def _log(self, node, is_preceded_by_comment):
785 is_preceded_by_comment = True
786 self.logger.log(logging.TEST, node)
787 elif not is_preceded_by_comment:
788 if isinstance(node, types.DictionaryType):
789 msg = "Creating %s\n with %s"
790 args = node.items()[0]
791 self.logger.log(logging.TEST, msg, *args)
793 self.logger.log(logging.TEST, node)
795 is_preceded_by_comment = False
796 return is_preceded_by_comment
798 def yaml_import(cr, module, yamlfile, idref=None, mode='init', noupdate=False, report=None):
801 yaml_string = yamlfile.read()
802 yaml_interpreter = YamlInterpreter(cr, module, idref, mode, filename=yamlfile.name, noupdate=noupdate)
803 yaml_interpreter.process(yaml_string)
805 # keeps convention of convert.py
806 convert_yaml_import = yaml_import
808 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: