mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-18 16:32:01 +02:00
Initial commit: OCA Technical packages (595 packages)
This commit is contained in:
commit
2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions
|
|
@ -0,0 +1 @@
|
|||
from . import service
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
|
||||
# @author Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
# @author Simone Orsi <simahawk@gmail.com>
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from psycopg2.errors import OperationalError
|
||||
from werkzeug.urls import url_encode, url_join
|
||||
|
||||
from odoo import exceptions, registry
|
||||
from odoo.http import Response, request
|
||||
from odoo.service.model import PG_CONCURRENCY_ERRORS_TO_RETRY
|
||||
|
||||
from odoo.addons.base_rest.http import JSONEncoder
|
||||
from odoo.addons.component.core import AbstractComponent
|
||||
|
||||
from ..exceptions import (
|
||||
RESTServiceDispatchException,
|
||||
RESTServiceMissingErrorException,
|
||||
RESTServiceUserErrorException,
|
||||
RESTServiceValidationErrorException,
|
||||
)
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def json_dump(data):
|
||||
"""Encode data to JSON as we like."""
|
||||
return json.dumps(data, cls=JSONEncoder, indent=4, sort_keys=True, default=str)
|
||||
|
||||
|
||||
class BaseRESTService(AbstractComponent):
|
||||
_inherit = "base.rest.service"
|
||||
# can be overridden to enable logging of requests to DB
|
||||
_log_calls_in_db = False
|
||||
|
||||
def dispatch(self, method_name, *args, params=None):
|
||||
if not self._db_logging_active(method_name):
|
||||
return super().dispatch(method_name, *args, params=params)
|
||||
return self._dispatch_with_db_logging(method_name, *args, params=params)
|
||||
|
||||
def _dispatch_with_db_logging(self, method_name, *args, params=None):
|
||||
try:
|
||||
with self.env.cr.savepoint():
|
||||
result = super().dispatch(method_name, *args, params=params)
|
||||
except exceptions.MissingError as orig_exception:
|
||||
self._dispatch_exception(
|
||||
method_name,
|
||||
RESTServiceMissingErrorException,
|
||||
orig_exception,
|
||||
*args,
|
||||
params=params,
|
||||
)
|
||||
except exceptions.ValidationError as orig_exception:
|
||||
self._dispatch_exception(
|
||||
method_name,
|
||||
RESTServiceValidationErrorException,
|
||||
orig_exception,
|
||||
*args,
|
||||
params=params,
|
||||
)
|
||||
except exceptions.UserError as orig_exception:
|
||||
self._dispatch_exception(
|
||||
method_name,
|
||||
RESTServiceUserErrorException,
|
||||
orig_exception,
|
||||
*args,
|
||||
params=params,
|
||||
)
|
||||
except Exception as orig_exception:
|
||||
self._dispatch_exception(
|
||||
method_name,
|
||||
RESTServiceDispatchException,
|
||||
orig_exception,
|
||||
*args,
|
||||
params=params,
|
||||
)
|
||||
self._log_dispatch_success(method_name, result, *args, params)
|
||||
return result
|
||||
|
||||
def _log_dispatch_success(self, method_name, result, *args, params=None):
|
||||
try:
|
||||
with self.env.cr.savepoint():
|
||||
log_entry = self._log_call_in_db(
|
||||
self.env, request, method_name, *args, params, result=result
|
||||
)
|
||||
if log_entry and not isinstance(result, Response):
|
||||
log_entry_url = self._get_log_entry_url(log_entry)
|
||||
result["log_entry_url"] = log_entry_url
|
||||
except Exception as e:
|
||||
_logger.exception("Rest Log Error Creation: %s", e)
|
||||
|
||||
def _dispatch_exception(
|
||||
self, method_name, exception_klass, orig_exception, *args, params=None
|
||||
):
|
||||
exc_msg, log_entry_url = None, None # in case it fails below
|
||||
try:
|
||||
exc_msg = self._get_exception_message(orig_exception)
|
||||
tb = traceback.format_exc()
|
||||
with registry(self.env.cr.dbname).cursor() as cr:
|
||||
log_entry = self._log_call_in_db(
|
||||
self.env(cr=cr),
|
||||
request,
|
||||
method_name,
|
||||
*args,
|
||||
params=params,
|
||||
traceback=tb,
|
||||
orig_exception=orig_exception,
|
||||
)
|
||||
log_entry_url = self._get_log_entry_url(log_entry)
|
||||
except Exception as e:
|
||||
_logger.exception("Rest Log Error Creation: %s", e)
|
||||
# let the OperationalError bubble up to the retrying mechanism
|
||||
# We can't wrap the OperationalError because we want to let it
|
||||
# bubble up to the retrying mechanism, it will be handled by
|
||||
# the default handler at the end of the chain.
|
||||
if (
|
||||
isinstance(orig_exception, OperationalError)
|
||||
and orig_exception.pgcode in PG_CONCURRENCY_ERRORS_TO_RETRY
|
||||
):
|
||||
raise orig_exception
|
||||
raise exception_klass(exc_msg, log_entry_url) from orig_exception
|
||||
|
||||
def _get_exception_message(self, exception):
|
||||
return exception.args and exception.args[0] or str(exception)
|
||||
|
||||
def _get_log_entry_url(self, entry):
|
||||
base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
|
||||
url_params = {
|
||||
"action": self.env.ref("rest_log.action_rest_log").id,
|
||||
"view_type": "form",
|
||||
"model": entry._name,
|
||||
"id": entry.id,
|
||||
}
|
||||
url = "/web?#%s" % url_encode(url_params)
|
||||
return url_join(base_url, url)
|
||||
|
||||
@property
|
||||
def _log_call_header_strip(self):
|
||||
return ("Cookie", "Api-Key")
|
||||
|
||||
def _log_call_in_db_values(self, _request, *args, params=None, **kw):
|
||||
httprequest = _request.httprequest
|
||||
headers = self._log_call_sanitize_headers(dict(httprequest.headers or []))
|
||||
params = dict(params or {})
|
||||
if args:
|
||||
params.update(args=args)
|
||||
params = self._log_call_sanitize_params(params)
|
||||
error, exception_name, exception_message = self._log_call_prepare_error(**kw)
|
||||
result, state = self._log_call_prepare_result(kw.get("result"))
|
||||
collection = self.work.collection
|
||||
return {
|
||||
"collection": collection._name,
|
||||
"collection_id": collection.id,
|
||||
"request_url": httprequest.url,
|
||||
"request_method": httprequest.method,
|
||||
"params": params,
|
||||
"headers": headers,
|
||||
"result": result,
|
||||
"error": error,
|
||||
"exception_name": exception_name,
|
||||
"exception_message": exception_message,
|
||||
"state": state,
|
||||
}
|
||||
|
||||
def _log_call_prepare_result(self, result):
|
||||
# NB: ``result`` might be an object of class ``odoo.http.Response``,
|
||||
# for example when you try to download a file. In this case, we need to
|
||||
# handle it properly, without the assumption that ``result`` is a dict.
|
||||
if isinstance(result, Response):
|
||||
status_code = result.status_code
|
||||
result = {
|
||||
"status": status_code,
|
||||
"headers": self._log_call_sanitize_headers(dict(result.headers or [])),
|
||||
}
|
||||
state = "success" if status_code in range(200, 300) else "failed"
|
||||
else:
|
||||
state = "success" if result else "failed"
|
||||
return result, state
|
||||
|
||||
def _log_call_prepare_error(self, traceback=None, orig_exception=None, **kw):
|
||||
exception_name = None
|
||||
exception_message = None
|
||||
if orig_exception:
|
||||
exception_name = orig_exception.__class__.__name__
|
||||
if hasattr(orig_exception, "__module__"):
|
||||
exception_name = orig_exception.__module__ + "." + exception_name
|
||||
exception_message = self._get_exception_message(orig_exception)
|
||||
return traceback, exception_name, exception_message
|
||||
|
||||
_log_call_in_db_keys_to_serialize = ("params", "headers", "result")
|
||||
|
||||
def _log_call_in_db(self, env, _request, method_name, *args, params=None, **kw):
|
||||
values = self._log_call_in_db_values(_request, *args, params=params, **kw)
|
||||
for k in self._log_call_in_db_keys_to_serialize:
|
||||
values[k] = json_dump(values[k])
|
||||
enabled_states = self._get_matching_active_conf(method_name)
|
||||
if not values or enabled_states and values["state"] not in enabled_states:
|
||||
return
|
||||
return env["rest.log"].sudo().create(values)
|
||||
|
||||
def _log_call_sanitize_params(self, params: dict) -> dict:
|
||||
if "password" in params:
|
||||
params["password"] = "<redacted>"
|
||||
return params
|
||||
|
||||
def _log_call_sanitize_headers(self, headers: dict) -> dict:
|
||||
for header_key in self._log_call_header_strip:
|
||||
if header_key in headers:
|
||||
headers[header_key] = "<redacted>"
|
||||
return headers
|
||||
|
||||
def _db_logging_active(self, method_name):
|
||||
enabled = self._log_calls_in_db
|
||||
if not enabled:
|
||||
enabled = bool(self._get_matching_active_conf(method_name))
|
||||
return request and enabled and self.env["rest.log"].logging_active()
|
||||
|
||||
def _get_matching_active_conf(self, method_name):
|
||||
return self.env["rest.log"]._get_matching_active_conf(
|
||||
self._collection, self._usage, method_name
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue