Skip to content

Commit

Permalink
Merge PR #460 into 17.0
Browse files Browse the repository at this point in the history
Signed-off-by lmignon
  • Loading branch information
OCA-git-bot committed Oct 3, 2024
2 parents 3fa322b + c056a16 commit 116f44a
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 8 deletions.
4 changes: 2 additions & 2 deletions fastapi/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
"demo": ["demo/fastapi_endpoint_demo.xml"],
"external_dependencies": {
"python": [
"fastapi",
"fastapi>=0.110.0",
"python-multipart",
"ujson",
"a2wsgi",
"a2wsgi>=1.10.6",
"parse-accept-language",
]
},
Expand Down
2 changes: 1 addition & 1 deletion fastapi/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def optionally_authenticated_partner(


def paging(
page: Annotated[int, Query(gte=1)] = 1, page_size: Annotated[int, Query(gte=1)] = 80
page: Annotated[int, Query(ge=1)] = 1, page_size: Annotated[int, Query(ge=1)] = 80
) -> Paging:
"""Return a Paging object from the page and page_size parameters"""
return Paging(limit=page_size, offset=(page - 1) * page_size)
Expand Down
16 changes: 15 additions & 1 deletion fastapi/fastapi_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,21 @@ def _get_environ(self):
# date odoo version. (EAFP: Easier to Ask for Forgiveness than Permission)
httprequest = self.request.httprequest
environ = httprequest.environ
environ["wsgi.input"] = httprequest._get_stream_for_parsing()
stream = httprequest._get_stream_for_parsing()
# Check if the stream supports seeking
if hasattr(stream, "seekable") and stream.seekable():
# Reset the stream to the beginning to ensure it can be consumed
# again by the application in case of a retry mechanism
stream.seek(0)
else:
# If the stream does not support seeking, we need wrap it
# in a BytesIO object. This way we can seek back to the beginning
# of the stream to read the data again if needed.
if not hasattr(httprequest, "_cached_stream"):
httprequest._cached_stream = BytesIO(stream.read())
stream = httprequest._cached_stream
stream.seek(0)
environ["wsgi.input"] = stream
return environ

@contextmanager
Expand Down
11 changes: 11 additions & 0 deletions fastapi/models/fastapi_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ class FastapiEndpoint(models.Model):
readonly=False,
domain="[('user_ids', 'in', user_id)]",
)
save_http_session = fields.Boolean(
string="Save HTTP Session",
help="Whether session should be saved into the session store. This is "
"required if for example you use the Odoo's authentication mechanism. "
"Oherwise chance are high that you don't need it and could turn off "
"this behaviour. Additionaly turning off this option will prevent useless "
"IO operation when storing and reading the session on the disk and prevent "
"unexpecteed disk space consumption.",
default=True,
)

@api.depends("root_path")
def _compute_root_path(self):
Expand Down Expand Up @@ -178,6 +188,7 @@ def _get_routing_info(self):
f"{self.root_path}/",
f"{self.root_path}/<path:application_path>",
],
"save_session": self.save_http_session,
# csrf ?????
}

Expand Down
6 changes: 6 additions & 0 deletions fastapi/readme/newsfragments/440.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Fix issue with the retry of a POST request with a body content.

Prior to this fix the retry of a POST request with a body content would
stuck in a loop and never complete. This was due to the fact that the
request input stream was not reset after a failed attempt to process the
request.
1 change: 1 addition & 0 deletions fastapi/readme/newsfragments/442.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* A new parameter is now available on the endpoint model to let you disable the creation and the store of session files used by Odoo for calls to your application endpoint. This is usefull to prevent disk space consumption and IO operations if your application doesn't need to use this sessions files which are mainly used by Odoo by to store the session info of logged in users.
27 changes: 25 additions & 2 deletions fastapi/routers/demo_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

from odoo.addons.base.models.res_partner import Partner

from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi import APIRouter, Depends, File, HTTPException, Query, status
from fastapi.responses import JSONResponse

from ..dependencies import authenticated_partner, fastapi_endpoint, odoo_env
from ..models import FastapiEndpoint
Expand Down Expand Up @@ -95,7 +96,7 @@ async def endpoint_app_info(

@router.get("/demo/retrying")
async def retrying(
nbr_retries: Annotated[int, Query(gt=1, lt=MAX_TRIES_ON_CONCURRENCY_FAILURE)]
nbr_retries: Annotated[int, Query(gt=1, lt=MAX_TRIES_ON_CONCURRENCY_FAILURE)],
) -> int:
"""This method is used in the test suite to check that the retrying
functionality in case of concurrency error on the database is working
Expand All @@ -114,6 +115,28 @@ async def retrying(
return tryno


@router.post("/demo/retrying")
async def retrying_post(
nbr_retries: Annotated[int, Query(gt=1, lt=MAX_TRIES_ON_CONCURRENCY_FAILURE)],
file: Annotated[bytes, File()],
) -> JSONResponse:
"""This method is used in the test suite to check that the retrying
functionality in case of concurrency error on the database is working
correctly for retryable exceptions.
The output will be the number of retries that have been done.
This method is mainly used to test the retrying functionality
"""
global _CPT
if _CPT < nbr_retries:
_CPT += 1
raise FakeConcurrentUpdateError("fake error")
tryno = _CPT
_CPT = 0
return JSONResponse(content={"retries": tryno, "file": file.decode("utf-8")})


class FakeConcurrentUpdateError(OperationalError):
@property
def pgcode(self):
Expand Down
12 changes: 12 additions & 0 deletions fastapi/tests/test_fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ def test_retrying(self):
self.assertEqual(response.status_code, 200)
self.assertEqual(int(response.content), nbr_retries)

def test_retrying_post(self):
"""Test that the retrying mechanism is working as expected with the
FastAPI endpoints in case of POST request with a file.
"""
nbr_retries = 3
route = f"/fastapi_demo/demo/retrying?nbr_retries={nbr_retries}"
response = self.url_open(
route, timeout=20, files={"file": ("test.txt", b"test")}
)
self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.json(), {"retries": nbr_retries, "file": "test"})

@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 @@ -46,6 +46,7 @@
<field name="user_id" />
<field name="company_id" />
<field name="description" />
<field name="save_http_session" />
</group>
<group name="resoures">
<field name="docs_url" widget="url" />
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# generated from manifests external_dependencies
a2wsgi
fastapi
a2wsgi>=1.10.6
fastapi>=0.110.0
parse-accept-language
python-multipart
ujson

0 comments on commit 116f44a

Please sign in to comment.