From 5be5e6aa04f152e25cae086477b780502b3ae4a3 Mon Sep 17 00:00:00 2001 From: Callum Dickinson Date: Mon, 15 Dec 2025 07:37:01 +1300 Subject: [PATCH] Fix more Python 3.10+ union type parsing issues * Fix parsing Python 3.10+ type hints when encoding search filters. * Fix parsing Python 3.10+ type hints when decoding record values. * Add missing release note for fixing parsing Python 3.10+ type hints when evaluating model refs. --- changelog.d/12.fixed.md | 1 + changelog.d/14.fixed.md | 1 + openstack_odooclient/base/record.py | 9 ++++----- openstack_odooclient/base/record_manager.py | 19 +++++++++++++------ 4 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 changelog.d/12.fixed.md create mode 100644 changelog.d/14.fixed.md diff --git a/changelog.d/12.fixed.md b/changelog.d/12.fixed.md new file mode 100644 index 0000000..2f5021f --- /dev/null +++ b/changelog.d/12.fixed.md @@ -0,0 +1 @@ +Fix parsing Python 3.10+ union type hints when evaluating model refs diff --git a/changelog.d/14.fixed.md b/changelog.d/14.fixed.md new file mode 100644 index 0000000..73d3464 --- /dev/null +++ b/changelog.d/14.fixed.md @@ -0,0 +1 @@ +Fix parsing Python 3.10+ union type hints when encoding search filters, and decoding record values diff --git a/openstack_odooclient/base/record.py b/openstack_odooclient/base/record.py index 8be05c4..503a413 100644 --- a/openstack_odooclient/base/record.py +++ b/openstack_odooclient/base/record.py @@ -395,10 +395,9 @@ def _getattr_model_ref( attr_type_origin = get_type_origin(attr_type) if attr_type_origin is Union or attr_type_origin is UnionType: unsupported_union = ( - "Only unions of the format Optional[T], " - "Union[T, type(None)] or Union[T, Literal[False]] " - "are supported for singular model refs, " - f"found type hint: {attr_type}" + "Only unions of the format 'T | None' " + "or 'T | Literal[False]' are supported for singular " + f"model refs, found type hint: {attr_type}" ) union_types = set(get_type_args(attr_type)) if len(union_types) > 2: # noqa: PLR2004 @@ -465,7 +464,7 @@ def _decode_value(cls, type_hint: Any, value: Any) -> Any: # Not suitable for handling complicated union structures. # TODO(callumdickinson): Find a way to handle complicated # union structures more smartly. - if value_type is Union: + if value_type is Union or value_type is UnionType: attr_union_types = get_type_args(type_hint) if len(attr_union_types) == 2: # noqa: PLR2004 # T | None diff --git a/openstack_odooclient/base/record_manager.py b/openstack_odooclient/base/record_manager.py index 25b4019..c23e9b6 100644 --- a/openstack_odooclient/base/record_manager.py +++ b/openstack_odooclient/base/record_manager.py @@ -18,7 +18,7 @@ import builtins from datetime import date, datetime -from types import MappingProxyType +from types import MappingProxyType, UnionType from typing import ( TYPE_CHECKING, Annotated, @@ -961,25 +961,32 @@ def _encode_value(self, type_hint: Any, value: Any) -> Any: return value # For every other field type, parse the possible value types # from the type hint. + # First, remove Annotated to get the actual attribute type. type_hint_origin = get_type_origin(type_hint) or type_hint attr_type = ( get_type_args(type_hint)[0] if type_hint_origin is Annotated - else type_hint_origin + else type_hint ) + # Next, get a list of allowed values types from the attribute type. + # If there is only one, create a list containing only that type. attr_type_origin = get_type_origin(attr_type) or attr_type value_types = ( get_type_args(attr_type) - if attr_type_origin is Union - else [attr_type_origin] + if attr_type_origin is Union or attr_type_origin is UnionType + else [attr_type] ) # Recursively handle the types that need to be serialised. for value_type in value_types: + value_type_origin = get_type_origin(value_type) or value_type if value_type is date and isinstance(value, date): return value.strftime(DEFAULT_SERVER_DATE_FORMAT) if value_type is datetime and isinstance(value, datetime): return value.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - if value_type is list and isinstance(value, (list, set, tuple)): - v_type = get_type_args(type_hint)[0] + if value_type_origin is list and isinstance( + value, + (list, set, tuple), + ): + v_type = get_type_args(value_type)[0] return [self._encode_value(v_type, v) for v in value] return value