Files
tryton/trytond/modules/__init__.py
T

437 lines
15 KiB
Python
Raw Normal View History

2014-12-02 12:54:01 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2012-04-01 23:08:15 +02:00
import os
import sys
2008-09-13 19:41:02 +02:00
import itertools
import logging
from functools import reduce
import imp
import operator
import ConfigParser
from glob import iglob
2013-09-06 22:32:37 +02:00
from sql import Table
2015-07-01 19:41:49 +02:00
from sql.functions import CurrentTimestamp
2010-08-19 22:01:12 +02:00
import trytond.tools as tools
from trytond.config import config
2010-08-19 22:01:12 +02:00
from trytond.transaction import Transaction
from trytond.cache import Cache
2015-05-28 21:02:01 +02:00
from trytond import backend
import trytond.convert as convert
2008-09-13 19:41:02 +02:00
2015-03-02 23:39:15 +01:00
logger = logging.getLogger(__name__)
2015-05-28 21:02:01 +02:00
ir_module = Table('ir_module')
2013-09-06 22:32:37 +02:00
ir_model_data = Table('ir_model_data')
2008-09-13 19:41:02 +02:00
OPJ = os.path.join
MODULES_PATH = os.path.abspath(os.path.dirname(__file__))
2008-09-13 19:41:02 +02:00
MODULES = []
2008-11-18 17:40:30 +01:00
EGG_MODULES = {}
2009-08-07 10:42:23 +02:00
2012-04-01 23:08:15 +02:00
2009-08-07 10:42:23 +02:00
def update_egg_modules():
global EGG_MODULES
try:
import pkg_resources
for ep in pkg_resources.iter_entry_points('trytond.modules'):
mod_name = ep.module_name.split('.')[-1]
EGG_MODULES[mod_name] = ep
except ImportError:
pass
update_egg_modules()
2008-11-18 17:40:30 +01:00
2008-09-13 19:41:02 +02:00
class Graph(dict):
def add_node(self, name, deps):
for i in [Node(x, self) for x in deps]:
i.add_child(name)
if not deps:
Node(name, self)
def __iter__(self):
level = 0
2009-04-08 17:55:24 +02:00
done = set(self.keys())
2008-09-13 19:41:02 +02:00
while done:
2012-04-01 23:08:15 +02:00
level_modules = [(name, module) for name, module in self.items()
if module.depth == level]
2008-09-13 19:41:02 +02:00
for name, module in level_modules:
done.remove(name)
yield module
level += 1
def __str__(self):
res = ''
for i in self:
res += str(i)
res += '\n'
return res
class Singleton(object):
def __new__(cls, name, graph):
if name in graph:
inst = graph[name]
else:
inst = object.__new__(cls)
graph[name] = inst
return inst
class Node(Singleton):
def __init__(self, name, graph):
super(Node, self).__init__()
self.name = name
self.graph = graph
2012-01-22 00:07:15 +01:00
# __init__ is called even if Node already exists
if not hasattr(self, 'info'):
self.info = None
2008-09-13 19:41:02 +02:00
if not hasattr(self, 'childs'):
self.childs = []
if not hasattr(self, 'depth'):
self.depth = 0
def add_child(self, name):
node = Node(name, self.graph)
node.depth = max(self.depth + 1, node.depth)
if node not in self.all_childs():
self.childs.append(node)
self.childs.sort(key=operator.attrgetter('name'))
2008-09-13 19:41:02 +02:00
def all_childs(self):
res = []
for child in self.childs:
res.append(child)
res += child.all_childs()
return res
def has_child(self, name):
return Node(name, self.graph) in self.childs or \
2013-02-27 22:50:31 +01:00
bool([c for c in self.childs if c.has_child(name)])
2008-09-13 19:41:02 +02:00
def __setattr__(self, name, value):
super(Node, self).__setattr__(name, value)
if name == 'depth':
for child in self.childs:
setattr(child, name, value + 1)
def __iter__(self):
return itertools.chain(iter(self.childs),
*[iter(x) for x in self.childs])
def __str__(self):
return self.pprint()
def pprint(self, depth=0):
res = '%s\n' % self.name
for child in self.childs:
res += '%s`-> %s' % (' ' * depth, child.pprint(depth + 1))
return res
2012-04-01 23:08:15 +02:00
def get_module_info(name):
"Return the content of the tryton.cfg"
module_config = ConfigParser.ConfigParser()
with tools.file_open(os.path.join(name, 'tryton.cfg')) as fp:
module_config.readfp(fp)
directory = os.path.dirname(fp.name)
info = dict(module_config.items('tryton'))
info['directory'] = directory
for key in ('depends', 'extras_depend', 'xml'):
if key in info:
info[key] = info[key].strip().splitlines()
return info
def create_graph(module_list):
2008-09-13 19:41:02 +02:00
graph = Graph()
packages = []
for module in module_list:
info = get_module_info(module)
packages.append((module, info.get('depends', []),
info.get('extras_depend', []), info))
2008-09-13 19:41:02 +02:00
2009-04-08 17:55:24 +02:00
current, later = set([x[0] for x in packages]), set()
2012-01-22 00:07:15 +01:00
all_packages = set(current)
2008-09-13 19:41:02 +02:00
while packages and current > later:
2012-01-22 00:07:15 +01:00
package, deps, xdep, info = packages[0]
2008-09-13 19:41:02 +02:00
# if all dependencies of 'package' are already in the graph,
# add 'package' in the graph
2012-01-22 00:07:15 +01:00
all_deps = deps + [x for x in xdep if x in all_packages]
if reduce(lambda x, y: x and y in graph, all_deps, True):
2014-12-02 12:54:01 +01:00
if package not in current:
2008-09-13 19:41:02 +02:00
packages.pop(0)
continue
later.clear()
current.remove(package)
2012-01-22 00:07:15 +01:00
graph.add_node(package, all_deps)
2008-09-13 19:41:02 +02:00
node = Node(package, graph)
2012-01-22 00:07:15 +01:00
node.info = info
2008-09-13 19:41:02 +02:00
else:
later.add(package)
2012-01-22 00:07:15 +01:00
packages.append((package, deps, xdep, info))
2008-09-13 19:41:02 +02:00
packages.pop(0)
2013-06-26 16:37:03 +02:00
missings = set()
for package, deps, _, _ in packages:
2008-09-13 19:41:02 +02:00
if package not in later:
continue
2013-06-26 16:37:03 +02:00
missings |= set((x for x in deps if x not in graph))
if missings:
raise Exception('Missing dependencies: %s' % list(missings
- set((p[0] for p in packages))))
2008-09-13 19:41:02 +02:00
return graph, packages, later
def is_module_to_install(module, update):
if module in update:
return True
return False
2012-04-01 23:08:15 +02:00
def load_module_graph(graph, pool, update=None, lang=None):
2008-09-13 19:41:02 +02:00
if lang is None:
lang = [config.get('database', 'language')]
if update is None:
update = []
2008-09-13 19:41:02 +02:00
modules_todo = []
models_to_update_history = set()
with Transaction().connection.cursor() as cursor:
modules = [x.name for x in graph]
cursor.execute(*ir_module.select(ir_module.name, ir_module.state,
where=ir_module.name.in_(modules)))
module2state = dict(cursor.fetchall())
for package in graph:
module = package.name
if module not in MODULES:
2015-01-16 10:21:52 +01:00
continue
logger.info(module)
2016-03-17 22:56:58 +01:00
classes = pool.fill(module)
if update:
pool.setup(classes)
package_state = module2state.get(module, 'uninstalled')
if (is_module_to_install(module, update)
2016-03-17 22:56:58 +01:00
or (update
and package_state in ('to install', 'to upgrade'))):
if package_state not in ('to install', 'to upgrade'):
if package_state == 'installed':
package_state = 'to upgrade'
elif package_state != 'to remove':
package_state = 'to install'
for child in package.childs:
module2state[child.name] = package_state
for type in classes.keys():
for cls in classes[type]:
logger.info('%s:register %s', module, cls.__name__)
cls.__register__(module)
for model in classes['model']:
if hasattr(model, '_history'):
models_to_update_history.add(model.__name__)
2008-09-13 19:41:02 +02:00
# Instanciate a new parser for the package:
tryton_parser = convert.TrytondXmlHandler(pool=pool,
module=module, module_state=package_state)
2008-09-13 19:41:02 +02:00
for filename in package.info.get('xml', []):
filename = filename.replace('/', os.sep)
logger.info('%s:loading %s', module, filename)
# Feed the parser with xml content:
with tools.file_open(OPJ(module, filename), 'rb') as fp:
tryton_parser.parse_xmlstream(fp)
modules_todo.append((module, list(tryton_parser.to_delete)))
2008-09-13 19:41:02 +02:00
localedir = '%s/%s' % (package.info['directory'], 'locale')
for filename in itertools.chain(
iglob('%s/*.po' % localedir),
iglob('%s/override/*.po' % localedir)):
filename = filename.replace('/', os.sep)
lang2 = os.path.splitext(os.path.basename(filename))[0]
if lang2 not in lang:
continue
logger.info('%s:loading %s', module,
filename[len(package.info['directory']) + 1:])
Translation = pool.get('ir.translation')
Translation.translation_import(lang2, module, filename)
if package_state == 'to remove':
continue
cursor.execute(*ir_module.select(ir_module.id,
where=(ir_module.name == package.name)))
try:
module_id, = cursor.fetchone()
cursor.execute(*ir_module.update([ir_module.state],
['installed'], where=(ir_module.id == module_id)))
except TypeError:
cursor.execute(*ir_module.insert(
[ir_module.create_uid, ir_module.create_date,
ir_module.name, ir_module.state],
[[0, CurrentTimestamp(), package.name,
'installed'],
]))
module2state[package.name] = 'installed'
Transaction().connection.commit()
2016-03-17 22:56:58 +01:00
if not update:
pool.setup()
for model_name in models_to_update_history:
model = pool.get(model_name)
if model._history:
logger.info('history:update %s', model.__name__)
model._update_history_table()
# Vacuum :
while modules_todo:
(module, to_delete) = modules_todo.pop()
convert.post_import(pool, module, to_delete)
2016-03-17 22:56:58 +01:00
logger.info('all modules loaded')
2008-09-13 19:41:02 +02:00
2012-04-01 23:08:15 +02:00
2008-09-13 19:41:02 +02:00
def get_module_list():
2008-12-01 20:25:09 +01:00
module_list = set()
2008-09-13 19:41:02 +02:00
if os.path.exists(MODULES_PATH) and os.path.isdir(MODULES_PATH):
for file in os.listdir(MODULES_PATH):
if file.startswith('.'):
continue
2015-03-15 15:52:03 +01:00
if file == '__pycache__':
continue
2008-09-13 19:41:02 +02:00
if os.path.isdir(OPJ(MODULES_PATH, file)):
2008-12-01 20:25:09 +01:00
module_list.add(file)
2009-08-07 10:42:23 +02:00
update_egg_modules()
module_list.update(EGG_MODULES.keys())
2008-12-01 20:25:09 +01:00
module_list.add('ir')
module_list.add('res')
2013-09-16 11:38:11 +02:00
module_list.add('tests')
2008-12-01 20:25:09 +01:00
return list(module_list)
2008-09-13 19:41:02 +02:00
2012-04-01 23:08:15 +02:00
def register_classes():
2009-08-07 10:42:23 +02:00
'''
Import modules to register the classes in the Pool
'''
import trytond.ir
2012-09-24 22:16:53 +02:00
trytond.ir.register()
import trytond.res
2012-09-24 22:16:53 +02:00
trytond.res.register()
2013-09-16 11:38:11 +02:00
import trytond.tests
trytond.tests.register()
2009-02-11 14:52:22 +01:00
2008-09-13 19:41:02 +02:00
for package in create_graph(get_module_list())[0]:
module = package.name
2015-03-05 11:32:18 +01:00
logger.info('%s:registering classes', module)
2008-09-13 19:41:02 +02:00
2016-01-16 19:20:30 +01:00
if module in ('ir', 'res', 'tests'):
2008-09-13 19:41:02 +02:00
MODULES.append(module)
continue
2011-05-26 15:21:15 +02:00
if os.path.isdir(OPJ(MODULES_PATH, module)):
2012-10-24 23:38:12 +02:00
mod_path = MODULES_PATH
2008-11-18 17:40:30 +01:00
elif module in EGG_MODULES:
ep = EGG_MODULES[module]
mod_path = os.path.join(ep.dist.location,
*ep.module_name.split('.')[:-1])
if not os.path.isdir(mod_path):
# Find module in path
for path in sys.path:
mod_path = os.path.join(path,
*ep.module_name.split('.')[:-1])
if os.path.isdir(os.path.join(mod_path, module)):
break
if not os.path.isdir(os.path.join(mod_path, module)):
# When testing modules from setuptools location is the
# module directory
mod_path = os.path.dirname(ep.dist.location)
2008-11-18 17:40:30 +01:00
else:
raise Exception('Couldn\'t find module %s' % module)
2012-10-24 23:38:12 +02:00
mod_file, pathname, description = imp.find_module(module,
[mod_path])
the_module = imp.load_module('trytond.modules.' + module,
mod_file, pathname, description)
# Some modules register nothing in the Pool
if hasattr(the_module, 'register'):
the_module.register()
if mod_file is not None:
mod_file.close()
2008-09-13 19:41:02 +02:00
MODULES.append(module)
2012-04-01 23:08:15 +02:00
def load_modules(database_name, pool, update=None, lang=None):
2008-09-30 11:55:07 +02:00
res = True
2014-01-31 00:02:03 +01:00
def _load_modules():
2014-07-20 12:39:15 +02:00
global res
2015-05-28 21:02:01 +02:00
TableHandler = backend.get('TableHandler')
transaction = Transaction()
2015-05-28 21:02:01 +02:00
with transaction.connection.cursor() as cursor:
# Migration from 3.6: remove double module
old_table = 'ir_module_module'
new_table = 'ir_module'
if TableHandler.table_exist(old_table):
TableHandler.table_rename(old_table, new_table)
if update:
cursor.execute(*ir_module.select(ir_module.name,
where=ir_module.state.in_(('installed', 'to install',
'to upgrade', 'to remove'))))
else:
cursor.execute(*ir_module.select(ir_module.name,
where=ir_module.state.in_(('installed', 'to upgrade',
'to remove'))))
module_list = [name for (name,) in cursor.fetchall()]
if update:
module_list += update
graph = create_graph(module_list)[0]
2008-09-13 19:41:02 +02:00
load_module_graph(graph, pool, update, lang)
2008-09-13 19:41:02 +02:00
if update:
cursor.execute(*ir_module.select(ir_module.name,
2013-09-06 22:32:37 +02:00
where=(ir_module.state == 'to remove')))
fetchall = cursor.fetchall()
if fetchall:
for (mod_name,) in fetchall:
# TODO check if ressource not updated by the user
cursor.execute(*ir_model_data.select(
ir_model_data.model, ir_model_data.db_id,
where=(ir_model_data.module == mod_name),
order_by=ir_model_data.id.desc))
for rmod, rid in cursor.fetchall():
Model = pool.get(rmod)
Model.delete([Model(rid)])
Transaction().connection.commit()
cursor.execute(*ir_module.update([ir_module.state],
['uninstalled'],
where=(ir_module.state == 'to remove')))
Transaction().connection.commit()
res = False
2008-09-13 19:41:02 +02:00
Module = pool.get('ir.module')
Module.update_list()
# Need to commit to unlock SQLite database
transaction.commit()
Cache.resets(database_name)
2014-01-31 00:02:03 +01:00
if not Transaction().connection:
2014-01-31 00:02:03 +01:00
with Transaction().start(database_name, 0):
_load_modules()
else:
with Transaction().new_transaction(), \
2014-01-31 00:02:03 +01:00
Transaction().set_user(0), \
Transaction().reset_context():
_load_modules()
2008-09-30 11:55:07 +02:00
return res