[REF] module code goes into openerp.modules.module.

bzr revid: vmt@openerp.com-20110511172448-p12p1rizvf3lqv97
This commit is contained in:
Vo Minh Thu 2011-05-11 19:24:48 +02:00
parent fb476648e0
commit 1fe579d8ec
5 changed files with 389 additions and 326 deletions

View File

@ -54,31 +54,15 @@ import openerp.modules.db
import openerp.modules.graph
import openerp.modules.migration
from openerp.modules.module import \
get_modules, get_modules_with_version, \
load_information_from_description_file, \
get_module_resource, zip_directory, \
get_module_path, initialize_sys_path, \
register_module_classes, init_module_models
logger = netsvc.Logger()
_ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base)
ad_paths = []
# Modules already loaded
loaded = []
def initialize_sys_path():
global ad_paths
if ad_paths:
return
ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
sys.path.insert(1, _ad)
ad_cnt=1
for adp in ad_paths:
if adp != _ad:
sys.path.insert(ad_cnt, adp)
ad_cnt+=1
ad_paths.append(_ad) # for get_module_path
def open_openerp_namespace():
# See comment for open_openerp_namespace.
@ -88,304 +72,6 @@ def open_openerp_namespace():
sys.modules[k[8:]] = v
def get_module_path(module, downloaded=False):
"""Return the path of the given module.
Search the addons paths and return the first path where the given
module is found. If downloaded is True, return the default addons
path if nothing else is found.
"""
initialize_sys_path()
for adp in ad_paths:
if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
return opj(adp, module)
if downloaded:
return opj(_ad, module)
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,))
return False
def get_module_filetree(module, dir='.'):
path = get_module_path(module)
if not path:
return False
dir = os.path.normpath(dir)
if dir == '.':
dir = ''
if dir.startswith('..') or (dir and dir[0] == '/'):
raise Exception('Cannot access file outside the module')
if not os.path.isdir(path):
# zipmodule
zip = zipfile.ZipFile(path + ".zip")
files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
else:
files = osutil.listdir(path, True)
tree = {}
for f in files:
if not f.startswith(dir):
continue
if dir:
f = f[len(dir)+int(not dir.endswith('/')):]
lst = f.split(os.sep)
current = tree
while len(lst) != 1:
current = current.setdefault(lst.pop(0), {})
current[lst.pop(0)] = None
return tree
def zip_directory(directory, b64enc=True, src=True):
"""Compress a directory
@param directory: The directory to compress
@param base64enc: if True the function will encode the zip file with base64
@param src: Integrate the source files
@return: a string containing the zip file
"""
RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I)
def _zippy(archive, path, src=True):
path = os.path.abspath(path)
base = os.path.basename(path)
for f in osutil.listdir(path, True):
bf = os.path.basename(f)
if not RE_exclude.search(bf) and (src or bf in ('__openerp__.py', '__terp__.py') or not bf.endswith('.py')):
archive.write(os.path.join(path, f), os.path.join(base, f))
archname = StringIO()
archive = PyZipFile(archname, "w", ZIP_DEFLATED)
# for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8)
directory = tools.ustr(directory).encode('utf-8')
archive.writepy(directory)
_zippy(archive, directory, src=src)
archive.close()
archive_data = archname.getvalue()
archname.close()
if b64enc:
return base64.encodestring(archive_data)
return archive_data
def get_module_as_zip(modulename, b64enc=True, src=True):
"""Generate a module as zip file with the source or not and can do a base64 encoding
@param modulename: The module name
@param b64enc: if True the function will encode the zip file with base64
@param src: Integrate the source files
@return: a stream to store in a file-like object
"""
ap = get_module_path(str(modulename))
if not ap:
raise Exception('Unable to find path for module %s' % modulename)
ap = ap.encode('utf8')
if os.path.isfile(ap + '.zip'):
val = file(ap + '.zip', 'rb').read()
if b64enc:
val = base64.encodestring(val)
else:
val = zip_directory(ap, b64enc, src)
return val
def get_module_resource(module, *args):
"""Return the full path of a resource of the given module.
@param module: the module
@param args: the resource path components
@return: absolute path to the resource
TODO name it get_resource_path
TODO make it available inside on osv object (self.get_resource_path)
"""
a = get_module_path(module)
if not a: return False
resource_path = opj(a, *args)
if zipfile.is_zipfile( a +'.zip') :
zip = zipfile.ZipFile( a + ".zip")
files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
resource_path = '/'.join(args)
if resource_path in files:
return opj(a, resource_path)
elif os.path.exists(resource_path):
return resource_path
return False
def get_modules():
"""Returns the list of module names
"""
def listdir(dir):
def clean(name):
name = os.path.basename(name)
if name[-4:] == '.zip':
name = name[:-4]
return name
def is_really_module(name):
name = opj(dir, name)
return os.path.isdir(name) or zipfile.is_zipfile(name)
return map(clean, filter(is_really_module, os.listdir(dir)))
plist = []
initialize_sys_path()
for ad in ad_paths:
plist.extend(listdir(ad))
return list(set(plist))
def load_information_from_description_file(module):
"""
:param module: The name of the module (sale, purchase, ...)
"""
terp_file = get_module_resource(module, '__openerp__.py')
if not terp_file:
terp_file = get_module_resource(module, '__terp__.py')
mod_path = get_module_path(module)
if terp_file:
info = {}
if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
terp_f = tools.file_open(terp_file)
try:
info = eval(terp_f.read())
except Exception:
logger.notifyChannel('modules', netsvc.LOG_ERROR,
'module %s: exception while evaluating file %s' %
(module, terp_file))
raise
finally:
terp_f.close()
# TODO the version should probably be mandatory
info.setdefault('version', '0')
info.setdefault('category', 'Uncategorized')
info.setdefault('depends', [])
info.setdefault('author', '')
info.setdefault('website', '')
info.setdefault('name', False)
info.setdefault('description', '')
info['certificate'] = info.get('certificate') or None
info['web'] = info.get('web') or False
info['license'] = info.get('license') or 'AGPL-3'
info.setdefault('installable', True)
info.setdefault('active', False)
for kind in ['data', 'demo', 'test',
'init_xml', 'update_xml', 'demo_xml']:
info.setdefault(kind, [])
return info
#TODO: refactor the logger in this file to follow the logging guidelines
# for 6.0
logging.getLogger('modules').debug('module %s: no descriptor file'
' found: __openerp__.py or __terp__.py (deprecated)', module)
return {}
def get_modules_with_version():
modules = get_modules()
res = {}
for module in modules:
try:
info = load_information_from_description_file(module)
res[module] = "%s.%s" % (release.major_version, info['version'])
except Exception, e:
continue
return res
def init_module_models(cr, module_name, obj_list):
""" Initialize a list of models.
Call _auto_init and init on each model to create or update the
database tables supporting the models.
TODO better explanation of _auto_init and init.
"""
logger.notifyChannel('init', netsvc.LOG_INFO,
'module %s: creating or updating database tables' % module_name)
# TODO _auto_init doesn't seem to return anything
# so this todo list would be useless.
todo = []
for obj in obj_list:
try:
# TODO the module in the context doesn't seem usefull:
# it is available (at least) in the class' _module attribute.
# (So module_name would be useless too.)
result = obj._auto_init(cr, {'module': module_name})
except Exception, e:
raise
if result:
todo += result
if hasattr(obj, 'init'):
obj.init(cr)
cr.commit()
todo.sort()
for t in todo:
t[1](cr, *t[2])
cr.commit()
def load_module(module_name):
""" Load a Python module found on the addons paths."""
fm = imp.find_module(module_name, ad_paths)
try:
imp.load_module(module_name, *fm)
finally:
if fm[0]:
fm[0].close()
def register_module_classes(m):
""" Register module named m, if not already registered.
This will load the module and register all of its models. (Actually, the
explicit constructor call of each of the models inside the module will
register them.)
"""
def log(e):
mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
msg = "Couldn't load %smodule %s" % (mt, m)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
global loaded
if m in loaded:
return
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
mod_path = get_module_path(m)
initialize_sys_path()
try:
zip_mod_path = mod_path + '.zip'
if not os.path.isfile(zip_mod_path):
load_module(m)
else:
zimp = zipimport.zipimporter(zip_mod_path)
zimp.load_module(m)
except Exception, e:
log(e)
raise
else:
loaded.append(m)
def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
"""Migrates+Updates or Installs all module nodes from ``graph``
:param graph: graph of module nodes to load

View File

@ -48,8 +48,6 @@ from cStringIO import StringIO
import logging
import openerp.modules.db
logger = netsvc.Logger()
@ -102,7 +100,7 @@ class Graph(dict):
# 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)
info = openerp.modules.module.load_information_from_description_file(module)
if info['installable']:
packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version
else:

View File

@ -98,7 +98,7 @@ class MigrationManager(object):
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
continue
get_module_filetree = openerp.modules.get_module_filetree
get_module_filetree = openerp.modules.module.get_module_filetree
self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}

379
openerp/modules/module.py Normal file
View File

@ -0,0 +1,379 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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
import openerp.modules.graph
_ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base)
ad_paths = []
# Modules already loaded
loaded = []
logger = netsvc.Logger()
def initialize_sys_path():
global ad_paths
if ad_paths:
return
ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
sys.path.insert(1, _ad)
ad_cnt=1
for adp in ad_paths:
if adp != _ad:
sys.path.insert(ad_cnt, adp)
ad_cnt+=1
ad_paths.append(_ad) # for get_module_path
def get_module_path(module, downloaded=False):
"""Return the path of the given module.
Search the addons paths and return the first path where the given
module is found. If downloaded is True, return the default addons
path if nothing else is found.
"""
initialize_sys_path()
for adp in ad_paths:
if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
return opj(adp, module)
if downloaded:
return opj(_ad, module)
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,))
return False
def get_module_filetree(module, dir='.'):
path = get_module_path(module)
if not path:
return False
dir = os.path.normpath(dir)
if dir == '.':
dir = ''
if dir.startswith('..') or (dir and dir[0] == '/'):
raise Exception('Cannot access file outside the module')
if not os.path.isdir(path):
# zipmodule
zip = zipfile.ZipFile(path + ".zip")
files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
else:
files = osutil.listdir(path, True)
tree = {}
for f in files:
if not f.startswith(dir):
continue
if dir:
f = f[len(dir)+int(not dir.endswith('/')):]
lst = f.split(os.sep)
current = tree
while len(lst) != 1:
current = current.setdefault(lst.pop(0), {})
current[lst.pop(0)] = None
return tree
def zip_directory(directory, b64enc=True, src=True):
"""Compress a directory
@param directory: The directory to compress
@param base64enc: if True the function will encode the zip file with base64
@param src: Integrate the source files
@return: a string containing the zip file
"""
RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I)
def _zippy(archive, path, src=True):
path = os.path.abspath(path)
base = os.path.basename(path)
for f in osutil.listdir(path, True):
bf = os.path.basename(f)
if not RE_exclude.search(bf) and (src or bf in ('__openerp__.py', '__terp__.py') or not bf.endswith('.py')):
archive.write(os.path.join(path, f), os.path.join(base, f))
archname = StringIO()
archive = PyZipFile(archname, "w", ZIP_DEFLATED)
# for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8)
directory = tools.ustr(directory).encode('utf-8')
archive.writepy(directory)
_zippy(archive, directory, src=src)
archive.close()
archive_data = archname.getvalue()
archname.close()
if b64enc:
return base64.encodestring(archive_data)
return archive_data
def get_module_as_zip(modulename, b64enc=True, src=True):
"""Generate a module as zip file with the source or not and can do a base64 encoding
@param modulename: The module name
@param b64enc: if True the function will encode the zip file with base64
@param src: Integrate the source files
@return: a stream to store in a file-like object
"""
ap = get_module_path(str(modulename))
if not ap:
raise Exception('Unable to find path for module %s' % modulename)
ap = ap.encode('utf8')
if os.path.isfile(ap + '.zip'):
val = file(ap + '.zip', 'rb').read()
if b64enc:
val = base64.encodestring(val)
else:
val = zip_directory(ap, b64enc, src)
return val
def get_module_resource(module, *args):
"""Return the full path of a resource of the given module.
@param module: the module
@param args: the resource path components
@return: absolute path to the resource
TODO name it get_resource_path
TODO make it available inside on osv object (self.get_resource_path)
"""
a = get_module_path(module)
if not a: return False
resource_path = opj(a, *args)
if zipfile.is_zipfile( a +'.zip') :
zip = zipfile.ZipFile( a + ".zip")
files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
resource_path = '/'.join(args)
if resource_path in files:
return opj(a, resource_path)
elif os.path.exists(resource_path):
return resource_path
return False
def load_information_from_description_file(module):
"""
:param module: The name of the module (sale, purchase, ...)
"""
terp_file = get_module_resource(module, '__openerp__.py')
if not terp_file:
terp_file = get_module_resource(module, '__terp__.py')
mod_path = get_module_path(module)
if terp_file:
info = {}
if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
terp_f = tools.file_open(terp_file)
try:
info = eval(terp_f.read())
except Exception:
logger.notifyChannel('modules', netsvc.LOG_ERROR,
'module %s: exception while evaluating file %s' %
(module, terp_file))
raise
finally:
terp_f.close()
# TODO the version should probably be mandatory
info.setdefault('version', '0')
info.setdefault('category', 'Uncategorized')
info.setdefault('depends', [])
info.setdefault('author', '')
info.setdefault('website', '')
info.setdefault('name', False)
info.setdefault('description', '')
info['certificate'] = info.get('certificate') or None
info['web'] = info.get('web') or False
info['license'] = info.get('license') or 'AGPL-3'
info.setdefault('installable', True)
info.setdefault('active', False)
for kind in ['data', 'demo', 'test',
'init_xml', 'update_xml', 'demo_xml']:
info.setdefault(kind, [])
return info
#TODO: refactor the logger in this file to follow the logging guidelines
# for 6.0
logging.getLogger('modules').debug('module %s: no descriptor file'
' found: __openerp__.py or __terp__.py (deprecated)', module)
return {}
def init_module_models(cr, module_name, obj_list):
""" Initialize a list of models.
Call _auto_init and init on each model to create or update the
database tables supporting the models.
TODO better explanation of _auto_init and init.
"""
logger.notifyChannel('init', netsvc.LOG_INFO,
'module %s: creating or updating database tables' % module_name)
# TODO _auto_init doesn't seem to return anything
# so this todo list would be useless.
todo = []
for obj in obj_list:
try:
# TODO the module in the context doesn't seem usefull:
# it is available (at least) in the class' _module attribute.
# (So module_name would be useless too.)
result = obj._auto_init(cr, {'module': module_name})
except Exception, e:
raise
if result:
todo += result
if hasattr(obj, 'init'):
obj.init(cr)
cr.commit()
todo.sort()
for t in todo:
t[1](cr, *t[2])
cr.commit()
def load_module(module_name):
""" Load a Python module found on the addons paths."""
fm = imp.find_module(module_name, ad_paths)
try:
imp.load_module(module_name, *fm)
finally:
if fm[0]:
fm[0].close()
def register_module_classes(m):
""" Register module named m, if not already registered.
This will load the module and register all of its models. (Actually, the
explicit constructor call of each of the models inside the module will
register them.)
"""
def log(e):
mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
msg = "Couldn't load %smodule %s" % (mt, m)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
global loaded
if m in loaded:
return
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
mod_path = get_module_path(m)
initialize_sys_path()
try:
zip_mod_path = mod_path + '.zip'
if not os.path.isfile(zip_mod_path):
load_module(m)
else:
zimp = zipimport.zipimporter(zip_mod_path)
zimp.load_module(m)
except Exception, e:
log(e)
raise
else:
loaded.append(m)
def get_modules():
"""Returns the list of module names
"""
def listdir(dir):
def clean(name):
name = os.path.basename(name)
if name[-4:] == '.zip':
name = name[:-4]
return name
def is_really_module(name):
name = opj(dir, name)
return os.path.isdir(name) or zipfile.is_zipfile(name)
return map(clean, filter(is_really_module, os.listdir(dir)))
plist = []
initialize_sys_path()
for ad in ad_paths:
plist.extend(listdir(ad))
return list(set(plist))
def get_modules_with_version():
modules = get_modules()
res = {}
for module in modules:
try:
info = load_information_from_description_file(module)
res[module] = "%s.%s" % (release.major_version, info['version'])
except Exception, e:
continue
return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -135,7 +135,7 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False):
@return: fileobject if pathinfo is False else (fileobject, filepath)
"""
import openerp.modules as addons
adps = addons.ad_paths
adps = addons.module.ad_paths
rtp = os.path.normcase(os.path.abspath(config['root_path']))
if name.replace(os.path.sep, '/').startswith('addons/'):