From d05f2481cc4a8d26d66bed88014884550f22dc9c Mon Sep 17 00:00:00 2001 From: ced <> Date: Thu, 9 Aug 2007 13:12:25 +0000 Subject: [PATCH] Merge branch 'assert' Conflicts: server/bin/addons/account/__terp__.py bzr revid: ced-81132660cb78828bcc06e9d50939915e1909ae06 --- bin/addons/__init__.py | 24 ++--- bin/osv/orm.py | 7 ++ bin/tools/config.py | 11 ++- bin/tools/convert.py | 199 ++++++++++++++++++++++++++++++++++++----- 4 files changed, 206 insertions(+), 35 deletions(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 486994c7d23..41d47c076e0 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -59,7 +59,7 @@ class Graph(dict): father.addChild(name) else: Node(name, self) - + def __iter__(self): level = 0 done = Set(self.keys()) @@ -69,7 +69,7 @@ class Graph(dict): done.remove(name) yield module level += 1 - + class Singleton(object): def __new__(cls, name, graph): @@ -89,7 +89,7 @@ class Node(Singleton): self.childs = [] if not hasattr(self, 'depth'): self.depth = 0 - + def addChild(self, name): node = Node(name, self.graph) node.depth = self.depth + 1 @@ -99,7 +99,7 @@ class Node(Singleton): if hasattr(self, attr): setattr(node, attr, True) self.childs.sort(lambda x,y: cmp(x.name, y.name)) - + def hasChild(self, name): return Node(name, self.graph) in self.childs or \ bool([c for c in self.childs if c.hasChild(name)]) @@ -149,7 +149,7 @@ def create_graph(module_list, force=None): current,later = Set([p for p, dep, data in packages]), Set() while packages and current > later: package, deps, datas = packages[0] - + # if all dependencies of 'package' are already in the graph, add 'package' in the graph if reduce(lambda x,y: x and y in graph, deps, True): if not package in current: @@ -170,7 +170,7 @@ def create_graph(module_list, force=None): for package in later: logger.notifyChannel('init', netsvc.LOG_ERROR, 'addon:%s:Unmet dependency' % package) - + return graph def init_module_objects(cr, module_name, obj_list): @@ -182,7 +182,8 @@ def init_module_objects(cr, module_name, obj_list): obj._auto_init(cr) cr.commit() -def load_module_graph(cr, graph, status=None): +def load_module_graph(cr, graph, status=None, **kwargs): + # **kwargs is passed directly to convert_xml_import if not status: status={} status = status.copy() @@ -218,7 +219,7 @@ def load_module_graph(cr, graph, status=None): cr.execute(new_query) cr.commit() else: - tools.convert_xml_import(cr, m, tools.file_open(opj(m, filename)).read(), idref, mode=mode) + tools.convert_xml_import(cr, m, tools.file_open(opj(m, filename)).read(), idref, mode=mode, **kwargs) if hasattr(package, 'demo') or (package_demo and package_state != 'installed'): status['progress'] = (float(statusi)+0.75)/len(graph) for xml in package.datas.get('demo_xml', []): @@ -227,7 +228,7 @@ def load_module_graph(cr, graph, status=None): if ext == '.csv': tools.convert_csv_import(cr, m, os.path.basename(xml), tools.file_open(opj(m, xml)).read(), idref, mode=mode, noupdate=True) else: - tools.convert_xml_import(cr, m, tools.file_open(opj(m, xml)).read(), idref) + tools.convert_xml_import(cr, m, tools.file_open(opj(m, xml)).read(), idref, mode=mode, noupdate=True, **kwargs) cr.execute('update ir_module_module set demo=%s where name=%s', (True, package.name)) package_todo.append(package.name) cr.execute("update ir_module_module set state='installed' where state in ('to upgrade', 'to install') and name=%s", (package.name,)) @@ -270,7 +271,10 @@ def load_modules(db, force_demo=False, status=None, update_module=False): cr.execute("select name from ir_module_module where state in ('installed', 'to upgrade', 'to remove')") module_list = [name for (name,) in cr.fetchall()] graph = create_graph(module_list, force) - load_module_graph(cr, graph, status) + report = tools.assertion_report() + load_module_graph(cr, graph, status, report=report) + if report.get_report(): + print report for kind in ('init', 'demo', 'update'): tools.config[kind]={} diff --git a/bin/osv/orm.py b/bin/osv/orm.py index a51a80f3219..0daf1dfc2a3 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -205,6 +205,12 @@ class browse_record(object): # raise an AttributeError exception. return self[name] + def __contains__(self, name): + return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name) + + def __hasattr__(self, name): + return name in self + def __int__(self): return self._id @@ -1513,6 +1519,7 @@ class orm(object): i+=1 elif field._type=='many2many': + #FIXME if args[i][1]=='child_of': if isinstance(args[i][2], basestring): ids2 = [x[0] for x in self.pool.get(field._obj).name_search(cr, user, args[i][2], [], 'like')] diff --git a/bin/tools/config.py b/bin/tools/config.py index 213e8b0608b..7ae58a8b05c 100644 --- a/bin/tools/config.py +++ b/bin/tools/config.py @@ -32,7 +32,7 @@ import ConfigParser,optparse,os,sys class configmanager(object): def __init__(self, fname=None): - import netsvc + import netsvc, logging logger = netsvc.Logger() self.options = { 'verbose': False, @@ -66,7 +66,11 @@ class configmanager(object): 'smtp_password': False, 'stop_after_init': False, # this will stop the server after initialization 'price_accuracy': 2, + + 'assert_exit_level': logging.WARNING, # level above which a failed assert will } + + assert_exit_levels = (netsvc.LOG_CRITICAL, netsvc.LOG_DEBUG, netsvc.LOG_ERROR, netsvc.LOG_INFO, netsvc.LOG_WARNING) parser = optparse.OptionParser(version=tinyerp_version_string) @@ -89,6 +93,7 @@ class configmanager(object): # stops the server from launching after initialization parser.add_option("--stop-after-init", action="store_true", dest="stop_after_init", default=False, help="stop the server after it initializes") parser.add_option('--debug', dest='debug_mode', action='store_true', default=False, help='enable debug mode') + parser.add_option("--assert-exit-level", dest='assert_exit_level', help="specify the level at which a failed assertion will stop the server " + str(assert_exit_levels)) parser.add_option("-S", "--secure", dest="secure", action="store_true", help="launch server over https instead of http", default=False) parser.add_option('--smtp', dest='smtp_server', default='', help='specify the SMTP server for sending email') parser.add_option('--smtp-user', dest='smtp_user', default='', help='specify the SMTP username for sending email') @@ -153,6 +158,10 @@ class configmanager(object): 'upgrade', 'verbose', 'debug_mode', 'stop_after_init', 'without_demo', 'netrpc', 'xmlrpc'): self.options[arg] = getattr(opt, arg) + + if opt.assert_exit_level: + assert opt.assert_exit_level in assert_exit_levels + self.options['assert_exit_level'] = getattr(logging, opt.assert_exit_level.upper()) if not self.options['root_path'] or self.options['root_path']=='None': self.options['root_path'] = os.path.abspath(os.path.dirname(sys.argv[0])) diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 7d4537f7c80..28063d0caf1 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -1,5 +1,5 @@ #---------------------------------------------------------- -# Convert +# Convert #---------------------------------------------------------- import re import StringIO,xml.dom.minidom @@ -11,6 +11,11 @@ import misc import netsvc from config import config +import logging + +# Number of imported lines between two commit (see convert_csv_import()): +COMMIT_STEP = 500 + # Number of imported lines between two commit (see convert_csv_import()): @@ -21,19 +26,28 @@ class ConvertError(Exception): def __init__(self, doc, orig_excpt): self.d = doc self.orig = orig_excpt - + def __str__(self): return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d) -def _eval_xml(self,node, pool, cr, uid, idref): +def _ref(self, cr): + return lambda x: self.id_get(cr, False, x) + +def _obj(pool, cr, uid, model_str, context=None): + model = pool.get(model_str) + return lambda x: model.browse(cr, uid, x, context=context) + +def _eval_xml(self,node, pool, cr, uid, idref, context=None): + if context is None: + context = {} if node.nodeType == node.TEXT_NODE: return node.data.encode("utf8") elif node.nodeType == node.ELEMENT_NODE: if node.nodeName in ('field','value'): t = node.getAttribute('type') or 'char' + f_model = node.getAttribute("model").encode('ascii') if len(node.getAttribute('search')): f_search = node.getAttribute("search").encode('utf-8') - f_model = node.getAttribute("model").encode('ascii') f_use = node.getAttribute("use").encode('ascii') f_name = node.getAttribute("name").encode('utf-8') if len(f_use)==0: @@ -58,6 +72,8 @@ def _eval_xml(self,node, pool, cr, uid, idref): import release idref['version'] = release.version.rsplit('.', 1)[0] idref['ref'] = lambda x: self.id_get(cr, False, x) + if len(f_model): + idref['obj'] = _obj(self.pool, cr, uid, f_model, context=context) try: import pytz except: @@ -115,18 +131,75 @@ def _eval_xml(self,node, pool, cr, uid, idref): idref['ref'] = lambda x: self.id_get(cr, False, x) args = eval(a_eval, idref) for n in [i for i in node.childNodes if (i.nodeType == i.ELEMENT_NODE)]: - args.append(_eval_xml(self,n, pool, cr, uid, idref)) + args.append(_eval_xml(self,n, pool, cr, uid, idref, context)) model = pool.get(node.getAttribute('model')) method = node.getAttribute('name') res = getattr(model, method)(cr, uid, *args) return res + elif node.nodeName=="test": + d = "" + for n in [i for i in node.childNodes]: + d+=str(_eval_xml(self,n,pool,cr,uid,idref, context=context)) + return d + escape_re = re.compile(r'(? config['assert_exit_level']: + # TODO: define a dedicated exception + raise Exception('Severe assertion failure') + return + + assert ids != None, 'You must give either an id or a search criteria' + + ref = _ref(self, cr) + for id in ids: + brrec = model.browse(cr, uid, id, context) + class d(dict): + def __getitem__(self2, key): + if key in brrec: + return brrec[key] + return dict.__getitem__(self2, key) + globals = d() + globals['floatEqual'] = self._assert_equals + globals['ref'] = ref + globals['_ref'] = ref for test in [i for i in rec.childNodes if (i.nodeType == i.ELEMENT_NODE and i.nodeName=="test")]: f_expr = test.getAttribute("expr").encode('utf-8') - class d(dict): - def __getitem__(self2, key): - return getattr(model.browse(cr, self.uid, id), key) + f_val = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True + if eval(f_expr, globals) != f_val: # assertion failed + self.assert_report.record_assertion(False, severity) + self.logger.notifyChannel('init', severity, 'assertion "' + rec_string + '" failed ! (tag ' + test.toxml() + ')' ) + sevval = getattr(logging, severity.upper()) + if sevval > config['assert_exit_level']: + # TODO: define a dedicated exception + raise Exception('Severe assertion failure') + return + else: # all tests were successful for this assertion tag (no break) + self.assert_report.record_assertion(True, severity) def _tag_record(self, cr, rec, data_node=None): rec_model = rec.getAttribute("model").encode('ascii') @@ -351,16 +498,16 @@ class xml_import(object): rec_id = rec.getAttribute("id").encode('ascii') self._test_xml_id(rec_id) -# if not rec_id and not data_node.getAttribute('noupdate'): +# if not rec_id and not self.isnoupdate(data_node): # print "Warning", rec_model - if data_node.getAttribute('noupdate') and not self.mode == 'init': + if self.isnoupdate(data_node) and not self.mode == 'init': # check if the xml record has an id string if rec_id: id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, self.module, rec_id) # check if the resource already existed at the last update if id: - # if it existed, we don't update the data, but we need to + # if it existed, we don't update the data, but we need to # know the id of the existing record anyway self.idref[rec_id] = id return None @@ -375,7 +522,7 @@ class xml_import(object): else: # otherwise it is skipped return None - + res = {} for field in [i for i in rec.childNodes if (i.nodeType == i.ELEMENT_NODE and i.nodeName=="field")]: #TODO: most of this code is duplicated above (in _eval_xml)... @@ -415,7 +562,7 @@ class xml_import(object): if isinstance(model._columns[f_name], osv.fields.integer): f_val = int(f_val) res[f_name] = f_val - id = self.pool.get('ir.model.data')._update(cr, self.uid, rec_model, self.module, res, rec_id or False, not data_node.getAttribute('noupdate'), noupdate=data_node.getAttribute('noupdate'), mode=self.mode ) + id = self.pool.get('ir.model.data')._update(cr, self.uid, rec_model, self.module, res, rec_id or False, not self.isnoupdate(data_node), noupdate=self.isnoupdate(data_node), mode=self.mode ) if rec_id: self.idref[rec_id] = id return rec_model, id @@ -445,7 +592,7 @@ class xml_import(object): self.cr.commit() return True - def __init__(self, cr, module, idref, mode): + def __init__(self, cr, module, idref, mode, report=assertion_report(), noupdate = False): self.logger = netsvc.Logger() self.mode = mode self.module = module @@ -454,6 +601,8 @@ class xml_import(object): self.pool = pooler.get_pool(cr.dbname) # self.pool = osv.osv.FakePool(module) self.uid = 1 + self.assert_report = report + self.noupdate = noupdate self._tags = { 'menuitem': self._tag_menuitem, 'record': self._tag_record, @@ -485,10 +634,10 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', n input=StringIO.StringIO(csvcontent) reader = csv.reader(input, quotechar='"', delimiter=',') fields = reader.next() - + if not (mode == 'init' or 'id' in fields): return - + uid = 1 datas = [] for line in reader: @@ -505,10 +654,12 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', n # # xml import/export # -def convert_xml_import(cr, module, xmlstr, idref=None, mode='init'): +def convert_xml_import(cr, module, xmlstr, idref=None, mode='init', noupdate = False, report=None): if not idref: idref={} - obj = xml_import(cr, module, idref, mode) + if report is None: + report=assertion_report() + obj = xml_import(cr, module, idref, mode, report=report, noupdate = noupdate) obj.parse(xmlstr) del obj return True