@@ -81,6 +81,11 @@ def _ensure_typing_import(content: str, name: str) -> str:
8181 )
8282
8383
84+ def _base_names (node : ast .ClassDef ) -> set [str ]:
85+ """Return the set of unsubscripted base-class names of `node`."""
86+ return {b .id for b in node .bases if isinstance (b , ast .Name )}
87+
88+
8489def fix_discriminators (content : str ) -> str :
8590 """Replace camelCase discriminator values with their snake_case equivalents."""
8691 for camel , snake in DISCRIMINATOR_FIXES .items ():
@@ -93,15 +98,27 @@ def fix_discriminators(content: str) -> str:
9398
9499
95100def deduplicate_error_type_enum (content : str ) -> str :
96- """Remove the duplicate `Type` enum and rewire references to `ErrorType`."""
97- # Remove the entire `class Type(StrEnum): ...` block up to the next class definition.
98- content = re .sub (
99- r'\nclass Type\(StrEnum\):.*?(?=\nclass )' ,
100- '\n ' ,
101- content ,
102- flags = re .DOTALL ,
101+ """Remove the duplicate `Type` enum and rewire references to `ErrorType`.
102+
103+ The `type` property on `ErrorResponse` discriminator subtypes (`RunFailedErrorDetail` etc.)
104+ re-emits the value list of the named `ErrorType` enum as a separate `class Type(StrEnum)` —
105+ upstream issue https://github.com/koxudaxi/datamodel-code-generator/issues/3104.
106+ """
107+ tree = ast .parse (content )
108+ type_node = next (
109+ (n for n in tree .body if isinstance (n , ast .ClassDef ) and n .name == 'Type' and 'StrEnum' in _base_names (n )),
110+ None ,
103111 )
104- # Replace standalone `Type` references in annotation contexts (`: Type`, `| Type`, `[Type`).
112+ if type_node is None :
113+ return content
114+
115+ assert type_node .end_lineno is not None # noqa: S101
116+ lines = content .split ('\n ' )
117+ del lines [type_node .lineno - 1 : type_node .end_lineno ]
118+ content = '\n ' .join (lines )
119+
120+ # Lookbehinds are deliberately narrow: matching bare `\bType\b` would also rewrite `Type` in
121+ # docstrings (`Content-Type`, `Type of event`), which broke an earlier version.
105122 content = re .sub (r'(?<=: )Type\b|(?<=\| )Type\b|(?<=\[)Type\b' , 'ErrorType' , content )
106123 return _collapse_blank_lines (content )
107124
@@ -124,7 +141,7 @@ def convert_enums_to_literals(content: str) -> str:
124141 for node in tree .body :
125142 if not isinstance (node , ast .ClassDef ):
126143 continue
127- base_names = { b . id for b in node . bases if isinstance ( b , ast . Name )}
144+ base_names = _base_names ( node )
128145 if 'StrEnum' not in base_names :
129146 continue
130147
@@ -340,7 +357,7 @@ def flatten_empty_typeddicts(content: str) -> str:
340357 if has_fields :
341358 continue
342359 # Only flatten TypedDict-based classes (e.g. skip Enum subclasses).
343- base_names = { b . id for b in node . bases if isinstance ( b , ast . Name )}
360+ base_names = _base_names ( node )
344361 if 'TypedDict' not in base_names :
345362 continue
346363 assert node .end_lineno is not None # noqa: S101
0 commit comments