2013-01-30 11:36:46 +00:00
# -*- coding: utf-8 -*-
from functools import wraps
import logging
2013-04-05 13:32:03 +00:00
from psycopg2 import IntegrityError , OperationalError , errorcodes
2013-04-04 13:07:04 +00:00
import random
2013-01-30 11:36:46 +00:00
import threading
2013-04-04 13:07:04 +00:00
import time
2013-01-30 11:36:46 +00:00
import openerp
from openerp . tools . translate import translate
from openerp . osv . orm import except_orm
2014-01-09 09:32:58 +00:00
from contextlib import contextmanager
2013-01-30 11:36:46 +00:00
2013-01-30 15:02:07 +00:00
import security
2013-01-30 11:36:46 +00:00
_logger = logging . getLogger ( __name__ )
2013-04-04 13:07:04 +00:00
PG_CONCURRENCY_ERRORS_TO_RETRY = ( errorcodes . LOCK_NOT_AVAILABLE , errorcodes . SERIALIZATION_FAILURE , errorcodes . DEADLOCK_DETECTED )
MAX_TRIES_ON_CONCURRENCY_FAILURE = 5
2013-01-30 15:02:07 +00:00
def dispatch ( method , params ) :
( db , uid , passwd ) = params [ 0 : 3 ]
2013-04-04 13:07:04 +00:00
# set uid tracker - cleaned up at the WSGI
# dispatching phase in openerp.service.wsgi_server.application
2013-01-30 15:02:07 +00:00
threading . current_thread ( ) . uid = uid
2013-04-04 13:07:04 +00:00
2013-01-30 15:02:07 +00:00
params = params [ 3 : ]
if method == ' obj_list ' :
raise NameError ( " obj_list has been discontinued via RPC as of 6.0, please query ir.model directly! " )
if method not in [ ' execute ' , ' execute_kw ' , ' exec_workflow ' ] :
raise NameError ( " Method not available %s " % method )
security . check ( db , uid , passwd )
openerp . modules . registry . RegistryManager . check_registry_signaling ( db )
fn = globals ( ) [ method ]
res = fn ( db , uid , * params )
openerp . modules . registry . RegistryManager . signal_caches_change ( db )
return res
2013-01-30 11:36:46 +00:00
def check ( f ) :
@wraps ( f )
2014-01-31 16:31:39 +00:00
def wrapper ( ___dbname , * args , * * kwargs ) :
2013-01-30 11:36:46 +00:00
""" Wraps around OSV functions and normalises a few exceptions
"""
2014-01-31 16:31:39 +00:00
dbname = ___dbname # NOTE: this forbid to use "___dbname" as arguments in http routes
2013-01-30 11:36:46 +00:00
def tr ( src , ttype ) :
# We try to do the same as the _(), but without the frame
# inspection, since we aready are wrapping an osv function
# trans_obj = self.get('ir.translation') cannot work yet :(
ctx = { }
if not kwargs :
if args and isinstance ( args [ - 1 ] , dict ) :
ctx = args [ - 1 ]
elif isinstance ( kwargs , dict ) :
[FIX] web: current transaction is aborted
This reverts commit bd9cbdfc41584c829be48f6a30abb7be3a8a63fb.
The above revision solved the SQL constraints not being
translated when raised. They were not translated because
the context, containing the lang, was not located as expected
in the `kwargs` dict.
While it solved this issue, it had as side-effect to raise
`current transaction is aborted,
commands ignored until end of transaction block` errors more
often when using the web client.
This can be explained by the double check, when the first
check raised this error
- which can happen, e.g. when the cursor is closed,
there is a retry mechanism in such cases -
and by the fact the transaction was not rollbacked.
This issue could have been solved as well by rollbacking
the transaction, but it is regarded as not-so-clean.
Therefore, to solve this issue, while still having
the SQL constraints translated, we apply the
second patch proposed in bd9cbdfc41584c829be48f6a30abb7be3a8a63fb
commit message, which is not-so-clean as well, but
which is a proper solution.
opw-651393
2015-10-13 15:12:34 +00:00
if ' context ' in kwargs :
ctx = kwargs [ ' context ' ]
elif ' kwargs ' in kwargs :
# http entry points such as call_kw()
ctx = kwargs [ ' kwargs ' ] . get ( ' context ' )
2013-01-30 11:36:46 +00:00
uid = 1
if args and isinstance ( args [ 0 ] , ( long , int ) ) :
uid = args [ 0 ]
lang = ctx and ctx . get ( ' lang ' )
if not ( lang or hasattr ( src , ' __call__ ' ) ) :
return src
# We open a *new* cursor here, one reason is that failed SQL
# queries (as in IntegrityError) will invalidate the current one.
cr = False
if hasattr ( src , ' __call__ ' ) :
# callable. We need to find the right parameters to call
# the orm._sql_message(self, cr, uid, ids, context) function,
# or we skip..
2013-03-27 11:10:14 +00:00
# our signature is f(registry, dbname [,uid, obj, method, args])
2013-01-30 11:36:46 +00:00
try :
if args and len ( args ) > 1 :
# TODO self doesn't exist, but was already wrong before (it was not a registry but just the object_service.
obj = self . get ( args [ 1 ] )
if len ( args ) > 3 and isinstance ( args [ 3 ] , ( long , int , list ) ) :
ids = args [ 3 ]
else :
ids = [ ]
cr = openerp . sql_db . db_connect ( dbname ) . cursor ( )
return src ( obj , cr , uid , ids , context = ( ctx or { } ) )
except Exception :
pass
finally :
if cr : cr . close ( )
return False # so that the original SQL error will
# be returned, it is the best we have.
try :
cr = openerp . sql_db . db_connect ( dbname ) . cursor ( )
res = translate ( cr , name = False , source_type = ttype ,
lang = lang , source = src )
if res :
return res
else :
return src
finally :
if cr : cr . close ( )
def _ ( src ) :
return tr ( src , ' code ' )
2013-04-04 13:07:04 +00:00
tries = 0
while True :
try :
2014-02-09 00:40:05 +00:00
if openerp . registry ( dbname ) . _init and not openerp . tools . config [ ' test_enable ' ] :
2013-04-04 13:07:04 +00:00
raise openerp . exceptions . Warning ( ' Currently, this database is not fully loaded and can not be used. ' )
return f ( dbname , * args , * * kwargs )
except OperationalError , e :
# Automatically retry the typical transaction serialization errors
if e . pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY :
raise
if tries > = MAX_TRIES_ON_CONCURRENCY_FAILURE :
_logger . warning ( " %s , maximum number of tries reached " % errorcodes . lookup ( e . pgcode ) )
raise
wait_time = random . uniform ( 0.0 , 2 * * tries )
tries + = 1
_logger . info ( " %s , retry %d / %d in %.04f sec... " % ( errorcodes . lookup ( e . pgcode ) , tries , MAX_TRIES_ON_CONCURRENCY_FAILURE , wait_time ) )
time . sleep ( wait_time )
except IntegrityError , inst :
registry = openerp . registry ( dbname )
for key in registry . _sql_error . keys ( ) :
if key in inst [ 0 ] :
raise openerp . osv . orm . except_orm ( _ ( ' Constraint Error ' ) , tr ( registry . _sql_error [ key ] , ' sql_constraint ' ) or inst [ 0 ] )
if inst . pgcode in ( errorcodes . NOT_NULL_VIOLATION , errorcodes . FOREIGN_KEY_VIOLATION , errorcodes . RESTRICT_VIOLATION ) :
msg = _ ( ' The operation cannot be completed, probably due to the following: \n - deletion: you may be trying to delete a record while other records still reference it \n - creation/update: a mandatory field is not correctly set ' )
_logger . debug ( " IntegrityError " , exc_info = True )
try :
errortxt = inst . pgerror . replace ( ' « ' , ' " ' ) . replace ( ' » ' , ' " ' )
if ' " public " . ' in errortxt :
context = errortxt . split ( ' " public " . ' ) [ 1 ]
model_name = table = context . split ( ' " ' ) [ 1 ]
else :
last_quote_end = errortxt . rfind ( ' " ' )
last_quote_begin = errortxt . rfind ( ' " ' , 0 , last_quote_end )
model_name = table = errortxt [ last_quote_begin + 1 : last_quote_end ] . strip ( )
model = table . replace ( " _ " , " . " )
2013-04-11 07:30:17 +00:00
if model in registry :
model_obj = registry [ model ]
2013-04-04 13:07:04 +00:00
model_name = model_obj . _description or model_obj . _name
msg + = _ ( ' \n \n [object with reference: %s - %s ] ' ) % ( model_name , model )
except Exception :
pass
raise openerp . osv . orm . except_orm ( _ ( ' Integrity Error ' ) , msg )
else :
raise openerp . osv . orm . except_orm ( _ ( ' Integrity Error ' ) , inst [ 0 ] )
2013-01-30 11:36:46 +00:00
return wrapper
def execute_cr ( cr , uid , obj , method , * args , * * kw ) :
2013-03-27 11:10:14 +00:00
object = openerp . registry ( cr . dbname ) . get ( obj )
2014-07-06 14:44:26 +00:00
if object is None :
2013-06-05 11:12:04 +00:00
raise except_orm ( ' Object Error ' , " Object %s doesn ' t exist " % obj )
2013-01-30 11:36:46 +00:00
return getattr ( object , method ) ( cr , uid , * args , * * kw )
def execute_kw ( db , uid , obj , method , args , kw = None ) :
return execute ( db , uid , obj , method , * args , * * kw or { } )
2014-01-09 09:32:58 +00:00
@check
def execute ( db , uid , obj , method , * args , * * kw ) :
threading . currentThread ( ) . dbname = db
2014-04-08 12:51:22 +00:00
with openerp . registry ( db ) . cursor ( ) as cr :
2014-01-09 09:32:58 +00:00
if method . startswith ( ' _ ' ) :
raise except_orm ( ' Access Denied ' , ' Private methods (such as %s ) cannot be called remotely. ' % ( method , ) )
res = execute_cr ( cr , uid , obj , method , * args , * * kw )
if res is None :
_logger . warning ( ' The method %s of the object %s can not return `None` ! ' , method , obj )
return res
2013-01-30 11:36:46 +00:00
def exec_workflow_cr ( cr , uid , obj , signal , * args ) :
res_id = args [ 0 ]
2013-06-05 11:12:04 +00:00
return execute_cr ( cr , uid , obj , ' signal_workflow ' , [ res_id ] , signal ) [ res_id ]
2013-01-30 11:36:46 +00:00
2014-01-09 09:32:58 +00:00
2013-01-30 11:36:46 +00:00
@check
def exec_workflow ( db , uid , obj , signal , * args ) :
2014-04-08 12:51:22 +00:00
with openerp . registry ( db ) . cursor ( ) as cr :
2014-01-09 09:32:58 +00:00
return exec_workflow_cr ( cr , uid , obj , signal , * args )
2013-01-30 11:36:46 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: