From 52d129150631685edd98cb21f90d53f9bfe4ae5e Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 11 May 2011 17:28:10 +0200 Subject: [PATCH] [REF] moved graph code from modules.__init__ to modules.graph - made upgrade_graph a method of Graph called add_modules - removed unnecessary create_graph function. bzr revid: vmt@openerp.com-20110511152810-etqru9pct1zwyqno --- openerp/modules/__init__.py | 166 +--------------------------- openerp/modules/graph.py | 215 ++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 162 deletions(-) create mode 100644 openerp/modules/graph.py diff --git a/openerp/modules/__init__.py b/openerp/modules/__init__.py index 2db494754eb..539abc6553f 100644 --- a/openerp/modules/__init__.py +++ b/openerp/modules/__init__.py @@ -51,6 +51,7 @@ from cStringIO import StringIO import logging import openerp.modules.db +import openerp.modules.graph logger = netsvc.Logger() @@ -85,110 +86,6 @@ def open_openerp_namespace(): if k.startswith('openerp.') and sys.modules.get(k[8:]) is None: sys.modules[k[8:]] = v -class Graph(dict): - """ Modules dependency graph. - - The graph is a mapping from module name to Nodes. - - """ - - def addNode(self, name, deps): - max_depth, father = 0, None - for n in [Node(x, self) for x in deps]: - if n.depth >= max_depth: - father = n - max_depth = n.depth - if father: - father.addChild(name) - else: - Node(name, self) - - def update_from_db(self, cr): - if not len(self): - return - # update the graph with values from the database (if exist) - ## First, we set the default values for each package in graph - additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None}) - ## Then we get the values from the database - cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version' - ' FROM ir_module_module' - ' WHERE name IN %s',(tuple(additional_data),) - ) - - ## and we update the default values with values from the database - additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()])) - - for package in self.values(): - for k, v in additional_data[package.name].items(): - setattr(package, k, v) - - def __iter__(self): - level = 0 - done = set(self.keys()) - while done: - level_modules = [(name, module) for name, module in self.items() if module.depth==level] - for name, module in level_modules: - done.remove(name) - yield module - level += 1 - -class Singleton(object): - def __new__(cls, name, graph): - if name in graph: - inst = graph[name] - else: - inst = object.__new__(cls) - inst.name = name - graph[name] = inst - return inst - - -class Node(Singleton): - """ One module in the modules dependency graph. - - Node acts as a per-module singleton. - - """ - - def __init__(self, name, graph): - self.graph = graph - if not hasattr(self, 'children'): - self.children = [] - if not hasattr(self, 'depth'): - self.depth = 0 - - def addChild(self, name): - node = Node(name, self.graph) - node.depth = self.depth + 1 - if node not in self.children: - self.children.append(node) - for attr in ('init', 'update', 'demo'): - if hasattr(self, attr): - setattr(node, attr, True) - self.children.sort(lambda x, y: cmp(x.name, y.name)) - - def __setattr__(self, name, value): - super(Singleton, self).__setattr__(name, value) - if name in ('init', 'update', 'demo'): - tools.config[name][self.name] = 1 - for child in self.children: - setattr(child, name, value) - if name == 'depth': - for child in self.children: - setattr(child, name, value + 1) - - def __iter__(self): - return itertools.chain(iter(self.children), *map(iter, self.children)) - - def __str__(self): - return self._pprint() - - def _pprint(self, depth=0): - s = '%s\n' % self.name - for c in self.children: - s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1)) - return s - def get_module_path(module, downloaded=False): """Return the path of the given module. @@ -407,62 +304,6 @@ def get_modules_with_version(): continue return res -def create_graph(cr, module_list, force=None): - graph = Graph() - upgrade_graph(graph, cr, module_list, force) - return graph - -def upgrade_graph(graph, cr, module_list, force=None): - if force is None: - force = [] - packages = [] - len_graph = len(graph) - for module in module_list: - # This will raise an exception if no/unreadable descriptor file. - # NOTE The call to load_information_from_description_file is already - # done by db.initialize, so it is possible to not do it again here. - info = load_information_from_description_file(module) - if info['installable']: - packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version - else: - logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module)) - - dependencies = dict([(p, info['depends']) for p, info in packages]) - current, later = set([p for p, info in packages]), set() - - while packages and current > later: - package, info = packages[0] - deps = info['depends'] - - # 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: - packages.pop(0) - continue - later.clear() - current.remove(package) - graph.addNode(package, deps) - node = Node(package, graph) - node.data = info - for kind in ('init', 'demo', 'update'): - if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force: - setattr(node, kind, True) - else: - later.add(package) - packages.append((package, info)) - packages.pop(0) - - graph.update_from_db(cr) - - for package in later: - unmet_deps = filter(lambda p: p not in graph, dependencies[package]) - logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps))) - - result = len(graph) - len_graph - if result != len(module_list): - logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.') - return result - def init_module_models(cr, module_name, obj_list): """ Initialize a list of models. @@ -898,7 +739,8 @@ def load_modules(db, force_demo=False, status=None, update_module=False): cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed')) # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) - graph = create_graph(cr, ['base'], force) + graph = openerp.modules.graph.Graph() + graph.add_module(cr, 'base', force) if not graph: logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)') raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)')) @@ -946,7 +788,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if not module_list: break - new_modules_in_graph = upgrade_graph(graph, cr, module_list, force) + new_modules_in_graph = graph.add_modules(cr, module_list, force) if new_modules_in_graph == 0: # nothing to load break diff --git a/openerp/modules/graph.py b/openerp/modules/graph.py new file mode 100644 index 00000000000..2a9951f29d9 --- /dev/null +++ b/openerp/modules/graph.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# Copyright (C) 2010-2011 OpenERP s.a. (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import os, sys, imp +from os.path import join as opj +import itertools +import zipimport + +import openerp + +import openerp.osv as osv +import openerp.tools as tools +import openerp.tools.osutil as osutil +from openerp.tools.safe_eval import safe_eval as eval +import openerp.pooler as pooler +from openerp.tools.translate import _ + +import openerp.netsvc as netsvc + +import zipfile +import openerp.release as release + +import re +import base64 +from zipfile import PyZipFile, ZIP_DEFLATED +from cStringIO import StringIO + +import logging + +import openerp.modules.db + +logger = netsvc.Logger() + + +class Graph(dict): + """ Modules dependency graph. + + The graph is a mapping from module name to Nodes. + + """ + + def add_node(self, name, deps): + max_depth, father = 0, None + for n in [Node(x, self) for x in deps]: + if n.depth >= max_depth: + father = n + max_depth = n.depth + if father: + return father.add_child(name) + else: + return Node(name, self) + + def update_from_db(self, cr): + if not len(self): + return + # update the graph with values from the database (if exist) + ## First, we set the default values for each package in graph + additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None}) + ## Then we get the values from the database + cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version' + ' FROM ir_module_module' + ' WHERE name IN %s',(tuple(additional_data),) + ) + + ## and we update the default values with values from the database + additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()])) + + for package in self.values(): + for k, v in additional_data[package.name].items(): + setattr(package, k, v) + + def add_module(self, cr, module, force=None): + self.add_modules(cr, [module], force) + + def add_modules(self, cr, module_list, force=None): + if force is None: + force = [] + packages = [] + len_graph = len(self) + for module in module_list: + # This will raise an exception if no/unreadable descriptor file. + # NOTE The call to load_information_from_description_file is already + # done by db.initialize, so it is possible to not do it again here. + info = openerp.modules.load_information_from_description_file(module) + if info['installable']: + packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version + else: + logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module)) + + dependencies = dict([(p, info['depends']) for p, info in packages]) + current, later = set([p for p, info in packages]), set() + + while packages and current > later: + package, info = packages[0] + deps = info['depends'] + + # if all dependencies of 'package' are already in the graph, add 'package' in the graph + if reduce(lambda x, y: x and y in self, deps, True): + if not package in current: + packages.pop(0) + continue + later.clear() + current.remove(package) + node = self.add_node(package, deps) + node.data = info + for kind in ('init', 'demo', 'update'): + if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force: + setattr(node, kind, True) + else: + later.add(package) + packages.append((package, info)) + packages.pop(0) + + self.update_from_db(cr) + + for package in later: + unmet_deps = filter(lambda p: p not in self, dependencies[package]) + logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps))) + + result = len(self) - len_graph + if result != len(module_list): + logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.') + return result + + + def __iter__(self): + level = 0 + done = set(self.keys()) + while done: + level_modules = [(name, module) for name, module in self.items() if module.depth==level] + for name, module in level_modules: + done.remove(name) + yield module + level += 1 + + +class Singleton(object): + def __new__(cls, name, graph): + if name in graph: + inst = graph[name] + else: + inst = object.__new__(cls) + inst.name = name + graph[name] = inst + return inst + + +class Node(Singleton): + """ One module in the modules dependency graph. + + Node acts as a per-module singleton. + + """ + + def __init__(self, name, graph): + self.graph = graph + if not hasattr(self, 'children'): + self.children = [] + if not hasattr(self, 'depth'): + self.depth = 0 + + def add_child(self, name): + node = Node(name, self.graph) + node.depth = self.depth + 1 + if node not in self.children: + self.children.append(node) + for attr in ('init', 'update', 'demo'): + if hasattr(self, attr): + setattr(node, attr, True) + self.children.sort(lambda x, y: cmp(x.name, y.name)) + return node + + def __setattr__(self, name, value): + super(Singleton, self).__setattr__(name, value) + if name in ('init', 'update', 'demo'): + tools.config[name][self.name] = 1 + for child in self.children: + setattr(child, name, value) + if name == 'depth': + for child in self.children: + setattr(child, name, value + 1) + + def __iter__(self): + return itertools.chain(iter(self.children), *map(iter, self.children)) + + def __str__(self): + return self._pprint() + + def _pprint(self, depth=0): + s = '%s\n' % self.name + for c in self.children: + s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1)) + return s + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: