[IMP] wsgi: modules can be pre-loaded and expose a WSGI handler.

An option --load is added to list the modules to pre-load.
sys.path and sys.modules are initialized when the config is
parsed.

bzr revid: vmt@openerp.com-20110902133136-4v7fgptyd0g1kc5s
This commit is contained in:
Vo Minh Thu 2011-09-02 15:31:36 +02:00
parent 79d0e39c4c
commit 8ca1a87201
9 changed files with 124 additions and 31 deletions

View File

@ -259,6 +259,16 @@ if __name__ == "__main__":
if config["stop_after_init"]:
sys.exit(0)
for m in openerp.conf.server_wide_modules:
__import__(m)
# Register a WSGI entry point if any.
info = openerp.modules.module.load_information_from_description_file(m)
if info['wsgi']:
openerp.wsgi.register_wsgi_handler(getattr(sys.modules[m], info['wsgi']))
openerp.wsgi.serve()
setup_pid_file()
setup_signal_handlers()
start_services()

View File

@ -22,10 +22,15 @@
""" Addons module.
This module only serves to contain OpenERP addons. For the code to
manage those addons, see openerp.modules. This module conveniently
reexports some symbols from openerp.modules. Importing them from here
is deprecated.
This module serves to contain all OpenERP addons, across all configured addons
paths. For the code to manage those addons, see openerp.modules.
Addons are made available here (i.e. under openerp.addons) after
openerp.tools.config.parse_config() is called (so that the addons paths
are known).
This module also conveniently reexports some symbols from openerp.modules.
Importing them from here is deprecated.
"""

View File

@ -25,5 +25,10 @@ import res
import publisher_warranty
import report
def YEAH(environ, start_response):
response = 'YEAH.\n'
start_response('200 OK', [('Content-Type', 'text/plain'), ('Content-Length', str(len(response)))])
return [response]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -93,6 +93,7 @@
'test/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules.
'test/test_ir_values.yml',
],
'wsgi': 'YEAH',
'installable': True,
'active': True,
'certificate': '0076807797149',

View File

@ -28,8 +28,21 @@ parsing, configuration file loading and saving, ...) in this module
and provide real Python variables, e.g. addons_paths is really a list
of paths.
To initialize properly this module, openerp.tools.config.parse_config()
must be used.
"""
import deprecation
# Paths to search for OpenERP addons.
addons_paths = []
# List of server-wide modules to load. Those modules are supposed to provide
# features not necessarily tied to a particular database. This is in contrast
# to modules that are always bound to a specific database when they are
# installed (i.e. the majority of OpenERP addons). This is set with the --load
# command-line option.
server_wide_modules = []
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -58,16 +58,16 @@ class Graph(dict):
"""
def add_node(self, name, deps):
def add_node(self, name, info):
max_depth, father = 0, None
for n in [Node(x, self) for x in deps]:
for n in [Node(x, self, None) for x in info['depends']]:
if n.depth >= max_depth:
father = n
max_depth = n.depth
if father:
return father.add_child(name)
return father.add_child(name, info)
else:
return Node(name, self)
return Node(name, self, info)
def update_from_db(self, cr):
if not len(self):
@ -120,7 +120,7 @@ class Graph(dict):
continue
later.clear()
current.remove(package)
node = self.add_node(package, deps)
node = self.add_node(package, info)
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:
@ -154,12 +154,13 @@ class Graph(dict):
class Singleton(object):
def __new__(cls, name, graph):
def __new__(cls, name, graph, info):
if name in graph:
inst = graph[name]
else:
inst = object.__new__(cls)
inst.name = name
inst.info = info
graph[name] = inst
return inst
@ -167,19 +168,21 @@ class Singleton(object):
class Node(Singleton):
""" One module in the modules dependency graph.
Node acts as a per-module singleton.
Node acts as a per-module singleton. A node is constructed via
Graph.add_module() or Graph.add_modules(). Some of its fields are from
ir_module_module (setted by Graph.update_from_db()).
"""
def __init__(self, name, graph):
def __init__(self, name, graph, info):
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)
def add_child(self, name, info):
node = Node(name, self.graph, info)
node.depth = self.depth + 1
if node not in self.children:
self.children.append(node)

View File

@ -31,7 +31,6 @@ 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
@ -58,6 +57,11 @@ loaded = []
logger = netsvc.Logger()
def initialize_sys_path():
""" Add all addons paths in sys.path.
This ensures something like ``import crm`` works even if the addons are
not in the PYTHONPATH.
"""
global ad_paths
if ad_paths:
@ -250,6 +254,7 @@ def load_information_from_description_file(module):
info['license'] = info.get('license') or 'AGPL-3'
info.setdefault('installable', True)
info.setdefault('active', False)
info.setdefault('wsgi', None) # WSGI entry point, given as a string
for kind in ['data', 'demo', 'test',
'init_xml', 'update_xml', 'demo_xml']:
info.setdefault(kind, [])
@ -290,23 +295,22 @@ def init_module_models(cr, module_name, obj_list):
t[1](cr, *t[2])
cr.commit()
# Import hook to write a addon m in both sys.modules['m'] and
# sys.modules['openerp.addons.m']. Otherwise it could be loaded twice
# if imported twice using different names.
#class MyImportHook(object):
# def find_module(self, module_name, package_path):
# print ">>>", module_name, package_path
# def load_module(self, module_name):
# raise ImportError("Restricted")
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()
#sys.meta_path.append(MyImportHook())
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.)
This loads the module and register all of its models, thanks to either
the MetaModel metaclass, or the explicit instantiation of the model.
"""
@ -326,7 +330,7 @@ def register_module_classes(m):
try:
zip_mod_path = mod_path + '.zip'
if not os.path.isfile(zip_mod_path):
load_module(m)
__import__(m)
else:
zimp = zipimport.zipimporter(zip_mod_path)
zimp.load_module(m)

View File

@ -24,6 +24,7 @@ import optparse
import os
import sys
import openerp
import openerp.conf
import openerp.loglevels as loglevels
import logging
import openerp.release as release
@ -101,8 +102,10 @@ class configmanager(object):
group.add_option("-P", "--import-partial", dest="import_partial", my_default='',
help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.")
group.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored")
group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules")
parser.add_option_group(group)
# XML-RPC / HTTP
group = optparse.OptionGroup(parser, "XML-RPC Configuration")
group.add_option("--xmlrpc-interface", dest="xmlrpc_interface", my_default='',
help="Specify the TCP IP address for the XML-RPC protocol. The empty string binds to all interfaces.")
@ -112,6 +115,7 @@ class configmanager(object):
help="disable the XML-RPC protocol")
parser.add_option_group(group)
# XML-RPC / HTTPS
title = "XML-RPC Secure Configuration"
if not self.has_ssl:
title += " (disabled as ssl is unavailable)"
@ -258,9 +262,26 @@ class configmanager(object):
self.options[option.dest] = option.my_default
self.casts[option.dest] = option
self.parse_config()
self.parse_config(None, False)
def parse_config(self, args=None):
def parse_config(self, args=None, complete=True):
""" Parse the configuration file (if any) and the command-line
arguments.
This method initializes openerp.tools.config and openerp.conf (the
former should be removed in the furture) with library-wide
configuration values.
This method must be called before proper usage of this library can be
made.
Typical usage of this method:
openerp.tools.config.parse_config(sys.argv[1:])
:param complete: this is a hack used in __init__(), leave it to True.
"""
if args is None:
args = []
opt, args = self.parser.parse_args(args)
@ -417,6 +438,17 @@ class configmanager(object):
if opt.save:
self.save()
openerp.conf.addons_paths = self.options['addons_path'].split(',')
openerp.conf.server_wide_modules = \
map(lambda m: m.strip(), opt.server_wide_modules.split(',')) if \
opt.server_wide_modules else []
if complete:
openerp.modules.module.initialize_sys_path()
openerp.modules.loading.open_openerp_namespace()
# openerp.addons.__path__.extend(openerp.conf.addons_paths) # This
# is not compatible with initialize_sys_path(): import crm and
# import openerp.addons.crm load twice the module.
def _generate_pgpassfile(self):
"""
Generate the pgpass file with the parameters from the command line (db_host, db_user,

View File

@ -95,11 +95,31 @@ def legacy_wsgi_xmlrpc(environ, start_response):
def wsgi_jsonrpc(environ, start_response):
pass
def wsgi_modules(environ, start_response):
""" WSGI handler dispatching to addons-provided entry points."""
pass
# WSGI handlers provided by modules loaded with the --load command-line option.
module_handlers = []
def register_wsgi_handler(handler):
""" Register a WSGI handler.
Handlers are tried in the order they are added. We might provide a way to
register a handler for specific routes later.
"""
module_handlers.append(handler)
def application(environ, start_response):
""" WSGI entry point."""
# Try all handlers until one returns some result (i.e. not None).
wsgi_handlers = [wsgi_xmlrpc, wsgi_jsonrpc, legacy_wsgi_xmlrpc]
wsgi_handlers = [
wsgi_xmlrpc,
wsgi_jsonrpc,
legacy_wsgi_xmlrpc,
wsgi_modules,
] + module_handlers
for handler in wsgi_handlers:
result = handler(environ, start_response)
if result is None: