Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
77cb0fa
[ADD] pydantic: allows pydantic usage into Odoo and withon base_rest
lmignon Nov 6, 2021
cf2ac8b
[UPD] Update base_rest_pydantic.pot
oca-travis Jun 14, 2022
b3e36ff
[UPD] README.rst
OCA-git-bot Jun 14, 2022
e1b4584
base_rest_pydantic 14.0.4.2.0
OCA-git-bot Jun 14, 2022
d119ec6
Forward port from 14.0
lmignon Jun 14, 2022
a46c3c7
[FIX] base_rest, base_rest_pydantic: Fix message translation
lmignon Jun 14, 2022
060d118
[UPD] Update base_rest_pydantic.pot
Jun 14, 2022
b8f93f0
[UPD] README.rst
OCA-git-bot Jun 14, 2022
a4afaed
base_rest_pydantic 15.0.4.2.1
OCA-git-bot Jun 14, 2022
bac9830
[IMP] allow to use multipart/form-data format
Oct 6, 2021
58b4a91
base_rest_pydantic 14.0.4.3.0
OCA-git-bot Jun 15, 2022
1106017
base_rest_pydantic 15.0.4.3.0
OCA-git-bot Jun 15, 2022
9bf21c2
Initialize 16.0 branch
lmignon Sep 27, 2022
d632693
[FIX] base_rest_pydantic: forword service to PydanticModel.from_params
petrus-v Aug 19, 2022
61d894b
[MIG] base_rest, base_rest_auth_api_key, base_rest_datamodel, base_re…
StefanRijnhart Dec 7, 2022
95969b0
[MIG] base_rest, base_rest_auth_api_key, base_rest_datamodel, base_re…
StefanRijnhart Nov 28, 2022
9d2d4c7
[UPD] Update base_rest_pydantic.pot
Jan 26, 2023
abcde50
[UPD] README.rst
OCA-git-bot Jan 26, 2023
d8bf509
Migration to Pydantic v2
lmignon Jul 24, 2023
d67ec79
[UPD] Update base_rest_pydantic.pot
Jul 25, 2023
e00dedf
base_rest_pydantic 16.0.2.0.0
OCA-git-bot Jul 25, 2023
ef046fb
[UPD] README.rst
OCA-git-bot Sep 3, 2023
8bc6140
[FIX] base_rest_pydantic: Fix schema generation after last changes in…
lmignon Sep 19, 2023
e5b87ae
oca-github-bot post-merge updates
OCA-git-bot Sep 19, 2023
b614980
[MIG] mark all modules installable=False
sbidoul Nov 12, 2023
34648dc
[MIG] base_rest_pydantic: migrate to 18.0
pierre-halleux Mar 4, 2025
8e21d8f
[FIX] base_rest*: Translate validation exception
lmignon Jun 4, 2025
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
20 changes: 11 additions & 9 deletions base_rest/restapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from cerberus import Validator

from odoo import _, http
from odoo import http
from odoo.exceptions import UserError, ValidationError

from .tools import ROUTING_DECORATOR_ATTR, cerberus_to_json
Expand Down Expand Up @@ -218,13 +218,13 @@
validator = self.get_cerberus_validator(service, "input")
if validator.validate(params):
return validator.document
raise UserError(_("BadRequest %s") % validator.errors)
raise UserError(service.env._("BadRequest %s") % validator.errors)

def to_response(self, service, result):
validator = self.get_cerberus_validator(service, "output")
if validator.validate(result):
return validator.document
raise SystemError(_("Invalid Response %s") % validator.errors)
raise SystemError(service.env._("Invalid Response %s") % validator.errors)

def to_openapi_query_parameters(self, service, spec):
json_schema = self.to_json_schema(service, spec, "input")
Expand Down Expand Up @@ -275,7 +275,9 @@
return schema
if isinstance(schema, dict):
return Validator(schema, purge_unknown=True)
raise Exception(_("Unable to get cerberus schema from %s") % self._schema)
raise Exception(

Check warning on line 278 in base_rest/restapi.py

View check run for this annotation

Codecov / codecov/patch

base_rest/restapi.py#L278

Added line #L278 was not covered by tests
service.env._("Unable to get cerberus schema from %s") % self._schema
)

def to_json_schema(self, service, spec, direction):
schema = self.get_cerberus_validator(service, direction).schema
Expand Down Expand Up @@ -321,7 +323,7 @@
for idx, p in enumerate(data):
if not validator.validate(p):
raise ExceptionClass(
_(
service.env._(
"BadRequest item %(idx)s :%(errors)s",
idx=idx,
errors=validator.errors,
Expand All @@ -330,7 +332,7 @@
values.append(validator.document)
if self._min_items is not None and len(values) < self._min_items:
raise ExceptionClass(
_(
service.env._(
"BadRequest: Not enough items in the list (%(current)s "
"< %(expected)s)",
current=len(values),
Expand All @@ -339,7 +341,7 @@
)
if self._max_items is not None and len(values) > self._max_items:
raise ExceptionClass(
_(
service.env._(
"BadRequest: Too many items in the list (%(current)s "
"> %(expected)s)",
current=len(values),
Expand Down Expand Up @@ -367,7 +369,7 @@
:param parts: list of RestMethodParam
"""
if not isinstance(parts, dict):
raise ValidationError(_("You must provide a dict of RestMethodParam"))
raise RuntimeError("You must provide a dict of RestMethodParam")

Check warning on line 372 in base_rest/restapi.py

View check run for this annotation

Codecov / codecov/patch

base_rest/restapi.py#L372

Added line #L372 was not covered by tests
self._parts = parts

def to_openapi_properties(self, service, spec, direction):
Expand Down Expand Up @@ -410,7 +412,7 @@
) # multipart ony sends its parts as string
except json.JSONDecodeError as error:
raise ValidationError(
_(f"{key}'s JSON content is malformed: {error}")
service.env._(f"{key}'s JSON content is malformed: {error}")
) from error
param = part.from_params(service, json_param)
params[key] = param
Expand Down
37 changes: 16 additions & 21 deletions base_rest/tests/test_cerberus_list_validator.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
# Copyright 2020 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import logging
import unittest

from cerberus import Validator

from odoo.exceptions import UserError
from odoo.tests.common import BaseCase, MetaCase
from odoo.tests.common import TransactionCase

from ..components.cerberus_validator import BaseRestCerberusValidator
from ..restapi import CerberusListValidator


class TestCerberusListValidator(BaseCase, MetaCase("DummyCase", (object,), {})):
class TestCerberusListValidator(TransactionCase):
"""Test all the methods that must be implemented by CerberusListValidator to
be a valid RestMethodParam"""

Expand Down Expand Up @@ -47,18 +46,8 @@ def setUpClass(cls):
schema=cls.nested_schema
)
cls.maxDiff = None

def setUp(self):
super().setUp()
# mute logger
loggers = ["odoo.tools.translate"]
for logger in loggers:
logging.getLogger(logger).addFilter(self)

@self.addCleanup
def un_mute_logger():
for logger_ in loggers:
logging.getLogger(logger_).removeFilter(self)
cls.mock_service = unittest.mock.Mock()
cls.mock_service.env = cls.env

def filter(self, record):
# required to mute logger
Expand Down Expand Up @@ -203,15 +192,18 @@ def test_from_params_validation(self):
# minItems / maxItems
with self.assertRaises(UserError):
# minItems = 1
self.simple_schema_list_validator.from_params(None, params=[])
self.simple_schema_list_validator.from_params(self.mock_service, params=[])
with self.assertRaises(UserError):
# maxItems = 2
self.simple_schema_list_validator.from_params(
None, params=[{"name": "test"}, {"name": "test"}, {"name": "test"}]
self.mock_service,
params=[{"name": "test"}, {"name": "test"}, {"name": "test"}],
)
with self.assertRaises(UserError):
# name required
self.simple_schema_list_validator.from_params(None, params=[{}])
self.simple_schema_list_validator.from_params(
self.mock_service, params=[{}]
)

def test_to_response_ignore_unknown(self):
result = [{"name": "test", "unknown": True}]
Expand All @@ -223,15 +215,18 @@ def test_to_response_validation(self):
# as a programmatic error not a user error
with self.assertRaises(SystemError):
# minItems = 1
self.simple_schema_list_validator.to_response(None, result=[])
self.simple_schema_list_validator.to_response(self.mock_service, result=[])
with self.assertRaises(SystemError):
# maxItems = 2
self.simple_schema_list_validator.to_response(
None, result=[{"name": "test"}, {"name": "test"}, {"name": "test"}]
self.mock_service,
result=[{"name": "test"}, {"name": "test"}, {"name": "test"}],
)
with self.assertRaises(SystemError):
# name required
self.simple_schema_list_validator.to_response(None, result=[{}])
self.simple_schema_list_validator.to_response(
self.mock_service, result=[{}]
)

def test_schema_lookup_from_string(self):
class MyService:
Expand Down
31 changes: 10 additions & 21 deletions base_rest/tests/test_cerberus_validator.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
# Copyright 2020 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import logging
import unittest

from cerberus import Validator

from odoo.exceptions import UserError
from odoo.tests.common import BaseCase, MetaCase
from odoo.tests.common import TransactionCase

from ..components.cerberus_validator import BaseRestCerberusValidator
from ..restapi import CerberusValidator
from ..tools import cerberus_to_json


class TestCerberusValidator(BaseCase, MetaCase("DummyCase", (object,), {})):
class TestCerberusValidator(TransactionCase):
"""Test all the methods that must be implemented by CerberusValidator to
be a valid RestMethodParam"""

Expand Down Expand Up @@ -50,22 +49,8 @@ def setUpClass(cls):
cls.nested_schema_cerberus_validator = CerberusValidator(
schema=cls.nested_schema
)

def setUp(self):
super().setUp()
# mute logger
loggers = ["odoo.tools.translate"]
for logger in loggers:
logging.getLogger(logger).addFilter(self)

@self.addCleanup
def un_mute_logger():
for logger_ in loggers:
logging.getLogger(logger_).removeFilter(self)

def filter(self, record):
# required to mute logger
return 0
cls.mock_service = unittest.mock.Mock()
cls.mock_service.env = cls.env

def test_to_openapi_responses(self):
res = self.simple_schema_cerberus_validator.to_openapi_responses(None, None)
Expand Down Expand Up @@ -271,7 +256,9 @@ def test_from_params_ignore_unknown(self):
def test_from_params_validation(self):
# name is required
with self.assertRaises(UserError):
self.simple_schema_cerberus_validator.from_params(None, params={})
self.simple_schema_cerberus_validator.from_params(
self.mock_service, params={}
)

def test_to_response_add_default(self):
result = {"name": "test"}
Expand All @@ -288,7 +275,9 @@ def test_to_response_validation(self):
# If a response is not conform to the expected schema it's considered
# as a programmatic error not a user error
with self.assertRaises(SystemError):
self.simple_schema_cerberus_validator.to_response(None, result={})
self.simple_schema_cerberus_validator.to_response(
self.mock_service, result={}
)

def test_schema_lookup_from_string(self):
class MyService:
Expand Down
113 changes: 113 additions & 0 deletions base_rest_pydantic/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
===================
Base Rest Datamodel
===================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ed7868cc1a1d1a63a8b53b8e25e19ed162638e6f7eb3246bcc6cad2c5c0ca1a6
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github
:target: https://github.com/OCA/rest-framework/tree/18.0/base_rest_pydantic
:alt: OCA/rest-framework
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/rest-framework-18-0/rest-framework-18-0-base_rest_pydantic
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This addon allows you to use Pydantic objects as params and/or response
with your REST API methods.

**Table of contents**

.. contents::
:local:

Usage
=====

To use Pydantic instances as request and/or response of a REST service
endpoint you must:

- Define your Pydantic classes;
- Provides the information required to the
``odoo.addons.base_rest.restapi.method`` decorator;

.. code:: python

from odoo.addons.base_rest import restapi
from odoo.addons.component.core import Component
from odoo.addons.pydantic.models import BaseModel

class PingMessage(BaseModel):
message: str


class PingService(Component):
_inherit = 'base.rest.service'
_name = 'ping.service'
_usage = 'ping'
_collection = 'my_module.services'


@restapi.method(
[(["/pong"], "GET")],
input_param=restapi.PydanticModel(PingMessage),
output_param=restapi.PydanticModel(PingMessage),
auth="public",
)
def pong(self, ping_message):
return PingMessage(message = "Received: " + ping_message.message)

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/rest-framework/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/rest-framework/issues/new?body=module:%20base_rest_pydantic%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* ACSONE SA/NV

Contributors
------------

- Laurent Mignon <laurent.mignon@acsone.eu>

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/rest-framework <https://github.com/OCA/rest-framework/tree/18.0/base_rest_pydantic>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions base_rest_pydantic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import restapi
18 changes: 18 additions & 0 deletions base_rest_pydantic/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2021 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

{
"name": "Base Rest Datamodel",
"summary": """
Pydantic binding for base_rest""",
"version": "18.0.1.0.0",
"license": "LGPL-3",
"author": "ACSONE SA/NV,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/rest-framework",
"depends": ["base_rest"],
"external_dependencies": {
"python": [
"pydantic>=2.0.0",
]
},
}
26 changes: 26 additions & 0 deletions base_rest_pydantic/i18n/base_rest_datamodel.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_rest_datamodel
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: base_rest_datamodel
#: code:addons/base_rest_datamodel/restapi.py:0
#, python-format
msgid "BadRequest %s"
msgstr ""

#. module: base_rest_datamodel
#: code:addons/base_rest_datamodel/restapi.py:0
#, python-format
msgid "Invalid Response %s"
msgstr ""
Loading