Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions fastapi/models/fastapi_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class FastapiEndpoint(models.Model):
default=True,
)
route_group = fields.Char(help="Use this to classify routes together", size=32)
expose_doc_urls = fields.Boolean("Expose FastAPI docs", default=True)

@api.depends("root_path")
def _compute_root_path(self):
Expand Down Expand Up @@ -152,7 +153,7 @@ def _handle_route_updates(self, vals):
@api.model
def _fastapi_app_fields(self) -> list[str]:
"""The list of fields requiring to refresh the fastapi app if modified"""
return []
return ["expose_doc_urls"]

def _make_routing_rule(self, options=None):
"""Generator of rule"""
Expand Down Expand Up @@ -323,12 +324,15 @@ def _get_app_dependencies_overrides(self) -> dict[Callable, Callable]:

def _prepare_fastapi_app_params(self) -> dict[str, Any]:
"""Return the params to pass to the Fast API app constructor"""
return {
to_return = {
"title": self.name,
"description": self.description,
"middleware": self._get_fastapi_app_middlewares(),
"dependencies": self._get_fastapi_app_dependencies(),
}
if not self.expose_doc_urls:
to_return |= {"docs_url": None, "redoc_url": None, "openapi_url": None}
return to_return

def _get_fastapi_routers(self) -> list[APIRouter]:
"""Return the api routers to use for the instance.
Expand Down
6 changes: 6 additions & 0 deletions fastapi/readme/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ Now, you can start your Odoo server, install your addon and create a new
endpoint instance for your app. Once it's done click on the docs url to
access the interactive documentation of your app.

**Note**: FastAPI automatically exposes three services to easily
interact with the APIs and their structure (Swagger, Redoc, OpenAPI).
When exposing your Odoo instance to the open internet you might want to
disable these services. To do so toggle the "Expose FastAPI docs"
checkbox in the FastAPI Endpoint form view.

Before trying to test your app, you need to define on the endpoint
instance the user that will be used to run the app. You can do it by
setting the **'user_id'** field. This information is the most important
Expand Down
42 changes: 42 additions & 0 deletions fastapi/tests/test_fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import unittest
from contextlib import contextmanager
from pathlib import Path

from odoo.tests.common import HttpCase
from odoo.tests.test_cursor import TestCursor
Expand Down Expand Up @@ -46,6 +47,17 @@ def _assert_expected_lang(self, accept_language, expected_lang):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, expected_lang)

def endpoint_path(self, endpoint, path):
"""Properly contatenate and endpoint's root_path and another path"""
if path[0] == "/":
# concatenating two paths where the second one starts with a '/' makes the
# resulting path exactly the same as this second path
# eg. Path("/root") / Path("/endpoint") -> PosixPath("/endpoint")
# We want to avoid that, we should get a result like
# -> PosixPath("/root/endpoint")
path = path[1:]
return str(Path(endpoint.root_path) / path)

def test_call(self):
route = "/fastapi_demo/demo/"
response = self.url_open(route)
Expand Down Expand Up @@ -80,6 +92,36 @@ def test_retrying_post(self):
self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.json(), {"retries": nbr_retries, "file": "test"})

def test_expose_docs(self):
self.assertNotIn(
"docs_url", self.fastapi_demo_app._prepare_fastapi_app_params().keys()
)
response = self.url_open(
self.endpoint_path(self.fastapi_demo_app, "/docs"), timeout=20
)
self.assertEqual(response.status_code, 200)

unexposed_endpoint = self.env["fastapi.endpoint"].create(
{
"name": "Test Endpoint - non exposed",
"root_path": "/test-endpoint/",
"app": "demo",
"demo_auth_method": "api_key",
"expose_doc_urls": False,
}
)
self.assertIn(
"docs_url", unexposed_endpoint._prepare_fastapi_app_params().keys()
)
self.assertEqual(
unexposed_endpoint._prepare_fastapi_app_params().get("docs_url"), None
)
unexposed_endpoint._handle_registry_sync()
response = self.url_open(
self.endpoint_path(unexposed_endpoint, "/docs"), timeout=20
)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

@mute_logger("odoo.http")
def assert_exception_processed(
self,
Expand Down
1 change: 1 addition & 0 deletions fastapi/views/fastapi_endpoint.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<field name="save_http_session" />
</group>
<group name="resoures">
<field name="expose_doc_urls" />
<field name="docs_url" widget="url" />
<field name="redoc_url" widget="url" />
<field name="openapi_url" widget="url" />
Expand Down
Loading