diff --git a/fastapi/demo/fastapi_endpoint_demo.xml b/fastapi/demo/fastapi_endpoint_demo.xml index a1c34e34b..7016b807c 100644 --- a/fastapi/demo/fastapi_endpoint_demo.xml +++ b/fastapi/demo/fastapi_endpoint_demo.xml @@ -44,4 +44,15 @@ methods. See documentation to learn more about how to create a new app. <field name="demo_auth_method">http_basic</field> <field name="user_id" ref="my_demo_app_user" /> </record> + + <record id="fastapi_endpoint_multislash_demo" model="fastapi.endpoint"> + <field name="name">Fastapi Multi-Slash Demo Endpoint</field> + <field name="description"> + Like the other demo endpoint but with multi-slash + </field> + <field name="app">demo</field> + <field name="root_path">/fastapi/demo</field> + <field name="demo_auth_method">http_basic</field> + <field name="user_id" ref="my_demo_app_user" /> + </record> </odoo> diff --git a/fastapi/fastapi_dispatcher.py b/fastapi/fastapi_dispatcher.py index c41beb2d2..70e1c8568 100644 --- a/fastapi/fastapi_dispatcher.py +++ b/fastapi/fastapi_dispatcher.py @@ -27,7 +27,7 @@ def dispatch(self, endpoint, args): # don't parse the httprequest let starlette parse the stream self.request.params = {} # dict(self.request.get_http_params(), **args) environ = self._get_environ() - root_path = "/" + environ["PATH_INFO"].split("/")[1] + root_path = environ["PATH_INFO"] # TODO store the env into contextvar to be used by the odoo_env # depends method with fastapi_app_pool.get_app(env=request.env, root_path=root_path) as app: diff --git a/fastapi/models/fastapi_endpoint.py b/fastapi/models/fastapi_endpoint.py index 25e3dc468..358531cc1 100644 --- a/fastapi/models/fastapi_endpoint.py +++ b/fastapi/models/fastapi_endpoint.py @@ -210,9 +210,24 @@ def _reset_app_cache_marker(self): to all the running instances. """ + @api.model + def get_endpoint(self, root_path): + # try to match the request url with the most similar endpoint + endpoints_by_length = self.search([]).sorted( + lambda fe: len(fe.root_path), reverse=True + ) + endpoint = False + while endpoints_by_length: + candidate_endpoint = endpoints_by_length[0] + if root_path.startswith(candidate_endpoint.root_path): + endpoint = candidate_endpoint + break + endpoints_by_length -= candidate_endpoint + return endpoint + @api.model def get_app(self, root_path): - record = self.search([("root_path", "=", root_path)]) + record = self.get_endpoint(root_path) if not record: return None app = FastAPI() @@ -238,7 +253,7 @@ def _clear_fastapi_exception_handlers(self, app: FastAPI) -> None: @api.model @tools.ormcache("root_path") def get_uid(self, root_path): - record = self.search([("root_path", "=", root_path)]) + record = self.get_endpoint(root_path) if not record: return None return record.user_id.id diff --git a/fastapi/tests/__init__.py b/fastapi/tests/__init__.py index ea40c354c..e01cb8acf 100644 --- a/fastapi/tests/__init__.py +++ b/fastapi/tests/__init__.py @@ -1,2 +1,3 @@ from . import test_fastapi from . import test_fastapi_demo +from . import test_fastapi_multislash_demo diff --git a/fastapi/tests/test_fastapi_multislash_demo.py b/fastapi/tests/test_fastapi_multislash_demo.py new file mode 100644 index 000000000..449d5b54b --- /dev/null +++ b/fastapi/tests/test_fastapi_multislash_demo.py @@ -0,0 +1,32 @@ +# License LGPL-3.0 or later (http://www.gnu.org/licenses/LGPL). + + +from requests import Response + +from fastapi import status + +from ..routers import demo_router +from .common import FastAPITransactionCase + + +class FastAPIMultiSlashDemoCase(FastAPITransactionCase): + """ + This test verifies that multi-slash endpoint are properly reached + """ + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.default_fastapi_router = demo_router + cls.default_fastapi_running_user = cls.env.ref( + "fastapi.fastapi_endpoint_multislash_demo" + ) + cls.default_fastapi_authenticated_partner = cls.env["res.partner"].create( + {"name": "FastAPI Demo"} + ) + + def test_hello_world(self) -> None: + with self._create_test_client() as test_client: + response: Response = test_client.get("/demo/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(response.json(), {"Hello": "World"})