Initial import from Docker volume

This commit is contained in:
root
2025-12-26 13:11:43 +00:00
commit 4998dc066a
13336 changed files with 1767801 additions and 0 deletions

2
protocols/__init__.py Executable file
View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

268
protocols/dispatcher.py Executable file
View File

@@ -0,0 +1,268 @@
# -*- coding: utf-8 -*-
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import logging
import pydoc
import time
from sql import Table
from trytond import __version__, backend, security
from trytond.config import config, get_hostname
from trytond.exceptions import (
ConcurrencyException, LoginException, RateLimitException, UserError,
UserWarning)
from trytond.rpc import RPCReturnException
from trytond.tools import is_instance_method
from trytond.tools.logging import format_args
from trytond.transaction import Transaction, TransactionError
from trytond.worker import run_task
from trytond.wsgi import app
from .wrappers import HTTPStatus, Response, abort, with_pool
__all__ = ['register_authentication_service']
logger = logging.getLogger(__name__)
ir_configuration = Table('ir_configuration')
ir_lang = Table('ir_lang')
ir_module = Table('ir_module')
res_user = Table('res_user')
@app.route('/<string:database_name>/', methods=['POST'])
def rpc(request, database_name):
methods = {
'common.db.login': login,
'common.db.logout': logout,
'common.db.reset_password': reset_password,
'system.listMethods': list_method,
'system.methodHelp': help_method,
'system.methodSignature': lambda *a: 'signatures not supported',
}
return methods.get(request.rpc_method, _dispatch)(
request, database_name, *request.rpc_params)
def login(request, database_name, user, parameters, language=None):
context = {
'language': language,
'_request': request.context,
}
try:
session = security.login(
database_name, user, parameters, context=context)
code = HTTPStatus.UNAUTHORIZED
except backend.DatabaseOperationalError:
logger.error('fail to connect to %s', database_name, exc_info=True)
abort(HTTPStatus.NOT_FOUND)
except RateLimitException:
session = None
code = HTTPStatus.TOO_MANY_REQUESTS
if not session:
abort(code)
return session
@app.auth_required
def logout(request, database_name):
auth = request.authorization
security.logout(
database_name, auth.get('userid'), auth.get('session'),
context={'_request': request.context})
def reset_password(request, database_name, user, language=None):
authentications = config.get(
'session', 'authentications', default='password').split(',')
if not any('password' in m.split('+') for m in authentications):
abort(HTTPStatus.FORBIDDEN)
context = {
'language': language,
'_request': request.context,
}
try:
security.reset_password(database_name, user, context=context)
except backend.DatabaseOperationalError:
logger.error('fail to connect to %s', database_name, exc_info=True)
abort(HTTPStatus.NOT_FOUND)
except RateLimitException:
abort(HTTPStatus.TOO_MANY_REQUESTS)
@app.route('/', methods=['POST'])
def root(request, *args):
methods = {
'common.server.version': lambda *a: __version__,
'common.db.list': db_list,
'common.authentication.services': authentication_services,
}
return methods[request.rpc_method](request, *request.rpc_params)
@app.route('/', methods=['OPTIONS'])
@app.route('/<path:path>', methods=['OPTIONS'])
def options(request, path=None):
return Response(status=HTTPStatus.NO_CONTENT)
def db_exist(request, database_name):
try:
backend.Database(database_name).connect()
return True
except Exception:
return False
def db_list(request, *args):
if not config.getboolean('database', 'list'):
abort(HTTPStatus.FORBIDDEN)
context = {'_request': request.context}
hostname = get_hostname(request.host)
with Transaction().start(
None, 0, context=context, readonly=True, close=True,
) as transaction:
return transaction.database.list(hostname=hostname)
def authentication_services(request):
return _AUTHENTICATION_SERVICES
def register_authentication_service(name, url):
_AUTHENTICATION_SERVICES.append((name, url))
_AUTHENTICATION_SERVICES = []
@app.auth_required
@with_pool
def list_method(request, pool):
methods = []
for type in ('model', 'wizard', 'report'):
for object_name, obj in pool.iterobject(type=type):
for method in obj.__rpc__:
methods.append(type + '.' + object_name + '.' + method)
return methods
def get_object_method(request, pool):
method = request.rpc_method
type, _ = method.split('.', 1)
name = '.'.join(method.split('.')[1:-1])
method = method.split('.')[-1]
return pool.get(name, type=type), method
@app.auth_required
@with_pool
def help_method(request, pool):
obj, method = get_object_method(request, pool)
return pydoc.getdoc(getattr(obj, method))
@app.auth_required
@with_pool
def _dispatch(request, pool, *args, **kwargs):
obj, method = get_object_method(request, pool)
if method in obj.__rpc__:
rpc = obj.__rpc__[method]
else:
abort(HTTPStatus.FORBIDDEN)
user = request.user_id
session = None
if request.authorization.type == 'session':
session = request.authorization.get('session')
if rpc.fresh_session and session:
context = {'_request': request.context}
if not security.check_timeout(
pool.database_name, user, session, context=context):
abort(HTTPStatus.UNAUTHORIZED)
log_message = '%s.%s%s from %s@%s%s in %i ms'
username = request.authorization.username
if isinstance(username, bytes):
username = username.decode('utf-8')
log_args = (
obj.__name__, method,
format_args(args, kwargs, logger.isEnabledFor(logging.DEBUG)),
username, request.remote_addr, request.path)
def duration():
return (time.monotonic() - started) * 1000
started = time.monotonic()
retry = config.getint('database', 'retry')
count = 0
transaction_extras = {}
while True:
if count:
time.sleep(0.02 * (retry - count))
with Transaction().start(
pool.database_name, user,
readonly=rpc.readonly, timeout=rpc.timeout,
**transaction_extras) as transaction:
try:
c_args, c_kwargs, transaction.context, transaction.timestamp \
= rpc.convert(obj, *args, **kwargs)
transaction.context['_request'] = request.context
meth = rpc.decorate(getattr(obj, method))
if (rpc.instantiate is None
or not is_instance_method(obj, method)):
result = rpc.result(meth(*c_args, **c_kwargs))
else:
assert rpc.instantiate == 0
inst = c_args.pop(0)
if hasattr(inst, method):
result = rpc.result(meth(inst, *c_args, **c_kwargs))
else:
result = [rpc.result(meth(i, *c_args, **c_kwargs))
for i in inst]
except TransactionError as e:
transaction.rollback()
transaction.tasks.clear()
e.fix(transaction_extras)
continue
except backend.DatabaseTimeoutError:
logger.warning(log_message, *log_args, exc_info=True)
abort(HTTPStatus.REQUEST_TIMEOUT)
except backend.DatabaseOperationalError:
if count < retry and not rpc.readonly:
transaction.rollback()
transaction.tasks.clear()
count += 1
logger.debug("Retry: %i", count)
continue
logger.exception(log_message, *log_args, duration())
raise
except RPCReturnException as e:
transaction.rollback()
transaction.tasks.clear()
result = e.result()
except (ConcurrencyException, UserError, UserWarning,
LoginException):
logger.info(
log_message, *log_args, duration(),
exc_info=logger.isEnabledFor(logging.DEBUG))
raise
except Exception:
logger.exception(log_message, *log_args, duration())
raise
# Need to commit to unlock SQLite database
transaction.commit()
while transaction.tasks:
task_id = transaction.tasks.pop()
run_task(pool, task_id)
if session:
context = {'_request': request.context}
security.reset(pool.database_name, session, context=context)
logger.info(log_message, *log_args, duration())
logger.debug('Result: %r', result)
response = app.make_response(request, result)
if rpc.readonly and rpc.cache:
response.headers.extend(rpc.cache.headers())
return response

193
protocols/jsonrpc.py Executable file
View File

@@ -0,0 +1,193 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import base64
import datetime
import json
from decimal import Decimal
from werkzeug.exceptions import (
BadRequest, Conflict, Forbidden, InternalServerError, Locked,
TooManyRequests)
from werkzeug.wrappers import Response
from trytond.exceptions import (
ConcurrencyException, LoginException, MissingDependenciesException,
RateLimitException, TrytonException, UserWarning)
from trytond.protocols.wrappers import Request
from trytond.tools import cached_property
class JSONDecoder(object):
decoders = {}
@classmethod
def register(cls, klass, decoder):
assert klass not in cls.decoders
cls.decoders[klass] = decoder
def __call__(self, dct):
if dct.get('__class__') in self.decoders:
return self.decoders[dct['__class__']](dct)
return dct
JSONDecoder.register('datetime',
lambda dct: datetime.datetime(dct['year'], dct['month'], dct['day'],
dct['hour'], dct['minute'], dct['second'], dct['microsecond']))
JSONDecoder.register('date',
lambda dct: datetime.date(dct['year'], dct['month'], dct['day']))
JSONDecoder.register('time',
lambda dct: datetime.time(dct['hour'], dct['minute'], dct['second'],
dct['microsecond']))
JSONDecoder.register('timedelta',
lambda dct: datetime.timedelta(seconds=dct['seconds']))
def _bytes_decoder(dct):
cast = bytearray if bytes == str else bytes
return cast(base64.decodebytes(dct['base64'].encode('utf-8')))
JSONDecoder.register('bytes', _bytes_decoder)
JSONDecoder.register('Decimal', lambda dct: Decimal(dct['decimal']))
class JSONEncoder(json.JSONEncoder):
serializers = {}
@classmethod
def register(cls, klass, encoder):
assert klass not in cls.serializers
cls.serializers[klass] = encoder
def default(self, obj):
marshaller = self.serializers.get(type(obj),
super(JSONEncoder, self).default)
return marshaller(obj)
JSONEncoder.register(datetime.datetime,
lambda o: {
'__class__': 'datetime',
'year': o.year,
'month': o.month,
'day': o.day,
'hour': o.hour,
'minute': o.minute,
'second': o.second,
'microsecond': o.microsecond,
})
JSONEncoder.register(datetime.date,
lambda o: {
'__class__': 'date',
'year': o.year,
'month': o.month,
'day': o.day,
})
JSONEncoder.register(datetime.time,
lambda o: {
'__class__': 'time',
'hour': o.hour,
'minute': o.minute,
'second': o.second,
'microsecond': o.microsecond,
})
JSONEncoder.register(datetime.timedelta,
lambda o: {
'__class__': 'timedelta',
'seconds': o.total_seconds(),
})
def _bytes_encoder(o):
return {
'__class__': 'bytes',
'base64': base64.encodebytes(o).decode('utf-8'),
}
JSONEncoder.register(bytes, _bytes_encoder)
JSONEncoder.register(bytearray, _bytes_encoder)
JSONEncoder.register(Decimal,
lambda o: {
'__class__': 'Decimal',
'decimal': str(o),
})
class JSONRequest(Request):
parsed_content_type = 'json'
@cached_property
def parsed_data(self):
if self.parsed_content_type in self.environ.get('CONTENT_TYPE', ''):
try:
return json.loads(
self.decoded_data.decode(
getattr(self, 'charset', 'utf-8'),
getattr(self, 'encoding_errors', 'replace')),
object_hook=JSONDecoder())
except Exception:
raise BadRequest('Unable to read JSON request')
else:
raise BadRequest('Not a JSON request')
@cached_property
def rpc_method(self):
try:
return self.parsed_data['method']
except Exception:
pass
@cached_property
def rpc_params(self):
try:
return self.parsed_data['params']
except Exception:
pass
class JSONProtocol:
content_type = 'json'
@classmethod
def request(cls, environ):
return JSONRequest(environ)
@classmethod
def response(cls, data, request):
try:
parsed_data = request.parsed_data
except BadRequest:
parsed_data = {}
if (isinstance(request, JSONRequest)
and set(parsed_data.keys()) == {'id', 'method', 'params'}):
response = {'id': parsed_data.get('id', 0)}
if isinstance(data, TrytonException):
response['error'] = data.args
elif isinstance(data, Exception):
# report exception back to server
response['error'] = (str(data), data.__format_traceback__)
else:
response['result'] = data
else:
if isinstance(data, UserWarning):
return Conflict(data)
elif isinstance(data, LoginException):
return Forbidden(data)
elif isinstance(data, ConcurrencyException):
return Locked(data)
elif isinstance(data, RateLimitException):
return TooManyRequests(data)
elif isinstance(data, MissingDependenciesException):
return InternalServerError(data)
elif isinstance(data, TrytonException):
return BadRequest(data)
elif isinstance(data, Exception):
return InternalServerError(data)
response = data
return Response(json.dumps(
response, cls=JSONEncoder, separators=(',', ':')),
content_type='application/json')

321
protocols/wrappers.py Executable file
View File

@@ -0,0 +1,321 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import base64
import gzip
import logging
import time
from functools import wraps
from io import BytesIO
try:
from http import HTTPStatus
except ImportError:
from http import client as HTTPStatus
from werkzeug import exceptions
from werkzeug.datastructures import Authorization
from werkzeug.exceptions import abort
from werkzeug.utils import redirect, send_file
from werkzeug.wrappers import Request as _Request
from werkzeug.wrappers import Response
from trytond import backend, security
from trytond.config import config
from trytond.exceptions import RateLimitException, UserError, UserWarning
from trytond.pool import Pool
from trytond.tools import cached_property
from trytond.transaction import Transaction, TransactionError, check_access
__all__ = [
'HTTPStatus',
'Request',
'Response',
'abort',
'allow_null_origin',
'exceptions',
'redirect',
'send_file',
'set_max_request_size',
'user_application',
'with_pool',
'with_transaction',
]
logger = logging.getLogger(__name__)
class Request(_Request):
view_args = None
def __repr__(self):
args = []
try:
if self.url is None or isinstance(self.url, str):
url = self.url
else:
url = self.url.decode(getattr(self, 'url_charset', 'utf-8'))
auth = self.authorization
if auth:
args.append("%s@%s" % (
auth.get('userid', auth.username), self.remote_addr))
else:
args.append(self.remote_addr)
args.append("'%s'" % url)
args.append("[%s]" % self.method)
if self.view_args:
args.append("%s" % (self.rpc_method or ''))
except Exception:
args.append("(invalid WSGI environ)")
return "<%s %s>" % (
self.__class__.__name__, " ".join(filter(None, args)))
@property
def decoded_data(self):
if self.content_encoding == 'gzip':
if self.user_id:
zipfile = gzip.GzipFile(fileobj=BytesIO(self.data), mode='rb')
return zipfile.read()
else:
abort(HTTPStatus.UNSUPPORTED_MEDIA_TYPE)
else:
return self.data
@property
def parsed_data(self):
return self.data
@property
def rpc_method(self):
return
@property
def rpc_params(self):
return
@cached_property
def authorization(self):
authorization = super(Request, self).authorization
if authorization is None:
header = self.headers.get('Authorization')
return parse_authorization_header(header)
elif authorization.type == 'session':
# Werkzeug may parse the session as parameters
# if the base64 uses the padding sign '='
if authorization.token is None:
header = self.headers.get('Authorization')
return parse_authorization_header(header)
else:
return parse_session(authorization.token)
return authorization
@cached_property
def user_id(self):
assert self.view_args is not None
database_name = self.view_args.get('database_name')
if not database_name:
return None
auth = self.authorization
if not auth:
return None
context = {'_request': self.context}
if auth.type == 'session':
user_id = security.check(
database_name, auth.get('userid'), auth.get('session'),
context=context)
elif auth.username:
parameters = getattr(auth, 'parameters', auth)
try:
user_id = security.login(
database_name, auth.username, parameters, cache=False,
context=context)
except RateLimitException:
abort(HTTPStatus.TOO_MANY_REQUESTS)
else:
user_id = None
return user_id
@cached_property
def context(self):
return {
'remote_addr': self.remote_addr,
'http_host': self.environ.get('HTTP_HOST'),
'scheme': self.scheme,
'is_secure': self.is_secure,
}
def parse_authorization_header(value):
if not value:
return
if isinstance(value, bytes):
value = value.decode('latin1')
try:
auth_type, auth_info = value.split(None, 1)
auth_type = auth_type.lower()
except ValueError:
return
if auth_type == 'session':
return parse_session(auth_info)
else:
authorization = Authorization(auth_type)
authorization.token = auth_info
return authorization
def parse_session(token):
try:
username, userid, session = (
base64.b64decode(token).decode().split(':', 3))
userid = int(userid)
except Exception:
return
return Authorization('session', {
'username': username,
'userid': userid,
'session': session,
})
def set_max_request_size(size):
def decorator(func):
func.max_request_size = size
return func
return decorator
def allow_null_origin(func):
func.allow_null_origin = True
return func
def with_pool(func):
@wraps(func)
def wrapper(request, database_name, *args, **kwargs):
database_list = Pool.database_list()
pool = Pool(database_name)
if database_name not in database_list:
with Transaction().start(database_name, 0, readonly=True):
pool.init()
log_message = '%s in %i ms'
def duration():
return (time.monotonic() - started) * 1000
started = time.monotonic()
try:
result = func(request, pool, *args, **kwargs)
except exceptions.HTTPException:
logger.info(
log_message, request, duration(),
exc_info=logger.isEnabledFor(logging.DEBUG))
raise
except (UserError, UserWarning) as e:
logger.info(
log_message, request, duration(),
exc_info=logger.isEnabledFor(logging.DEBUG))
if request.rpc_method:
raise
else:
abort(HTTPStatus.BAD_REQUEST, e)
except Exception as e:
logger.exception(log_message, request, duration())
if request.rpc_method:
raise
else:
abort(HTTPStatus.INTERNAL_SERVER_ERROR, e)
logger.info(log_message, request, duration())
return result
return wrapper
def with_transaction(readonly=None, user=0, context=None):
from trytond.worker import run_task
def decorator(func):
@wraps(func)
def wrapper(request, pool, *args, **kwargs):
readonly_ = readonly # can not modify non local
if readonly_ is None:
if request.method in {'POST', 'PUT', 'DELETE', 'PATCH'}:
readonly_ = False
else:
readonly_ = True
if context is None:
context_ = {}
else:
context_ = context.copy()
context_['_request'] = request.context
if user == 'request':
user_ = request.user_id
else:
user_ = user
retry = config.getint('database', 'retry')
count = 0
transaction_extras = {}
while True:
if count:
time.sleep(0.02 * (retry - count))
with Transaction().start(
pool.database_name, user_, readonly=readonly_,
context=context_, **transaction_extras) as transaction:
try:
result = func(request, pool, *args, **kwargs)
except TransactionError as e:
transaction.rollback()
transaction.tasks.clear()
e.fix(transaction_extras)
continue
except backend.DatabaseOperationalError:
if count < retry and not readonly_:
transaction.rollback()
transaction.tasks.clear()
count += 1
logger.debug("Retry: %i", count)
continue
raise
# Need to commit to unlock SQLite database
transaction.commit()
while transaction.tasks:
task_id = transaction.tasks.pop()
run_task(pool, task_id)
return result
return wrapper
return decorator
def user_application(name, json=True):
from .jsonrpc import JSONEncoder
from .jsonrpc import json as json_
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
pool = Pool()
UserApplication = pool.get('res.user.application')
authorization = request.authorization
if authorization is None:
header = request.headers.get('Authorization')
authorization = parse_authorization_header(header)
if authorization is None:
abort(HTTPStatus.UNAUTHORIZED)
if authorization.type != 'bearer':
abort(HTTPStatus.FORBIDDEN)
token = getattr(authorization, 'token', '')
application = UserApplication.check(token, name)
if not application:
abort(HTTPStatus.FORBIDDEN)
transaction = Transaction()
# TODO language
with transaction.set_user(application.user.id), \
check_access():
response = func(request, *args, **kwargs)
if not isinstance(response, Response) and json:
response = Response(json_.dumps(response, cls=JSONEncoder),
content_type='application/json')
return response
return wrapper
return decorator

187
protocols/xmlrpc.py Executable file
View File

@@ -0,0 +1,187 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
import logging
import xmlrpc.client as client
# convert decimal to float before marshalling:
from decimal import Decimal
import defusedxml.xmlrpc
from werkzeug.exceptions import (
BadRequest, Conflict, Forbidden, InternalServerError, Locked,
TooManyRequests)
from werkzeug.wrappers import Response
from trytond.exceptions import (
ConcurrencyException, LoginException, MissingDependenciesException,
RateLimitException, TrytonException, UserWarning)
from trytond.model.fields.dict import ImmutableDict
from trytond.protocols.wrappers import Request
from trytond.tools import cached_property
logger = logging.getLogger(__name__)
defusedxml.xmlrpc.monkey_patch()
def dump_decimal(self, value, write):
write('<value><bigdecimal>')
write(str(Decimal(value)))
write('</bigdecimal></value>')
def dump_date(self, value, write):
value = {'__class__': 'date',
'year': value.year,
'month': value.month,
'day': value.day,
}
self.dump_struct(value, write)
def dump_time(self, value, write):
value = {'__class__': 'time',
'hour': value.hour,
'minute': value.minute,
'second': value.second,
'microsecond': value.microsecond,
}
self.dump_struct(value, write)
def dump_timedelta(self, value, write):
value = {'__class__': 'timedelta',
'seconds': value.total_seconds(),
}
self.dump_struct(value, write)
def dump_long(self, value, write):
try:
self.dump_long(value, write)
except OverflowError:
write('<value><biginteger>')
write(str(int(value)))
write('</biginteger></value>\n')
client.Marshaller.dispatch[Decimal] = dump_decimal
client.Marshaller.dispatch[datetime.date] = dump_date
client.Marshaller.dispatch[datetime.time] = dump_time
client.Marshaller.dispatch[datetime.timedelta] = dump_timedelta
client.Marshaller.dispatch[int] = dump_long
def dump_struct(self, value, write, escape=client.escape):
converted_value = {}
for k, v in value.items():
if isinstance(k, int):
k = str(k)
elif isinstance(k, float):
k = repr(k)
converted_value[k] = v
return self.dump_struct(converted_value, write, escape=escape)
client.Marshaller.dispatch[dict] = dump_struct
client.Marshaller.dispatch[ImmutableDict] = dump_struct
class XMLRPCDecoder(object):
decoders = {}
@classmethod
def register(cls, klass, decoder):
assert klass not in cls.decoders
cls.decoders[klass] = decoder
def __call__(self, dct):
if dct.get('__class__') in self.decoders:
return self.decoders[dct['__class__']](dct)
return dct
XMLRPCDecoder.register('date',
lambda dct: datetime.date(dct['year'], dct['month'], dct['day']))
XMLRPCDecoder.register('time',
lambda dct: datetime.time(dct['hour'], dct['minute'], dct['second'],
dct['microsecond']))
XMLRPCDecoder.register('timedelta',
lambda dct: datetime.timedelta(seconds=dct['seconds']))
XMLRPCDecoder.register('Decimal', lambda dct: Decimal(dct['decimal']))
def end_struct(self, data):
mark = self._marks.pop()
# map structs to Python dictionaries
dct = {}
items = self._stack[mark:]
for i in range(0, len(items), 2):
dct[items[i]] = items[i + 1]
dct = XMLRPCDecoder()(dct)
self._stack[mark:] = [dct]
self._value = 0
client.Unmarshaller.dispatch['struct'] = end_struct
class XMLRequest(Request):
parsed_content_type = 'xml'
@cached_property
def parsed_data(self):
if self.parsed_content_type in self.environ.get('CONTENT_TYPE', ''):
try:
# TODO replace by own loads
return client.loads(self.decoded_data, use_builtin_types=True)
except Exception:
raise BadRequest('Unable to read XMl request')
else:
raise BadRequest('Not an XML request')
@property
def rpc_method(self):
return self.parsed_data[1]
@property
def rpc_params(self):
return self.parsed_data[0]
class XMLProtocol:
content_type = 'xml'
@classmethod
def request(cls, environ):
return XMLRequest(environ)
@classmethod
def response(cls, data, request):
if isinstance(request, XMLRequest):
if isinstance(data, TrytonException):
data = client.Fault(data.code, str(data))
elif isinstance(data, Exception):
data = client.Fault(255, str(data))
else:
data = (data,)
return Response(client.dumps(
data, methodresponse=True, allow_none=True),
content_type='text/xml')
else:
if isinstance(data, UserWarning):
return Conflict(data)
elif isinstance(data, LoginException):
return Forbidden(data)
elif isinstance(data, ConcurrencyException):
return Locked(data)
elif isinstance(data, RateLimitException):
return TooManyRequests(data)
elif isinstance(data, MissingDependenciesException):
return InternalServerError(data)
elif isinstance(data, TrytonException):
return BadRequest(data)
elif isinstance(data, Exception):
return InternalServerError(data)
return Response(data)