[FIX] better search view on modules
[odoo/odoo.git] / openerp / netsvc.py
index 359813e..c112076 100644 (file)
@@ -21,7 +21,6 @@
 ##############################################################################
 
 import errno
 ##############################################################################
 
 import errno
-import heapq
 import logging
 import logging.handlers
 import os
 import logging
 import logging.handlers
 import os
@@ -31,12 +30,14 @@ import socket
 import sys
 import threading
 import time
 import sys
 import threading
 import time
+import traceback
 import types
 from pprint import pformat
 
 # TODO modules that import netsvc only for things from loglevels must be changed to use loglevels.
 from loglevels import *
 import tools
 import types
 from pprint import pformat
 
 # TODO modules that import netsvc only for things from loglevels must be changed to use loglevels.
 from loglevels import *
 import tools
+import openerp
 
 def close_socket(sock):
     """ Closes a socket instance cleanly
 
 def close_socket(sock):
     """ Closes a socket instance cleanly
@@ -60,20 +61,19 @@ def close_socket(sock):
 #.apidoc title: Common Services: netsvc
 #.apidoc module-mods: member-order: bysource
 
 #.apidoc title: Common Services: netsvc
 #.apidoc module-mods: member-order: bysource
 
+def abort_response(dummy_1, description, dummy_2, details):
+    # TODO Replace except_{osv,orm} with these directly.
+    raise openerp.osv.osv.except_osv(description, details)
+
 class Service(object):
     """ Base class for *Local* services
 
         Functionality here is trusted, no authentication.
     """
     _services = {}
 class Service(object):
     """ Base class for *Local* services
 
         Functionality here is trusted, no authentication.
     """
     _services = {}
-    def __init__(self, name, audience=''):
+    def __init__(self, name):
         Service._services[name] = self
         self.__name = name
         Service._services[name] = self
         self.__name = name
-        self._methods = {}
-
-    def joinGroup(self, name):
-        raise Exception("No group for local services")
-        #GROUPS.setdefault(name, {})[self.__name] = self
 
     @classmethod
     def exists(cls, name):
 
     @classmethod
     def exists(cls, name):
@@ -84,75 +84,39 @@ class Service(object):
         if cls.exists(name):
             cls._services.pop(name)
 
         if cls.exists(name):
             cls._services.pop(name)
 
-    def exportMethod(self, method):
-        if callable(method):
-            self._methods[method.__name__] = method
-
-    def abortResponse(self, error, description, origin, details):
-        if not tools.config['debug_mode']:
-            raise Exception("%s -- %s\n\n%s"%(origin, description, details))
-        else:
-            raise
+def LocalService(name):
+  # Special case for addons support, will be removed in a few days when addons
+  # are updated to directly use openerp.osv.osv.service.
+  if name == 'object_proxy':
+      return openerp.osv.osv.service
 
 
-class LocalService(object):
-    """ Proxy for local services.
-
-        Any instance of this class will behave like the single instance
-        of Service(name)
-    """
-    __logger = logging.getLogger('service')
-    def __init__(self, name):
-        self.__name = name
-        try:
-            self._service = Service._services[name]
-            for method_name, method_definition in self._service._methods.items():
-                setattr(self, method_name, method_definition)
-        except KeyError, keyError:
-            self.__logger.error('This service does not exist: %s' % (str(keyError),) )
-            raise
-
-    def __call__(self, method, *params):
-        return getattr(self, method)(*params)
+  return Service._services[name]
 
 class ExportService(object):
     """ Proxy for exported services.
 
 
 class ExportService(object):
     """ Proxy for exported services.
 
-    All methods here should take an AuthProxy as their first parameter. It
-    will be appended by the calling framework.
-
     Note that this class has no direct proxy, capable of calling
     eservice.method(). Rather, the proxy should call
     Note that this class has no direct proxy, capable of calling
     eservice.method(). Rather, the proxy should call
-    dispatch(method,auth,params)
+    dispatch(method, params)
     """
 
     _services = {}
     """
 
     _services = {}
-    _groups = {}
     _logger = logging.getLogger('web-services')
     _logger = logging.getLogger('web-services')
-
-    def __init__(self, name, audience=''):
+    
+    def __init__(self, name):
         ExportService._services[name] = self
         self.__name = name
         self._logger.debug("Registered an exported service: %s" % name)
 
         ExportService._services[name] = self
         self.__name = name
         self._logger.debug("Registered an exported service: %s" % name)
 
-    def joinGroup(self, name):
-        ExportService._groups.setdefault(name, {})[self.__name] = self
-
     @classmethod
     def getService(cls,name):
         return cls._services[name]
 
     @classmethod
     def getService(cls,name):
         return cls._services[name]
 
-    def dispatch(self, method, auth, params):
+    # Dispatch a RPC call w.r.t. the method name. The dispatching
+    # w.r.t. the service (this class) is done by OpenERPDispatcher.
+    def dispatch(self, method, params):
         raise Exception("stub dispatch at %s" % self.__name)
 
         raise Exception("stub dispatch at %s" % self.__name)
 
-    def new_dispatch(self,method,auth,params):
-        raise Exception("stub dispatch at %s" % self.__name)
-
-    def abortResponse(self, error, description, origin, details):
-        if not tools.config['debug_mode']:
-            raise Exception("%s -- %s\n\n%s"%(origin, description, details))
-        else:
-            raise
-
 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10)
 #The background is set with 40 plus the number of the color, and the foreground with 30
 #These are the sequences need to get colored ouput
 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10)
 #The background is set with 40 plus the number of the color, and the foreground with 30
 #These are the sequences need to get colored ouput
@@ -244,83 +208,6 @@ def init_alternative_logger():
     logger.addHandler(handler)
     logger.setLevel(logging.ERROR)
 
     logger.addHandler(handler)
     logger.setLevel(logging.ERROR)
 
-class Agent(object):
-    """ Singleton that keeps track of cancellable tasks to run at a given
-        timestamp.
-
-        The tasks are characterised by:
-
-            * a timestamp
-            * the database on which the task run
-            * the function to call
-            * the arguments and keyword arguments to pass to the function
-
-        Implementation details:
-
-          - Tasks are stored as list, allowing the cancellation by setting
-            the timestamp to 0.
-          - A heapq is used to store tasks, so we don't need to sort
-            tasks ourself.
-    """
-    __tasks = []
-    __tasks_by_db = {}
-    _logger = logging.getLogger('netsvc.agent')
-
-    @classmethod
-    def setAlarm(cls, function, timestamp, db_name, *args, **kwargs):
-        task = [timestamp, db_name, function, args, kwargs]
-        heapq.heappush(cls.__tasks, task)
-        cls.__tasks_by_db.setdefault(db_name, []).append(task)
-
-    @classmethod
-    def cancel(cls, db_name):
-        """Cancel all tasks for a given database. If None is passed, all tasks are cancelled"""
-        cls._logger.debug("Cancel timers for %s db", db_name or 'all')
-        if db_name is None:
-            cls.__tasks, cls.__tasks_by_db = [], {}
-        else:
-            if db_name in cls.__tasks_by_db:
-                for task in cls.__tasks_by_db[db_name]:
-                    task[0] = 0
-
-    @classmethod
-    def quit(cls):
-        cls.cancel(None)
-
-    @classmethod
-    def runner(cls):
-        """Neverending function (intended to be ran in a dedicated thread) that
-           checks every 60 seconds tasks to run. TODO: make configurable
-        """
-        current_thread = threading.currentThread()
-        while True:
-            while cls.__tasks and cls.__tasks[0][0] < time.time():
-                task = heapq.heappop(cls.__tasks)
-                timestamp, dbname, function, args, kwargs = task
-                cls.__tasks_by_db[dbname].remove(task)
-                if not timestamp:
-                    # null timestamp -> cancelled task
-                    continue
-                current_thread.dbname = dbname   # hack hack
-                cls._logger.debug("Run %s.%s(*%s, **%s)", function.im_class.__name__, function.func_name, args, kwargs)
-                delattr(current_thread, 'dbname')
-                task_thread = threading.Thread(target=function, name='netsvc.Agent.task', args=args, kwargs=kwargs)
-                # force non-daemon task threads (the runner thread must be daemon, and this property is inherited by default)
-                task_thread.setDaemon(False)
-                task_thread.start()
-                time.sleep(1)
-            time.sleep(60)
-
-def start_agent():
-    agent_runner = threading.Thread(target=Agent.runner, name="netsvc.Agent.runner")
-    # the agent runner is a typical daemon thread, that will never quit and must be
-    # terminated when the main process exits - with no consequence (the processing
-    # threads it spawns are not marked daemon)
-    agent_runner.setDaemon(True)
-    agent_runner.start()
-
-import traceback
-
 class Server:
     """ Generic interface for all servers with an event loop etc.
         Override this to impement http, net-rpc etc. servers.
 class Server:
     """ Generic interface for all servers with an event loop etc.
         Override this to impement http, net-rpc etc. servers.
@@ -403,11 +290,6 @@ class Server:
     def _close_socket(self):
         close_socket(self.socket)
 
     def _close_socket(self):
         close_socket(self.socket)
 
-class OpenERPDispatcherException(Exception):
-    def __init__(self, exception, traceback):
-        self.exception = exception
-        self.traceback = traceback
-
 def replace_request_password(args):
     # password is always 3rd argument in a request, we replace it in RPC logs
     # so it's easier to forward logs for diagnostics/debugging purposes...
 def replace_request_password(args):
     # password is always 3rd argument in a request, we replace it in RPC logs
     # so it's easier to forward logs for diagnostics/debugging purposes...
@@ -425,33 +307,47 @@ def log(title, msg, channel=logging.DEBUG_RPC, depth=None, fn=""):
             logger.log(channel, indent+line)
             indent=indent_after
 
             logger.log(channel, indent+line)
             indent=indent_after
 
-class OpenERPDispatcher:
-    def log(self, title, msg, channel=logging.DEBUG_RPC, depth=None, fn=""):
+def dispatch_rpc(service_name, method, params):
+    """ Handle a RPC call.
+
+    This is pure Python code, the actual marshalling (from/to XML-RPC or
+    NET-RPC) is done in a upper layer.
+    """
+    def _log(title, msg, channel=logging.DEBUG_RPC, depth=None, fn=""):
         log(title, msg, channel=channel, depth=depth, fn=fn)
         log(title, msg, channel=channel, depth=depth, fn=fn)
-    def dispatch(self, service_name, method, params):
-        try:
-            auth = getattr(self, 'auth_provider', None)
-            logger = logging.getLogger('result')
-            start_time = end_time = 0
-            if logger.isEnabledFor(logging.DEBUG_RPC_ANSWER):
-                self.log('service', tuple(replace_request_password(params)), depth=None, fn='%s.%s'%(service_name,method))
-            if logger.isEnabledFor(logging.DEBUG_RPC):
-                start_time = time.time()
-            result = ExportService.getService(service_name).dispatch(method, auth, params)
-            if logger.isEnabledFor(logging.DEBUG_RPC):
-                end_time = time.time()
-            if not logger.isEnabledFor(logging.DEBUG_RPC_ANSWER):
-                self.log('service (%.3fs)' % (end_time - start_time), tuple(replace_request_password(params)), depth=1, fn='%s.%s'%(service_name,method))
-            self.log('execution time', '%.3fs' % (end_time - start_time), channel=logging.DEBUG_RPC_ANSWER)
-            self.log('result', result, channel=logging.DEBUG_RPC_ANSWER)
-            return result
-        except Exception, e:
-            self.log('exception', tools.exception_to_unicode(e))
-            tb = getattr(e, 'traceback', sys.exc_info())
-            tb_s = "".join(traceback.format_exception(*tb))
-            if tools.config['debug_mode'] and isinstance(tb[2], types.TracebackType):
-                import pdb
-                pdb.post_mortem(tb[2])
-            raise OpenERPDispatcherException(e, tb_s)
+    try:
+        logger = logging.getLogger('result')
+        start_time = end_time = 0
+        if logger.isEnabledFor(logging.DEBUG_RPC_ANSWER):
+            _log('service', tuple(replace_request_password(params)), depth=None, fn='%s.%s'%(service_name,method))
+        if logger.isEnabledFor(logging.DEBUG_RPC):
+            start_time = time.time()
+        result = ExportService.getService(service_name).dispatch(method, params)
+        if logger.isEnabledFor(logging.DEBUG_RPC):
+            end_time = time.time()
+        if not logger.isEnabledFor(logging.DEBUG_RPC_ANSWER):
+            _log('service (%.3fs)' % (end_time - start_time), tuple(replace_request_password(params)), depth=1, fn='%s.%s'%(service_name,method))
+        _log('execution time', '%.3fs' % (end_time - start_time), channel=logging.DEBUG_RPC_ANSWER)
+        _log('result', result, channel=logging.DEBUG_RPC_ANSWER)
+        return result
+    except openerp.exceptions.AccessError:
+        raise
+    except openerp.exceptions.AccessDenied:
+        raise
+    except openerp.exceptions.Warning:
+        raise
+    except openerp.exceptions.DeferredException, e:
+        _log('exception', tools.exception_to_unicode(e))
+        post_mortem(e.traceback)
+        raise
+    except Exception, e:
+        _log('exception', tools.exception_to_unicode(e))
+        post_mortem(sys.exc_info())
+        raise
+
+def post_mortem(info):
+    if tools.config['debug_mode'] and isinstance(info[2], types.TracebackType):
+        import pdb
+        pdb.post_mortem(info[2])
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: