Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ public String generatePackageName(String packageName) {

@Override
public String generatorLanguageVersion() {
return "3.9+";
return "3.11+";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

breaking change 👀

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import warnings
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
from typing import Any, Dict, List, Optional, Tuple, Union
from typing_extensions import Annotated
from typing import Annotated

{{#imports}}
{{import}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ class ApiClient:
return_data = self.__deserialize_file(response_data)
elif response_type is not None:
match = None
content_type = response_data.headers.get('content-type')
content_type = response_data.getheader('content-type')
if content_type is not None:
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
encoding = match.group(1) if match else "utf-8"
Expand All @@ -338,7 +338,7 @@ class ApiClient:
return ApiResponse(
status_code = response_data.status,
data = return_data,
headers = response_data.headers,
headers = response_data.getheaders(),
raw_data = response_data.data
)

Expand Down Expand Up @@ -389,13 +389,10 @@ class ApiClient:
# and attributes which value is not None.
# Convert attribute name to json key in
# model definition for request.
if hasattr(obj, 'to_dict') and callable(getattr(obj, 'to_dict')):
obj_dict = obj.to_dict()
else:
obj_dict = obj.__dict__
obj_dict = obj.model_dump(by_alias=True, exclude_none=True)
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: RootModel values now cause sanitize_for_serialization to crash because model_dump may return a non-dict, leading to .items() AttributeError

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/api_client.mustache, line 392:

<comment>RootModel values now cause sanitize_for_serialization to crash because model_dump may return a non-dict, leading to .items() AttributeError</comment>

<file context>
@@ -389,13 +389,10 @@ class ApiClient:
-                obj_dict = obj.to_dict()
-            else:
-                obj_dict = obj.__dict__
+            obj_dict = obj.model_dump(by_alias=True, exclude_none=True)
 
         if isinstance(obj_dict, list):
</file context>
Suggested change
obj_dict = obj.model_dump(by_alias=True, exclude_none=True)
obj_dict = obj.model_dump(by_alias=True, exclude_none=True)
if not isinstance(obj_dict, (dict, list)):
return self.sanitize_for_serialization(obj_dict)
Fix with Cubic

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Serialization now always drops None fields, so clients can’t send explicit JSON null for nullable fields (e.g., to clear values in PATCH). This is a behavior regression compared to using raw __dict__ and breaks APIs that distinguish null vs omitted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/api_client.mustache, line 392:

<comment>Serialization now always drops `None` fields, so clients can’t send explicit JSON `null` for nullable fields (e.g., to clear values in PATCH). This is a behavior regression compared to using raw `__dict__` and breaks APIs that distinguish `null` vs omitted.</comment>

<file context>
@@ -389,13 +389,10 @@ class ApiClient:
-                obj_dict = obj.to_dict()
-            else:
-                obj_dict = obj.__dict__
+            obj_dict = obj.model_dump(by_alias=True, exclude_none=True)
 
         if isinstance(obj_dict, list):
</file context>
Suggested change
obj_dict = obj.model_dump(by_alias=True, exclude_none=True)
obj_dict = obj.model_dump(by_alias=True)
Fix with Cubic

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Hardcoding exclude_none=True in request serialization drops explicit null values for nullable fields, changing behavior from previous to_dict() and preventing clients from sending nulls when required (e.g., PATCH/unset).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/api_client.mustache, line 392:

<comment>Hardcoding `exclude_none=True` in request serialization drops explicit `null` values for nullable fields, changing behavior from previous `to_dict()` and preventing clients from sending nulls when required (e.g., PATCH/unset).</comment>

<file context>
@@ -389,13 +389,10 @@ class ApiClient:
-                obj_dict = obj.to_dict()
-            else:
-                obj_dict = obj.__dict__
+            obj_dict = obj.model_dump(by_alias=True, exclude_none=True)
 
         if isinstance(obj_dict, list):
</file context>
Suggested change
obj_dict = obj.model_dump(by_alias=True, exclude_none=True)
obj_dict = obj.model_dump(by_alias=True)
Fix with Cubic


if isinstance(obj_dict, list):
# here we handle instances that can either be a list or something else, and only became a real list by calling to_dict()
# here we handle instances that can either be a list or something else, and only became a real list by calling model_dump(by_alias=True)
return self.sanitize_for_serialization(obj_dict)

return {
Expand Down Expand Up @@ -719,14 +716,14 @@ class ApiClient:
os.close(fd)
os.remove(path)

content_disposition = response.headers.get("Content-Disposition")
content_disposition = response.getheader("Content-Disposition")
if content_disposition:
m = re.search(
r'filename=[\'"]?([^\'"\s]+)[\'"]?',
content_disposition
)
assert m is not None, "Unexpected 'content-disposition' header value"
filename = m.group(1)
filename = os.path.basename(m.group(1)) # Strip any directory traversal
path = os.path.join(os.path.dirname(path), filename)

with open(path, "wb") as f:
Expand Down Expand Up @@ -819,4 +816,4 @@ class ApiClient:
:return: model object.
"""

return klass.from_dict(data)
return klass.model_validate(data)
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ from logging import FileHandler
import multiprocessing
{{/async}}
import sys
from typing import Any, ClassVar, Dict, List, Literal, Optional, TypedDict, Union
from typing_extensions import NotRequired, Self
from typing import Any, ClassVar, Dict, List, Literal, NotRequired, Optional, TypedDict, Union, Self
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Importing NotRequired/Self directly from typing breaks Python <3.11 because these names are unavailable there; the template lacks a fallback or enforced 3.11+ requirement.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/configuration.mustache, line 17:

<comment>Importing NotRequired/Self directly from typing breaks Python <3.11 because these names are unavailable there; the template lacks a fallback or enforced 3.11+ requirement.</comment>

<file context>
@@ -14,8 +14,7 @@ from logging import FileHandler
 import sys
-from typing import Any, ClassVar, Dict, List, Literal, Optional, TypedDict, Union
-from typing_extensions import NotRequired, Self
+from typing import Any, ClassVar, Dict, List, Literal, NotRequired, Optional, TypedDict, Union, Self
 
 {{^async}}
</file context>
Fix with Cubic


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

breaking change for python 3.9

python -c "from typing import Self; print('ok')"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: cannot import name 'Self' from 'typing'

{{^async}}
import urllib3
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# coding: utf-8

{{>partial_header}}

from typing import Any, Optional
from typing_extensions import Self
from typing import Any, Optional, Self

class OpenApiException(Exception):
"""The base exception class for all OpenAPIExceptions"""
Expand Down Expand Up @@ -95,9 +92,9 @@ class ApiKeyError(OpenApiException, KeyError):
class ApiException(OpenApiException):

def __init__(
self,
status=None,
reason=None,
self,
status=None,
reason=None,
http_resp=None,
*,
body: Optional[str] = None,
Expand All @@ -119,14 +116,14 @@ class ApiException(OpenApiException):
self.body = http_resp.data.decode('utf-8')
except Exception:
pass
self.headers = http_resp.headers
self.headers = http_resp.getheaders()
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using http_resp.getheaders() breaks compatibility with urllib3 v2 where getheaders() was removed; use the headers attribute instead to avoid AttributeError.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/exceptions.mustache, line 119:

<comment>Using http_resp.getheaders() breaks compatibility with urllib3 v2 where getheaders() was removed; use the headers attribute instead to avoid AttributeError.</comment>

<file context>
@@ -119,14 +116,14 @@ class ApiException(OpenApiException):
                 except Exception:
                     pass
-            self.headers = http_resp.headers
+            self.headers = http_resp.getheaders()
 
     @classmethod
</file context>
Suggested change
self.headers = http_resp.getheaders()
self.headers = http_resp.headers
Fix with Cubic


@classmethod
def from_response(
cls,
*,
http_resp,
body: Optional[str],
cls,
*,
http_resp,
body: Optional[str],
data: Optional[Any],
) -> Self:
if http_resp.status == 400:
Expand Down Expand Up @@ -160,11 +157,8 @@ class ApiException(OpenApiException):
error_message += "HTTP response headers: {0}\n".format(
self.headers)

if self.body:
error_message += "HTTP response body: {0}\n".format(self.body)

if self.data:
error_message += "HTTP response data: {0}\n".format(self.data)
if self.data or self.body:
error_message += "HTTP response body: {0}\n".format(self.data or self.body)
Comment on lines +160 to +161
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using or operator to select between data and body fails for falsy but valid data values like empty dicts/lists. If self.data is {} or [], it will incorrectly show self.body instead. Consider using explicit None checks:

if self.data is not None or self.body:
    error_message += "HTTP response body: {0}\n".format(self.data if self.data is not None else self.body)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/exceptions.mustache, line 160:

<comment>Using `or` operator to select between `data` and `body` fails for falsy but valid data values like empty dicts/lists. If `self.data` is `{}` or `[]`, it will incorrectly show `self.body` instead. Consider using explicit `None` checks:
```python
if self.data is not None or self.body:
    error_message += &quot;HTTP response body: {0}\n&quot;.format(self.data if self.data is not None else self.body)
```</comment>

<file context>
@@ -160,11 +157,8 @@ class ApiException(OpenApiException):
-
-        if self.data:
-            error_message += &quot;HTTP response data: {0}\n&quot;.format(self.data)
+        if self.data or self.body:
+            error_message += &quot;HTTP response body: {0}\n&quot;.format(self.data or self.body)
 
</file context>
Suggested change
if self.data or self.body:
error_message += "HTTP response body: {0}\n".format(self.data or self.body)
if self.data is not None or self.body:
error_message += "HTTP response body: {0}\n".format(self.data if self.data is not None else self.body)
Fix with Cubic


Comment on lines +161 to 162
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: ApiException str now drops one of body/data and mislabels data as body due to self.data or self.body

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/exceptions.mustache, line 161:

<comment>ApiException __str__ now drops one of body/data and mislabels data as body due to `self.data or self.body`</comment>

<file context>
@@ -160,11 +157,8 @@ class ApiException(OpenApiException):
-        if self.data:
-            error_message += "HTTP response data: {0}\n".format(self.data)
+        if self.data or self.body:
+            error_message += "HTTP response body: {0}\n".format(self.data or self.body)
 
         return error_message
</file context>
Suggested change
error_message += "HTTP response body: {0}\n".format(self.data or self.body)
if self.body is not None:
error_message += "HTTP response body: {0}\n".format(self.body)
if self.data is not None:
error_message += "HTTP response data: {0}\n".format(self.data)

return error_message

Expand Down
168 changes: 25 additions & 143 deletions modules/openapi-generator/src/main/resources/python/model_anyof.mustache
Original file line number Diff line number Diff line change
@@ -1,177 +1,59 @@
from __future__ import annotations
from inspect import getfullargspec
import json
import pprint
import re # noqa: F401
{{#vendorExtensions.x-py-other-imports}}
{{{.}}}
{{/vendorExtensions.x-py-other-imports}}
{{#vendorExtensions.x-py-model-imports}}
{{{.}}}
{{/vendorExtensions.x-py-model-imports}}
from typing import Union, Any, List, Set, TYPE_CHECKING, Optional, Dict
from typing_extensions import Literal, Self
from pydantic import Field
from pydantic import Field, RootModel
from typing import Any, Dict, List, Union, Self

{{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ANY_OF_SCHEMAS = [{{#anyOf}}"{{.}}"{{^-last}}, {{/-last}}{{/anyOf}}]

class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}):

class {{classname}}(RootModel[Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}]]):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: RootModel base evaluates anyOf types before postponed imports, risking NameError when any union member is only imported in the deferred block.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/model_anyof.mustache, line 15:

<comment>RootModel base evaluates anyOf types before postponed imports, risking NameError when any union member is only imported in the deferred block.</comment>

<file context>
@@ -1,177 +1,40 @@
 
-class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}):
+
+class {{classname}}(RootModel[Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}]]):
     """
     {{{description}}}{{^description}}{{{classname}}}{{/description}}
</file context>

"""
{{{description}}}{{^description}}{{{classname}}}{{/description}}
"""

{{#composedSchemas.anyOf}}
# data type: {{{dataType}}}
{{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}}
{{/composedSchemas.anyOf}}
if TYPE_CHECKING:
actual_instance: Optional[Union[{{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}]] = None
else:
actual_instance: Any = None
any_of_schemas: Set[str] = { {{#anyOf}}"{{.}}"{{^-last}}, {{/-last}}{{/anyOf}} }

model_config = {
"validate_assignment": True,
"protected_namespaces": (),
}
{{#discriminator}}

discriminator_value_class_map: Dict[str, str] = {
{{#children}}
'{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}': '{{{classname}}}'{{^-last}},{{/-last}}
{{/children}}
}
{{/discriminator}}
root: Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}{{#isNullable}}, None{{/isNullable}}] = Field(
{{#isNullable}}None{{/isNullable}}{{^isNullable}}...{{/isNullable}}{{#discriminator}}, discriminator="{{discriminatorName}}"{{/discriminator}}
)

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)
def __getattr__(self, name):
"""
Delegate attribute access to the root model if the attribute
doesn't exist on the main class.
"""

@field_validator('actual_instance')
def actual_instance_must_validate_anyof(cls, v):
{{#isNullable}}
if v is None:
return v
if name in self.__dict__:
return super().__getattribute__(name)

{{/isNullable}}
instance = {{{classname}}}.model_construct()
error_messages = []
{{#composedSchemas.anyOf}}
# validate data type: {{{dataType}}}
{{#isContainer}}
try:
instance.{{vendorExtensions.x-py-name}} = v
return v
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isContainer}}
{{^isContainer}}
{{#isPrimitiveType}}
try:
instance.{{vendorExtensions.x-py-name}} = v
return v
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isPrimitiveType}}
{{^isPrimitiveType}}
if not isinstance(v, {{{dataType}}}):
error_messages.append(f"Error! Input type `{type(v)}` is not `{{{dataType}}}`")
else:
return v
root = self.__dict__.get('root')
if root is not None:
return getattr(root, name)

{{/isPrimitiveType}}
{{/isContainer}}
{{/composedSchemas.anyOf}}
if error_messages:
# no match
raise ValueError("No match found when setting the actual_instance in {{{classname}}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. Details: " + ", ".join(error_messages))
else:
return v
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

@classmethod
def from_dict(cls, obj: Dict[str, Any]) -> Self:
return cls.from_json(json.dumps(obj))
"""Returns the object represented by the python Dict"""
return cls.model_validate(obj, strict=True)

@classmethod
def from_json(cls, json_str: str) -> Self:
"""Returns the object represented by the json string"""
instance = cls.model_construct()
{{#isNullable}}
if json_str is None:
return instance

{{/isNullable}}
error_messages = []
{{#composedSchemas.anyOf}}
{{#isContainer}}
# deserialize data into {{{dataType}}}
try:
# validation
instance.{{vendorExtensions.x-py-name}} = json.loads(json_str)
# assign value to actual_instance
instance.actual_instance = instance.{{vendorExtensions.x-py-name}}
return instance
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isContainer}}
{{^isContainer}}
{{#isPrimitiveType}}
# deserialize data into {{{dataType}}}
try:
# validation
instance.{{vendorExtensions.x-py-name}} = json.loads(json_str)
# assign value to actual_instance
instance.actual_instance = instance.{{vendorExtensions.x-py-name}}
return instance
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isPrimitiveType}}
{{^isPrimitiveType}}
# {{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}}
try:
instance.actual_instance = {{{dataType}}}.from_json(json_str)
return instance
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isPrimitiveType}}
{{/isContainer}}
{{/composedSchemas.anyOf}}

if error_messages:
# no match
raise ValueError("No match found when deserializing the JSON string into {{{classname}}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. Details: " + ", ".join(error_messages))
else:
return instance
return cls.model_validate_json(json_str)

def to_json(self) -> str:
"""Returns the JSON representation of the actual instance"""
if self.actual_instance is None:
return "null"
return self.model_dump_json(by_alias=True)

if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json):
return self.actual_instance.to_json()
else:
return json.dumps(self.actual_instance)

def to_dict(self) -> Optional[Union[Dict[str, Any], {{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}]]:
def to_dict(self) -> Dict[str, Any]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: to_dict now claims to return Dict[str, Any] but still returns the root value, which may be non-dict for primitive/array anyOf branches, causing a contract mismatch and potential runtime errors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/model_anyof.mustache, line 40:

<comment>`to_dict` now claims to return `Dict[str, Any]` but still returns the root value, which may be non-dict for primitive/array anyOf branches, causing a contract mismatch and potential runtime errors.</comment>

<file context>
@@ -37,7 +37,7 @@ class {{classname}}(RootModel[Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyO
         return self.model_dump_json(by_alias=True)
 
-    def to_dict(self) -> Optional[Union[Dict[str, Any], {{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}]]:
+    def to_dict(self) -> Dict[str, Any]:
         """Returns the dict representation of the actual instance"""
         return self.model_dump(by_alias=True)
</file context>

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: to_dict is annotated as returning Dict[str, Any], but for RootModel the model_dump() return value is the root value directly, which can be a primitive or list for anyOf schemas. The type hint is incorrect and will mislead static analysis.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/model_anyof.mustache, line 50:

<comment>`to_dict` is annotated as returning `Dict[str, Any]`, but for `RootModel` the `model_dump()` return value is the root value directly, which can be a primitive or list for anyOf schemas. The type hint is incorrect and will mislead static analysis.</comment>

<file context>
@@ -1,177 +1,59 @@
-            return json.dumps(self.actual_instance)
-
-    def to_dict(self) -> Optional[Union[Dict[str, Any], {{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}]]:
+    def to_dict(self) -> Dict[str, Any]:
         """Returns the dict representation of the actual instance"""
-        if self.actual_instance is None:
</file context>
Suggested change
def to_dict(self) -> Dict[str, Any]:
def to_dict(self) -> Any:
Fix with Cubic

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: to_dict is typed as Dict[str, Any], but RootModel.model_dump returns the root value directly (can be primitive or list for anyOf). This misleads type checkers and callers when the root is not a dict.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/python/model_anyof.mustache, line 50:

<comment>to_dict is typed as Dict[str, Any], but RootModel.model_dump returns the root value directly (can be primitive or list for anyOf). This misleads type checkers and callers when the root is not a dict.</comment>

<file context>
@@ -1,177 +1,59 @@
-            return json.dumps(self.actual_instance)
-
-    def to_dict(self) -> Optional[Union[Dict[str, Any], {{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}]]:
+    def to_dict(self) -> Dict[str, Any]:
         """Returns the dict representation of the actual instance"""
-        if self.actual_instance is None:
</file context>
Suggested change
def to_dict(self) -> Dict[str, Any]:
def to_dict(self) -> Any:
Fix with Cubic

"""Returns the dict representation of the actual instance"""
if self.actual_instance is None:
return None

if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict):
return self.actual_instance.to_dict()
else:
return self.actual_instance
return self.model_dump(by_alias=True)

def to_str(self) -> str:
"""Returns the string representation of the actual instance"""
return pprint.pformat(self.model_dump())
return pprint.pformat(self.model_dump(by_alias=True, mode="json"))

{{#vendorExtensions.x-py-postponed-model-imports.size}}
{{#vendorExtensions.x-py-postponed-model-imports}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ from {{modelPackage}}.{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}} im
# TODO update the JSON string below
json = "{}"
# create an instance of {{classname}} from a JSON string
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance = {{classname}}.from_json(json)
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance = {{classname}}.model_validate_json(json)
# print the JSON string representation of the object
print({{classname}}.to_json())
print({{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance.model_dump_json(by_alias=True, exclude_unset=True))

# convert the object into a dict
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict = {{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance.to_dict()
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict = {{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance.model_dump(by_alias=True)
# create an instance of {{classname}} from a dict
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_from_dict = {{classname}}.from_dict({{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict)
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_from_dict = {{classname}}.model_validate({{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict)
```
{{/isEnum}}
{{#isEnum}}
Expand Down
Loading
Loading