Newer
Older
##############################################################################
#
# Converter Odoo module
# Copyright © 2020, 2024 XCG Consulting <https://xcg-consulting.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

Vincent Hatakeyama
committed
from collections.abc import Iterable, Mapping
from typing import Any, Final
from odoo import _, api, models # type: ignore[import-untyped]
from odoo.exceptions import UserError # type: ignore[import-untyped]
ContextBuilder,
Converter,
Newinstance,
NewinstanceType,
from .validate import NotInitialized, Validation, Validator
try:
from fastjsonschema import JsonSchemaException # type: ignore[import-untyped]
except ImportError:
# Ignore no-redef, added for compatibility
class JsonSchemaException(Exception): # type: ignore[no-redef]
"""Custom error in case of missing optional requirement"""
class IncorrectTypeException(Exception):
"""__type__ is in the message is not the same as the expected value"""
class MissingRequiredValidatorException(Exception):
def __init__(self):
super().__init__("Strict validation without validator")
"""A converter that takes a dict of key, used when a message has values"""
def __init__(
self,
converters: Mapping[str, Converter],
# The validator is usually not given at this point but is common
# throughout a project. That’s why it is a property
validator: Validator | None = None,
merge_with: Iterable[Converter] | None = None,
datatype: str | None = None,
__type__: str | None = None,
"""
:param datatype: datatype to use. Usually used with None __type__.
"""
super().__init__()
self._converters: Mapping[str, Converter] = converters
self._post_hooks_converters: dict[str, PostHookConverter] = {}
"""First converter whose `is_instance_getter` is true if any"""
self.merge_with: Iterable[Converter] | None = merge_with
self.context: ContextBuilder | None = context
self._datatype: Final[str | None] = datatype
if self._get_instance is None and converter.is_instance_getter:
if isinstance(converter, PostHookConverter):
self._post_hooks_converters[key] = converter
def odoo_to_message(self, instance: models.BaseModel, ctx: Context = None) -> Any:
ctx = build_context(instance, ctx, self.context)
message_data = {}
try:
value = self._converters[key].odoo_to_message(instance, ctx)
except Exception as e:
errors.append(
{"key": key, "traceback": "".join(traceback.format_exception(e))}
)
continue
if len(errors) != 0:
formatted_errors = "\n\n".join(
[f"{error['traceback']}Key: {error['key']}" for error in errors]
)
raise UserError(
_(
"Got unexpected errors while parsing substitutions:\n%s",
formatted_errors,
)
)
if self.merge_with:
for conv in self.merge_with:
value = conv.odoo_to_message(instance, ctx)
if self.validation != Validation.SKIP and self._jsonschema is not None:
if self.validator:
try:
self.validator.validate(self._jsonschema, message_data)
except (NotInitialized, JsonSchemaException):
_logger.warning("Validation failed", exc_info=True)
if self.validation == Validation.STRICT:
raise
elif self.validation == Validation.STRICT:
raise MissingRequiredValidatorException()
return message_data
def message_to_odoo(
self,
if self.validation != Validation.SKIP and self._jsonschema is not None:
if self.validator:
try:
self.validator.validate(self._jsonschema, message_value)
except (NotInitialized, JsonSchemaException):
_logger.warning("Validation failed", exc_info=True)
if self.validation == Validation.STRICT:
raise
elif self.validation == Validation.STRICT:
raise MissingRequiredValidatorException()
if self._type is not None and message_value["__type__"] != self._type:
raise IncorrectTypeException(
"Expected __type__ {}, found {}".format(
self._type, message_value["__type__"]
)
)
for key in self._converters:
value = message_value.get(key, None) if message_value else None
attribute_vals = self._converters[key].message_to_odoo(
odoo_env,
phase,
value,
instance,
message_value and key in message_value,
if isinstance(attribute_vals, SkipType):
continue
values.update(attribute_vals)
if self.merge_with:
for conv in self.merge_with:
value = conv.message_to_odoo(
odoo_env, phase, message_value, instance, value_present
)
continue
values.update(value)
return values
def is_instance_getter(self) -> bool:
return self._get_instance is not None
def get_instance(
self, odoo_env: api.Environment, message_data
""":return: an instance linked to the converter, if any"""
if self._get_instance:
instance = self._converters[self._get_instance].get_instance(
odoo_env, message_data[self._get_instance]
)
if instance is None:
instance = Newinstance
def post_hook(self, instance: models.BaseModel, message_data):
for key in self._post_hooks_converters:
self._post_hooks_converters[key].post_hook(instance, message_data[key])
for converter in self.merge_with:
if hasattr(converter, "post_hook"):
converter.post_hook(instance, message_data)

Vincent Hatakeyama
committed
def get__type__(self) -> set[str]:
@property
def possible_datatypes(self) -> set[str]:
result = set()
if self._datatype is not None:
result.add(self._datatype)
return result
def odoo_datatype(self, instance: models.BaseModel) -> str | None:
return self._datatype