Skip to content
  •  
  •  
  •  
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)

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

{{^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()

@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

@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

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
172 changes: 22 additions & 150 deletions modules/openapi-generator/src/main/resources/python/model_anyof.mustache
Original file line number Diff line number Diff line change
@@ -1,177 +1,49 @@
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, Union

{{#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

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}}

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)
root: Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}] = Field(
Copy link

Choose a reason for hiding this comment

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

P1: Nullable anyOf schemas now reject null because the required root Union field no longer includes Optional/nullable handling.

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 23:

<comment>Nullable anyOf schemas now reject null because the required root Union field no longer includes Optional/nullable handling.</comment>

<file context>
@@ -9,169 +9,35 @@ import re  # noqa: F401
-        """Returns the JSON representation of the actual instance"""
-        if self.actual_instance is None:
-            return "null"
+    root: Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}] = Field(
+        ...{{#discriminator}}, discriminator="{{discriminatorName}}"{{/discriminator}}
+    )
</file context>
Suggested change
root: Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}] = Field(
root: {{#isNullable}}Optional[{{/isNullable}}Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}]{{#isNullable}}]{{/isNullable}} = Field(
{{#isNullable}}None{{/isNullable}}{{^isNullable}}...{{/isNullable}}{{#discriminator}}, discriminator="{{discriminatorName}}"{{/discriminator}}
)

...{{#discriminator}}, discriminator="{{discriminatorName}}"{{/discriminator}}
)

@field_validator('actual_instance')
def actual_instance_must_validate_anyof(cls, v):
{{#isNullable}}
if v is None:
return v
def __getattr__(self, name):
"""
Delegate attribute access to the root model if the attribute
doesn't exist on the main class.
"""

{{/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
if name in self.__dict__:
return super().__getattribute__(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
root = self.__dict__.get('root')
if root is not None:
return getattr(root, name)

@classmethod
def from_dict(cls, obj: Dict[str, Any]) -> Self:
return cls.from_json(json.dumps(obj))

@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
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

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

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>

"""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