|
| 1 | +# Copyright 2023 ACSONE SA/NV |
| 2 | +# Copyright 2024 Akretion (http://www.akretion.com). |
| 3 | +# @author Florian Mounier <florian.mounier@akretion.com> |
| 4 | +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
| 5 | + |
| 6 | +from functools import lru_cache |
| 7 | + |
| 8 | +import odoo |
| 9 | +import json |
| 10 | +import werkzeug.datastructures |
| 11 | + |
| 12 | +from odoo import http |
| 13 | +from odoo.addons.fastapi.fastapi_dispatcher import FastApiDispatcher |
| 14 | +from odoo.tools import date_utils |
| 15 | + |
| 16 | + |
| 17 | +class FastapiRootPaths: |
| 18 | + _root_paths_by_db = {} |
| 19 | + |
| 20 | + @classmethod |
| 21 | + def set_root_paths(cls, db, root_paths): |
| 22 | + cls._root_paths_by_db[db] = root_paths |
| 23 | + cls.is_fastapi_path.cache_clear() |
| 24 | + |
| 25 | + @classmethod |
| 26 | + @lru_cache(maxsize=1024) |
| 27 | + def is_fastapi_path(cls, db, path): |
| 28 | + return any( |
| 29 | + path.startswith(root_path) |
| 30 | + for root_path in cls._root_paths_by_db.get(db, []) |
| 31 | + ) |
| 32 | + |
| 33 | + |
| 34 | +class FastapiRequest(http.WebRequest): |
| 35 | + _request_type = "fastapi" |
| 36 | + |
| 37 | + def __init__(self, *args): |
| 38 | + super().__init__(*args) |
| 39 | + self.params = {} |
| 40 | + self._dispatcher = FastApiDispatcher(self) |
| 41 | + |
| 42 | + def make_response(self, data, headers=None, cookies=None, status=200): |
| 43 | + """Helper for non-HTML responses, or HTML responses with custom |
| 44 | + response headers or cookies. |
| 45 | +
|
| 46 | + While handlers can just return the HTML markup of a page they want to |
| 47 | + send as a string if non-HTML data is returned they need to create a |
| 48 | + complete response object, or the returned data will not be correctly |
| 49 | + interpreted by the clients. |
| 50 | +
|
| 51 | + :param basestring data: response body |
| 52 | + :param headers: HTTP headers to set on the response |
| 53 | + :type headers: ``[(name, value)]`` |
| 54 | + :param collections.abc.Mapping cookies: cookies to set on the client |
| 55 | + """ |
| 56 | + response = http.Response(data, status=status, headers=headers) |
| 57 | + if cookies: |
| 58 | + for k, v in cookies.items(): |
| 59 | + response.set_cookie(k, v) |
| 60 | + return response |
| 61 | + |
| 62 | + def make_json_response(self, data, headers=None, cookies=None, status=200): |
| 63 | + """Helper for JSON responses, it json-serializes ``data`` and |
| 64 | + sets the Content-Type header accordingly if none is provided. |
| 65 | +
|
| 66 | + :param data: the data that will be json-serialized into the response body |
| 67 | + :param int status: http status code |
| 68 | + :param List[(str, str)] headers: HTTP headers to set on the response |
| 69 | + :param collections.abc.Mapping cookies: cookies to set on the client |
| 70 | + :rtype: :class:`~odoo.http.Response` |
| 71 | + """ |
| 72 | + data = json.dumps(data, ensure_ascii=False, default=date_utils.json_default) |
| 73 | + |
| 74 | + headers = werkzeug.datastructures.Headers(headers) |
| 75 | + headers["Content-Length"] = len(data) |
| 76 | + if "Content-Type" not in headers: |
| 77 | + headers["Content-Type"] = "application/json; charset=utf-8" |
| 78 | + |
| 79 | + return self.make_response(data, headers.to_wsgi_list(), cookies, status) |
| 80 | + |
| 81 | + def dispatch(self): |
| 82 | + try: |
| 83 | + return self._dispatcher.dispatch(None, None) |
| 84 | + except Exception as e: |
| 85 | + return self._handle_exception(e) |
| 86 | + |
| 87 | + def _handle_exception(self, exception): |
| 88 | + return self._dispatcher.handle_error(exception) |
| 89 | + |
| 90 | + |
| 91 | +ori_get_request = http.root.__class__.get_request |
| 92 | + |
| 93 | + |
| 94 | +def get_request(self, httprequest): |
| 95 | + db = httprequest.session.db |
| 96 | + if db and odoo.service.db.exp_db_exist(db): |
| 97 | + # on the very first request processed by a worker, |
| 98 | + # registry is not loaded yet |
| 99 | + # so we enforce its loading here. |
| 100 | + odoo.registry(db) |
| 101 | + if FastapiRootPaths.is_fastapi_path(db, httprequest.path): |
| 102 | + return FastapiRequest(httprequest) |
| 103 | + return ori_get_request(self, httprequest) |
| 104 | + |
| 105 | + |
| 106 | +http.root.__class__.get_request = get_request |
0 commit comments