Support normalizing anyof/oneof enum constraints to a single enum (#21917)

* Support normalizing anyof/oneof enum constraints to a single enum

* Add SIMPLIFY_ONEOF_ANYOF_ENUM to the documentation

* Process referenced schemas with oneof/enum as well

* Implement referenced enum merging from oneof/anyof

* Implement retaining the enum description as x-enum-desriptions for oneof enum

* Update samples and docs with oneOf enum normalization

* update samples to fix python tests

* fix test file name

* fix incorrect filename

---------

Co-authored-by: Pieter Bos <pieter.bos@nedap.com>
This commit is contained in:
William Cheng
2025-09-07 17:36:02 +08:00
committed by GitHub
parent e62908e30e
commit 6e089f5f03
17 changed files with 493 additions and 376 deletions

View File

@@ -2,27 +2,16 @@
oneOf enum strings
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
## Enum
## Example
* `A` (value: `'a'`)
```python
from petstore_api.models.one_of_enum_string import OneOfEnumString
* `B` (value: `'b'`)
# TODO update the JSON string below
json = "{}"
# create an instance of OneOfEnumString from a JSON string
one_of_enum_string_instance = OneOfEnumString.from_json(json)
# print the JSON string representation of the object
print OneOfEnumString.to_json()
* `C` (value: `'c'`)
* `D` (value: `'d'`)
# convert the object into a dict
one_of_enum_string_dict = one_of_enum_string_instance.to_dict()
# create an instance of OneOfEnumString from a dict
one_of_enum_string_from_dict = OneOfEnumString.from_dict(one_of_enum_string_dict)
```
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -12,130 +12,31 @@
""" # noqa: E501
from __future__ import annotations
from inspect import getfullargspec
import json
import pprint
import re # noqa: F401
from aenum import Enum, no_arg
from typing import Any, List, Optional
from pydantic import BaseModel, Field, StrictStr, ValidationError, validator
from petstore_api.models.enum_string1 import EnumString1
from petstore_api.models.enum_string2 import EnumString2
from typing import Union, Any, List, TYPE_CHECKING
from pydantic import StrictStr, Field
ONEOFENUMSTRING_ONE_OF_SCHEMAS = ["EnumString1", "EnumString2"]
class OneOfEnumString(BaseModel):
class OneOfEnumString(str, Enum):
"""
oneOf enum strings
"""
# data type: EnumString1
oneof_schema_1_validator: Optional[EnumString1] = None
# data type: EnumString2
oneof_schema_2_validator: Optional[EnumString2] = None
if TYPE_CHECKING:
actual_instance: Union[EnumString1, EnumString2]
else:
actual_instance: Any
one_of_schemas: List[str] = Field(ONEOFENUMSTRING_ONE_OF_SCHEMAS, const=True)
class Config:
validate_assignment = True
def __init__(self, *args, **kwargs) -> None:
if args:
if len(args) > 1:
raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`")
if kwargs:
raise ValueError("If a position argument is used, keyword arguments cannot be used.")
super().__init__(actual_instance=args[0])
else:
super().__init__(**kwargs)
@validator('actual_instance')
def actual_instance_must_validate_oneof(cls, v):
instance = OneOfEnumString.construct()
error_messages = []
match = 0
# validate data type: EnumString1
if not isinstance(v, EnumString1):
error_messages.append(f"Error! Input type `{type(v)}` is not `EnumString1`")
else:
match += 1
# validate data type: EnumString2
if not isinstance(v, EnumString2):
error_messages.append(f"Error! Input type `{type(v)}` is not `EnumString2`")
else:
match += 1
if match > 1:
# more than 1 match
raise ValueError("Multiple matches found when setting `actual_instance` in OneOfEnumString with oneOf schemas: EnumString1, EnumString2. Details: " + ", ".join(error_messages))
elif match == 0:
# no match
raise ValueError("No match found when setting `actual_instance` in OneOfEnumString with oneOf schemas: EnumString1, EnumString2. Details: " + ", ".join(error_messages))
else:
return v
@classmethod
def from_dict(cls, obj: dict) -> OneOfEnumString:
return cls.from_json(json.dumps(obj))
"""
allowed enum values
"""
A = 'a'
B = 'b'
C = 'c'
D = 'd'
@classmethod
def from_json(cls, json_str: str) -> OneOfEnumString:
"""Returns the object represented by the json string"""
instance = OneOfEnumString.construct()
error_messages = []
match = 0
# deserialize data into EnumString1
try:
instance.actual_instance = EnumString1.from_json(json_str)
match += 1
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
# deserialize data into EnumString2
try:
instance.actual_instance = EnumString2.from_json(json_str)
match += 1
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
if match > 1:
# more than 1 match
raise ValueError("Multiple matches found when deserializing the JSON string into OneOfEnumString with oneOf schemas: EnumString1, EnumString2. Details: " + ", ".join(error_messages))
elif match == 0:
# no match
raise ValueError("No match found when deserializing the JSON string into OneOfEnumString with oneOf schemas: EnumString1, EnumString2. Details: " + ", ".join(error_messages))
else:
return instance
def to_json(self) -> str:
"""Returns the JSON representation of the actual instance"""
if self.actual_instance is None:
return "null"
to_json = getattr(self.actual_instance, "to_json", None)
if callable(to_json):
return self.actual_instance.to_json()
else:
return json.dumps(self.actual_instance)
def to_dict(self) -> dict:
"""Returns the dict representation of the actual instance"""
if self.actual_instance is None:
return None
to_dict = getattr(self.actual_instance, "to_dict", None)
if callable(to_dict):
return self.actual_instance.to_dict()
else:
# primitive type
return self.actual_instance
def to_str(self) -> str:
"""Returns the string representation of the actual instance"""
return pprint.pformat(self.dict())
"""Create an instance of OneOfEnumString from a JSON string"""
return OneOfEnumString(json.loads(json_str))

View File

@@ -59,9 +59,6 @@ class WithNestedOneOf(BaseModel):
# override the default output from pydantic by calling `to_dict()` of nested_pig
if self.nested_pig:
_dict['nested_pig'] = self.nested_pig.to_dict()
# override the default output from pydantic by calling `to_dict()` of nested_oneof_enum_string
if self.nested_oneof_enum_string:
_dict['nested_oneof_enum_string'] = self.nested_oneof_enum_string.to_dict()
return _dict
@classmethod
@@ -76,7 +73,7 @@ class WithNestedOneOf(BaseModel):
_obj = WithNestedOneOf.parse_obj({
"size": obj.get("size"),
"nested_pig": Pig.from_dict(obj.get("nested_pig")) if obj.get("nested_pig") is not None else None,
"nested_oneof_enum_string": OneOfEnumString.from_dict(obj.get("nested_oneof_enum_string")) if obj.get("nested_oneof_enum_string") is not None else None
"nested_oneof_enum_string": obj.get("nested_oneof_enum_string")
})
return _obj