pyopenapi-gen 0.10.2__py3-none-any.whl → 0.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyopenapi_gen/__init__.py +1 -1
- pyopenapi_gen/core/exceptions.py +1 -6
- pyopenapi_gen/core/loader/loader.py +19 -3
- pyopenapi_gen/core/loader/operations/parser.py +12 -6
- pyopenapi_gen/core/loader/operations/post_processor.py +4 -2
- pyopenapi_gen/core/loader/operations/request_body.py +10 -5
- pyopenapi_gen/core/loader/parameters/parser.py +66 -10
- pyopenapi_gen/core/loader/responses/parser.py +14 -7
- pyopenapi_gen/core/loader/schemas/extractor.py +42 -11
- pyopenapi_gen/core/parsing/context.py +4 -2
- pyopenapi_gen/core/parsing/keywords/all_of_parser.py +8 -4
- pyopenapi_gen/core/parsing/keywords/any_of_parser.py +10 -5
- pyopenapi_gen/core/parsing/keywords/array_items_parser.py +6 -3
- pyopenapi_gen/core/parsing/keywords/one_of_parser.py +10 -5
- pyopenapi_gen/core/parsing/schema_finalizer.py +6 -3
- pyopenapi_gen/core/parsing/schema_parser.py +6 -4
- pyopenapi_gen/emitters/endpoints_emitter.py +2 -1
- pyopenapi_gen/emitters/models_emitter.py +12 -14
- pyopenapi_gen/generator/client_generator.py +20 -5
- pyopenapi_gen/helpers/type_resolution/named_resolver.py +6 -7
- pyopenapi_gen/helpers/type_resolution/object_resolver.py +18 -14
- pyopenapi_gen/types/resolvers/schema_resolver.py +18 -3
- pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +1 -1
- pyopenapi_gen/visit/endpoint/generators/signature_generator.py +3 -11
- pyopenapi_gen/visit/model/alias_generator.py +15 -8
- pyopenapi_gen/visit/model/dataclass_generator.py +25 -15
- pyopenapi_gen/visit/model/enum_generator.py +34 -22
- pyopenapi_gen/visit/model/model_visitor.py +7 -5
- {pyopenapi_gen-0.10.2.dist-info → pyopenapi_gen-0.12.0.dist-info}/METADATA +3 -3
- {pyopenapi_gen-0.10.2.dist-info → pyopenapi_gen-0.12.0.dist-info}/RECORD +33 -33
- {pyopenapi_gen-0.10.2.dist-info → pyopenapi_gen-0.12.0.dist-info}/WHEEL +0 -0
- {pyopenapi_gen-0.10.2.dist-info → pyopenapi_gen-0.12.0.dist-info}/entry_points.txt +0 -0
- {pyopenapi_gen-0.10.2.dist-info → pyopenapi_gen-0.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -77,9 +77,10 @@ class ClientGenerator:
|
|
77
77
|
else:
|
78
78
|
log_msg = f"{timestamp} ({elapsed:.2f}s) {message}"
|
79
79
|
|
80
|
-
|
81
|
-
# Also print to stdout for CLI users
|
82
|
-
|
80
|
+
logger.info(log_msg)
|
81
|
+
# Also print to stdout for CLI users when verbose mode is enabled
|
82
|
+
if self.verbose:
|
83
|
+
print(log_msg)
|
83
84
|
|
84
85
|
def generate(
|
85
86
|
self,
|
@@ -523,9 +524,23 @@ class ClientGenerator:
|
|
523
524
|
"""
|
524
525
|
spec_path_obj = Path(path_or_url)
|
525
526
|
if spec_path_obj.exists() and spec_path_obj.is_file(): # Added is_file() check
|
526
|
-
|
527
|
+
text = spec_path_obj.read_text()
|
528
|
+
# Prefer JSON for .json files to avoid optional PyYAML dependency in tests
|
529
|
+
if spec_path_obj.suffix.lower() == ".json":
|
530
|
+
import json
|
531
|
+
|
532
|
+
data = json.loads(text)
|
533
|
+
else:
|
534
|
+
try:
|
535
|
+
import yaml
|
536
|
+
|
537
|
+
data = yaml.safe_load(text)
|
538
|
+
except ModuleNotFoundError:
|
539
|
+
# Fallback: attempt JSON parsing if YAML is unavailable
|
540
|
+
import json
|
541
|
+
|
542
|
+
data = json.loads(text)
|
527
543
|
|
528
|
-
data = yaml.safe_load(spec_path_obj.read_text())
|
529
544
|
if not isinstance(data, dict):
|
530
545
|
raise GenerationError("Loaded spec is not a dictionary.")
|
531
546
|
return data
|
@@ -75,15 +75,14 @@ class NamedTypeResolver:
|
|
75
75
|
if schema.name and schema.name in self.all_schemas:
|
76
76
|
# This schema is a REFERENCE to a globally defined schema (e.g., in components/schemas)
|
77
77
|
ref_schema = self.all_schemas[schema.name] # Get the actual definition
|
78
|
-
|
78
|
+
if ref_schema.name is None:
|
79
|
+
raise RuntimeError(f"Schema '{schema.name}' resolved to ref_schema with None name.")
|
79
80
|
|
80
81
|
# NEW: Use generation_name and final_module_stem from the referenced schema
|
81
|
-
|
82
|
-
ref_schema.
|
83
|
-
|
84
|
-
|
85
|
-
ref_schema.final_module_stem is not None
|
86
|
-
), f"Referenced schema '{ref_schema.name}' must have final_module_stem set."
|
82
|
+
if ref_schema.generation_name is None:
|
83
|
+
raise RuntimeError(f"Referenced schema '{ref_schema.name}' must have generation_name set.")
|
84
|
+
if ref_schema.final_module_stem is None:
|
85
|
+
raise RuntimeError(f"Referenced schema '{ref_schema.name}' must have final_module_stem set.")
|
87
86
|
|
88
87
|
class_name_for_ref = ref_schema.generation_name
|
89
88
|
module_name_for_ref = ref_schema.final_module_stem
|
@@ -113,12 +113,14 @@ class ObjectTypeResolver:
|
|
113
113
|
# If this named object is a component schema, ensure it's imported.
|
114
114
|
if schema.name and schema.name in self.all_schemas:
|
115
115
|
actual_schema_def = self.all_schemas[schema.name]
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
116
|
+
if actual_schema_def.generation_name is None:
|
117
|
+
raise RuntimeError(
|
118
|
+
f"Actual schema '{actual_schema_def.name}' for '{schema.name}' must have generation_name."
|
119
|
+
)
|
120
|
+
if actual_schema_def.final_module_stem is None:
|
121
|
+
raise RuntimeError(
|
122
|
+
f"Actual schema '{actual_schema_def.name}' for '{schema.name}' must have final_module_stem."
|
123
|
+
)
|
122
124
|
|
123
125
|
class_name_to_use = actual_schema_def.generation_name
|
124
126
|
module_stem_to_use = actual_schema_def.final_module_stem
|
@@ -162,14 +164,16 @@ class ObjectTypeResolver:
|
|
162
164
|
schema.name and schema.name in self.all_schemas
|
163
165
|
): # Named object, no properties, AND it's a known component
|
164
166
|
actual_schema_def = self.all_schemas[schema.name]
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
167
|
+
if actual_schema_def.generation_name is None:
|
168
|
+
raise RuntimeError(
|
169
|
+
f"Actual schema (no props) '{actual_schema_def.name}' "
|
170
|
+
f"for '{schema.name}' must have generation_name."
|
171
|
+
)
|
172
|
+
if actual_schema_def.final_module_stem is None:
|
173
|
+
raise RuntimeError(
|
174
|
+
f"Actual schema (no props) '{actual_schema_def.name}' "
|
175
|
+
f"for '{schema.name}' must have final_module_stem."
|
176
|
+
)
|
173
177
|
|
174
178
|
class_name_to_use = actual_schema_def.generation_name
|
175
179
|
module_stem_to_use = actual_schema_def.final_module_stem
|
@@ -172,10 +172,25 @@ class OpenAPISchemaResolver(SchemaTypeResolver):
|
|
172
172
|
|
173
173
|
def _resolve_string(self, schema: IRSchema, context: TypeContext, required: bool) -> ResolvedType:
|
174
174
|
"""Resolve string type, handling enums and formats."""
|
175
|
+
# Check if this is a properly processed enum (has generation_name)
|
175
176
|
if hasattr(schema, "enum") and schema.enum:
|
176
|
-
#
|
177
|
-
|
178
|
-
|
177
|
+
# Check if this enum was properly processed (has generation_name)
|
178
|
+
if hasattr(schema, "generation_name") and schema.generation_name:
|
179
|
+
# This is a properly processed enum, it should have been handled earlier
|
180
|
+
# by _resolve_named_schema. If we're here, it might be during initial processing.
|
181
|
+
# Return the enum type name
|
182
|
+
return ResolvedType(python_type=schema.generation_name, is_optional=not required)
|
183
|
+
else:
|
184
|
+
# This is an unprocessed inline enum - log warning with details
|
185
|
+
enum_values = schema.enum[:5] if len(schema.enum) > 5 else schema.enum
|
186
|
+
more = f" (and {len(schema.enum) - 5} more)" if len(schema.enum) > 5 else ""
|
187
|
+
context_info = f"name='{schema.name}'" if schema.name else "unnamed"
|
188
|
+
logger.warning(
|
189
|
+
f"Found inline enum in string schema that wasn't promoted: "
|
190
|
+
f"{context_info}, type={schema.type}, enum_values={enum_values}{more}. "
|
191
|
+
f"This will be treated as plain 'str' instead of a proper Enum type."
|
192
|
+
)
|
193
|
+
return ResolvedType(python_type="str", is_optional=not required)
|
179
194
|
|
180
195
|
# Handle string formats
|
181
196
|
format_type = getattr(schema, "format", None)
|
@@ -388,7 +388,7 @@ class EndpointResponseHandlerGenerator:
|
|
388
388
|
# All code paths should be covered by the match statement above
|
389
389
|
writer.write_line("# All paths above should return or raise - this should never execute")
|
390
390
|
context.add_import("typing", "NoReturn")
|
391
|
-
writer.write_line("
|
391
|
+
writer.write_line("raise RuntimeError('Unexpected code path') # pragma: no cover")
|
392
392
|
writer.write_line("") # Add a blank line for readability
|
393
393
|
|
394
394
|
def _write_strategy_based_return(
|
@@ -68,17 +68,9 @@ class EndpointMethodSignatureGenerator:
|
|
68
68
|
p = p_orig.copy() # Work with a copy
|
69
69
|
arg_str = f"{NameSanitizer.sanitize_method_name(p['name'])}: {p['type']}" # Ensure param name is sanitized
|
70
70
|
if not p.get("required", False):
|
71
|
-
#
|
72
|
-
#
|
73
|
-
|
74
|
-
default_val = p.get("default")
|
75
|
-
if default_val is None and not p.get("required", False): # Explicitly check for None for Optional types
|
76
|
-
arg_str += f" = None"
|
77
|
-
elif default_val is not None: # Only add if default is not None
|
78
|
-
if isinstance(default_val, str):
|
79
|
-
arg_str += f' = "{default_val}"'
|
80
|
-
else:
|
81
|
-
arg_str += f" = {default_val}"
|
71
|
+
# For optional parameters, always default to None to avoid type mismatches
|
72
|
+
# (e.g., enum-typed params with string defaults)
|
73
|
+
arg_str += " = None"
|
82
74
|
args.append(arg_str)
|
83
75
|
|
84
76
|
actual_return_type = return_type
|
@@ -24,7 +24,8 @@ class AliasGenerator:
|
|
24
24
|
all_schemas: Optional[Dict[str, IRSchema]],
|
25
25
|
):
|
26
26
|
# Pre-condition
|
27
|
-
|
27
|
+
if renderer is None:
|
28
|
+
raise ValueError("PythonConstructRenderer cannot be None")
|
28
29
|
self.renderer = renderer
|
29
30
|
self.all_schemas = all_schemas if all_schemas is not None else {}
|
30
31
|
self.type_service = UnifiedTypeService(self.all_schemas)
|
@@ -58,10 +59,14 @@ class AliasGenerator:
|
|
58
59
|
- ``TypeAlias`` is imported in the context if not already present.
|
59
60
|
"""
|
60
61
|
# Pre-conditions
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
if schema is None:
|
63
|
+
raise ValueError("Schema cannot be None for alias generation.")
|
64
|
+
if schema.name is None:
|
65
|
+
raise ValueError("Schema name must be present for alias generation.")
|
66
|
+
if not base_name:
|
67
|
+
raise ValueError("Base name cannot be empty for alias generation.")
|
68
|
+
if context is None:
|
69
|
+
raise ValueError("RenderContext cannot be None.")
|
65
70
|
|
66
71
|
alias_name = NameSanitizer.sanitize_class_name(base_name)
|
67
72
|
target_type = self.type_service.resolve_schema_type(schema, context, required=True, resolve_underlying=True)
|
@@ -77,13 +82,15 @@ class AliasGenerator:
|
|
77
82
|
)
|
78
83
|
|
79
84
|
# Post-condition
|
80
|
-
|
85
|
+
if not rendered_code.strip():
|
86
|
+
raise RuntimeError("Generated alias code cannot be empty.")
|
81
87
|
# PythonConstructRenderer is responsible for adding TypeAlias import
|
82
88
|
# We can check if it was added to context if 'TypeAlias' is in the rendered code
|
83
89
|
if "TypeAlias" in rendered_code:
|
84
|
-
|
90
|
+
if not (
|
85
91
|
"typing" in context.import_collector.imports
|
86
92
|
and "TypeAlias" in context.import_collector.imports["typing"]
|
87
|
-
)
|
93
|
+
):
|
94
|
+
raise RuntimeError("TypeAlias import was not added to context by renderer.")
|
88
95
|
|
89
96
|
return rendered_code
|
@@ -31,7 +31,8 @@ class DataclassGenerator:
|
|
31
31
|
Pre-conditions:
|
32
32
|
- ``renderer`` is not None.
|
33
33
|
"""
|
34
|
-
|
34
|
+
if renderer is None:
|
35
|
+
raise ValueError("PythonConstructRenderer cannot be None.")
|
35
36
|
self.renderer = renderer
|
36
37
|
self.all_schemas = all_schemas if all_schemas is not None else {}
|
37
38
|
self.type_service = UnifiedTypeService(self.all_schemas)
|
@@ -56,8 +57,10 @@ class DataclassGenerator:
|
|
56
57
|
- Returns a valid Python default value string
|
57
58
|
(e.g., "None", "field(default_factory=list)", "\"abc\"") or None.
|
58
59
|
"""
|
59
|
-
|
60
|
-
|
60
|
+
if ps is None:
|
61
|
+
raise ValueError("Property schema (ps) cannot be None.")
|
62
|
+
if context is None:
|
63
|
+
raise ValueError("RenderContext cannot be None.")
|
61
64
|
|
62
65
|
if ps.type == "array":
|
63
66
|
context.add_import("dataclasses", "field")
|
@@ -124,10 +127,14 @@ class DataclassGenerator:
|
|
124
127
|
- Returns a non-empty string containing valid Python code for a dataclass.
|
125
128
|
- ``@dataclass`` decorator is present, implying ``dataclasses.dataclass`` is imported.
|
126
129
|
"""
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
130
|
+
if schema is None:
|
131
|
+
raise ValueError("Schema cannot be None for dataclass generation.")
|
132
|
+
if schema.name is None:
|
133
|
+
raise ValueError("Schema name must be present for dataclass generation.")
|
134
|
+
if not base_name:
|
135
|
+
raise ValueError("Base name cannot be empty for dataclass generation.")
|
136
|
+
if context is None:
|
137
|
+
raise ValueError("RenderContext cannot be None.")
|
131
138
|
# Additional check for schema type might be too strict here, as ModelVisitor decides eligibility.
|
132
139
|
|
133
140
|
class_name = base_name
|
@@ -136,7 +143,8 @@ class DataclassGenerator:
|
|
136
143
|
|
137
144
|
if schema.type == "array" and schema.items:
|
138
145
|
field_name_for_array_content = "items"
|
139
|
-
|
146
|
+
if schema.items is None:
|
147
|
+
raise ValueError("Schema items must be present for array type dataclass field.")
|
140
148
|
|
141
149
|
list_item_py_type = self.type_service.resolve_schema_type(schema.items, context, required=True)
|
142
150
|
list_item_py_type = TypeFinalizer(context)._clean_type(list_item_py_type)
|
@@ -213,16 +221,18 @@ class DataclassGenerator:
|
|
213
221
|
field_mappings=field_mappings if field_mappings else None,
|
214
222
|
)
|
215
223
|
|
216
|
-
|
224
|
+
if not rendered_code.strip():
|
225
|
+
raise RuntimeError("Generated dataclass code cannot be empty.")
|
217
226
|
# PythonConstructRenderer adds the @dataclass decorator and import
|
218
|
-
|
219
|
-
|
227
|
+
if "@dataclass" not in rendered_code:
|
228
|
+
raise RuntimeError("Dataclass code missing @dataclass decorator.")
|
229
|
+
if not (
|
220
230
|
"dataclasses" in context.import_collector.imports
|
221
231
|
and "dataclass" in context.import_collector.imports["dataclasses"]
|
222
|
-
)
|
232
|
+
):
|
233
|
+
raise RuntimeError("dataclass import was not added to context by renderer.")
|
223
234
|
if "default_factory" in rendered_code: # Check for field import if factory is used
|
224
|
-
|
225
|
-
"dataclasses"
|
226
|
-
), "'field' import from dataclasses missing when default_factory is used."
|
235
|
+
if "field" not in context.import_collector.imports.get("dataclasses", set()):
|
236
|
+
raise RuntimeError("'field' import from dataclasses missing when default_factory is used.")
|
227
237
|
|
228
238
|
return rendered_code
|
@@ -19,7 +19,8 @@ class EnumGenerator:
|
|
19
19
|
|
20
20
|
def __init__(self, renderer: PythonConstructRenderer):
|
21
21
|
# Pre-condition
|
22
|
-
|
22
|
+
if renderer is None:
|
23
|
+
raise ValueError("PythonConstructRenderer cannot be None")
|
23
24
|
self.renderer = renderer
|
24
25
|
|
25
26
|
def _generate_member_name_for_string_enum(self, value: str) -> str:
|
@@ -32,7 +33,8 @@ class EnumGenerator:
|
|
32
33
|
Post-conditions:
|
33
34
|
- Returns a non-empty string that is a valid Python identifier, typically uppercase.
|
34
35
|
"""
|
35
|
-
|
36
|
+
if not isinstance(value, str):
|
37
|
+
raise TypeError("Input value must be a string.")
|
36
38
|
base_member_name = str(value).upper().replace("-", "_").replace(" ", "_")
|
37
39
|
sanitized_member_name = re.sub(r"[^A-Z0-9_]", "", base_member_name)
|
38
40
|
|
@@ -58,10 +60,11 @@ class EnumGenerator:
|
|
58
60
|
if not re.match(r"^[A-Z_]", sanitized_member_name.upper()):
|
59
61
|
sanitized_member_name = f"MEMBER_{sanitized_member_name}"
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
63
|
+
if not (sanitized_member_name and re.match(r"^[A-Z_][A-Z0-9_]*$", sanitized_member_name.upper())):
|
64
|
+
raise ValueError(
|
65
|
+
f"Generated string enum member name '{sanitized_member_name}' "
|
66
|
+
f"is not a valid Python identifier from value '{value}'."
|
67
|
+
)
|
65
68
|
return sanitized_member_name
|
66
69
|
|
67
70
|
def _generate_member_name_for_integer_enum(self, value: str | int, int_value_for_fallback: int) -> str:
|
@@ -75,8 +78,10 @@ class EnumGenerator:
|
|
75
78
|
Post-conditions:
|
76
79
|
- Returns a non-empty string that is a valid Python identifier, typically uppercase.
|
77
80
|
"""
|
78
|
-
|
79
|
-
|
81
|
+
if not isinstance(value, (str, int)):
|
82
|
+
raise TypeError("Input value for integer enum naming must be str or int.")
|
83
|
+
if not isinstance(int_value_for_fallback, int):
|
84
|
+
raise TypeError("Fallback integer value must be an int.")
|
80
85
|
|
81
86
|
name_basis = str(value) # Use string representation as basis for name
|
82
87
|
base_member_name = name_basis.upper().replace("-", "_").replace(" ", "_").replace(".", "_DOT_")
|
@@ -105,10 +110,11 @@ class EnumGenerator:
|
|
105
110
|
if not sanitized_member_name: # Should be impossible
|
106
111
|
sanitized_member_name = f"ENUM_MEMBER_UNKNOWN_{abs(int_value_for_fallback)}"
|
107
112
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
113
|
+
if not (sanitized_member_name and re.match(r"^[A-Z_][A-Z0-9_]*$", sanitized_member_name.upper())):
|
114
|
+
raise ValueError(
|
115
|
+
f"Generated integer enum member name '{sanitized_member_name}' "
|
116
|
+
f"is not a valid Python identifier from value '{value}'."
|
117
|
+
)
|
112
118
|
return sanitized_member_name
|
113
119
|
|
114
120
|
def generate(
|
@@ -138,12 +144,18 @@ class EnumGenerator:
|
|
138
144
|
- Returns a non-empty string containing valid Python code for an enum.
|
139
145
|
- ``Enum`` from the ``enum`` module is imported in the context.
|
140
146
|
"""
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
+
if schema is None:
|
148
|
+
raise ValueError("Schema cannot be None for enum generation.")
|
149
|
+
if schema.name is None:
|
150
|
+
raise ValueError("Schema name must be present for enum generation.")
|
151
|
+
if not base_name:
|
152
|
+
raise ValueError("Base name cannot be empty for enum generation.")
|
153
|
+
if context is None:
|
154
|
+
raise ValueError("RenderContext cannot be None.")
|
155
|
+
if not schema.enum:
|
156
|
+
raise ValueError("Schema must have enum values for enum generation.")
|
157
|
+
if schema.type not in ("string", "integer"):
|
158
|
+
raise ValueError("Enum schema type must be 'string' or 'integer'.")
|
147
159
|
|
148
160
|
enum_class_name = base_name # PythonConstructRenderer will sanitize this class name
|
149
161
|
base_type = "str" if schema.type == "string" else "int"
|
@@ -192,9 +204,9 @@ class EnumGenerator:
|
|
192
204
|
context=context,
|
193
205
|
)
|
194
206
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
207
|
+
if not rendered_code.strip():
|
208
|
+
raise RuntimeError("Generated enum code cannot be empty.")
|
209
|
+
if not ("enum" in context.import_collector.imports and "Enum" in context.import_collector.imports["enum"]):
|
210
|
+
raise RuntimeError("Enum import was not added to context by renderer.")
|
199
211
|
|
200
212
|
return rendered_code
|
@@ -114,7 +114,7 @@ class ModelVisitor(Visitor[IRSchema, str]):
|
|
114
114
|
logger.error(
|
115
115
|
f"ModelVisitor: Schema has no usable name (name or generation_name) for model generation: {schema}"
|
116
116
|
)
|
117
|
-
|
117
|
+
raise RuntimeError("Schema must have a name or generation_name for model code generation at this point.")
|
118
118
|
# return "" # Should not reach here if assertions are active
|
119
119
|
|
120
120
|
# --- Import Registration ---
|
@@ -144,13 +144,15 @@ class ModelVisitor(Visitor[IRSchema, str]):
|
|
144
144
|
# "alias, enum, or dataclass. No standalone model generated by ModelVisitor."
|
145
145
|
# )
|
146
146
|
# Post-condition: returns empty string if no specific generator called
|
147
|
-
|
147
|
+
if rendered_code:
|
148
|
+
raise RuntimeError("Rendered code should be empty if no generator was matched.")
|
148
149
|
return ""
|
149
150
|
|
150
151
|
# Post-condition: ensure some code was generated if a generator was called
|
151
|
-
|
152
|
-
|
153
|
-
|
152
|
+
if not rendered_code.strip() and (is_type_alias or is_enum or is_dataclass):
|
153
|
+
raise RuntimeError(
|
154
|
+
f"Code generation resulted in an empty string for schema '{schema.name}' which was matched as a model type."
|
155
|
+
)
|
154
156
|
|
155
157
|
return self.formatter.format(rendered_code)
|
156
158
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pyopenapi-gen
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.12.0
|
4
4
|
Summary: Modern, async-first Python client generator for OpenAPI specifications with advanced cycle detection and unified type resolution
|
5
5
|
Project-URL: Homepage, https://github.com/your-org/pyopenapi-gen
|
6
6
|
Project-URL: Documentation, https://github.com/your-org/pyopenapi-gen/blob/main/README.md
|
@@ -32,13 +32,13 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
32
32
|
Classifier: Topic :: System :: Networking
|
33
33
|
Classifier: Typing :: Typed
|
34
34
|
Requires-Python: <4.0.0,>=3.12
|
35
|
-
Requires-Dist: click
|
35
|
+
Requires-Dist: click>=8.0.0
|
36
36
|
Requires-Dist: dataclass-wizard>=0.22.0
|
37
37
|
Requires-Dist: httpx>=0.24.0
|
38
38
|
Requires-Dist: openapi-core>=0.19
|
39
39
|
Requires-Dist: openapi-spec-validator>=0.7
|
40
40
|
Requires-Dist: pyyaml>=6.0
|
41
|
-
Requires-Dist: typer
|
41
|
+
Requires-Dist: typer>=0.14.0
|
42
42
|
Provides-Extra: dev
|
43
43
|
Requires-Dist: bandit[toml]>=1.7.0; extra == 'dev'
|
44
44
|
Requires-Dist: black>=23.0; extra == 'dev'
|
@@ -1,4 +1,4 @@
|
|
1
|
-
pyopenapi_gen/__init__.py,sha256=
|
1
|
+
pyopenapi_gen/__init__.py,sha256=jU5GYbWa4MBDeDzOzwUHl_8eLGtotxRGLj42d2yskUo,3017
|
2
2
|
pyopenapi_gen/__main__.py,sha256=4-SCaCNhBd7rtyRK58uoDbdl93J0KhUeajP_b0CPpLE,110
|
3
3
|
pyopenapi_gen/cli.py,sha256=_ewksNDaA5st3TJJMZJWgCZdBGOQp__tkMVqr_6U3vs,2339
|
4
4
|
pyopenapi_gen/http_types.py,sha256=EMMYZBt8PNVZKPFu77TQija-JI-nOKyXvpiQP9-VSWE,467
|
@@ -10,7 +10,7 @@ pyopenapi_gen/context/import_collector.py,sha256=rnOgR5-GsHs_oS1iUVbOF3tagcH5nam
|
|
10
10
|
pyopenapi_gen/context/render_context.py,sha256=AS08ha9WVjgRUsM1LFPjMCgrsHbczHH7c60Z5PbojhY,30320
|
11
11
|
pyopenapi_gen/core/CLAUDE.md,sha256=bz48K-PSrhxCq5ScmiLiU9kfpVVzSWRKOA9RdKk_pbg,6482
|
12
12
|
pyopenapi_gen/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
pyopenapi_gen/core/exceptions.py,sha256=
|
13
|
+
pyopenapi_gen/core/exceptions.py,sha256=HYFiYdmzsZUl46vB8M3B6Vpp6m8iqjUcKDWdL4yEKHo,498
|
14
14
|
pyopenapi_gen/core/http_transport.py,sha256=77ZOTyl0_CLuDtSCOVDQoxHDQBnclJgz6f3Hs6cy7hY,9675
|
15
15
|
pyopenapi_gen/core/pagination.py,sha256=aeDOKo-Lu8mcSDqv0TlPXV9Ul-Nca76ZuKhQHKlsMUs,2301
|
16
16
|
pyopenapi_gen/core/postprocess_manager.py,sha256=ky27ijbq6zEo43aqe-odz9CR3vFD_3XHhQR35XgMZo0,6879
|
@@ -22,22 +22,22 @@ pyopenapi_gen/core/warning_collector.py,sha256=DYl9D7eZYs04mDU84KeonS-5-d0aM7hNq
|
|
22
22
|
pyopenapi_gen/core/auth/base.py,sha256=E2KUerA_mYv9D7xulUm-lenIxqZHqanjA4oRKpof2ZE,792
|
23
23
|
pyopenapi_gen/core/auth/plugins.py,sha256=bDWx4MTRFsCKp1i__BsQtZEvQPGU-NKI137-zoxmrgs,3465
|
24
24
|
pyopenapi_gen/core/loader/__init__.py,sha256=bt-MQ35fbq-f1YnCcopPg53TuXCI9_7wcMzQZoWVpjU,391
|
25
|
-
pyopenapi_gen/core/loader/loader.py,sha256=
|
25
|
+
pyopenapi_gen/core/loader/loader.py,sha256=fjRw6ZrG6hRS2RBWJY5IOv9e1ULc6tnxVGagqXhMmpk,6374
|
26
26
|
pyopenapi_gen/core/loader/operations/__init__.py,sha256=7se21D-BOy7Qw6C9auJ9v6D3NCuRiDpRlhqxGq11fJs,366
|
27
|
-
pyopenapi_gen/core/loader/operations/parser.py,sha256=
|
28
|
-
pyopenapi_gen/core/loader/operations/post_processor.py,sha256=
|
29
|
-
pyopenapi_gen/core/loader/operations/request_body.py,sha256=
|
27
|
+
pyopenapi_gen/core/loader/operations/parser.py,sha256=QHg3o8TRaReYofEEcBwuDuNxGDxZjkWXRDqOxKFiRtk,7046
|
28
|
+
pyopenapi_gen/core/loader/operations/post_processor.py,sha256=Rzb3GSiLyJk-0hTBZ6s6iWAj4KqE4Rfo3w-q2wm_R7w,2487
|
29
|
+
pyopenapi_gen/core/loader/operations/request_body.py,sha256=r-jscZEfOmqFA-E4i1Uj3S66rNEf1gDTlBiGUiA0P9k,3224
|
30
30
|
pyopenapi_gen/core/loader/parameters/__init__.py,sha256=p13oSibCRC5RCfsP6w7yD9MYs5TXcdI4WwPv7oGUYKk,284
|
31
|
-
pyopenapi_gen/core/loader/parameters/parser.py,sha256=
|
31
|
+
pyopenapi_gen/core/loader/parameters/parser.py,sha256=rOZWbykHQy7MH1gTHI61460mFG1nPKdU9A4hOsYAiIc,7505
|
32
32
|
pyopenapi_gen/core/loader/responses/__init__.py,sha256=6APWoH3IdNkgVmI0KsgZoZ6knDaG-S-pnUCa6gkzT8E,216
|
33
|
-
pyopenapi_gen/core/loader/responses/parser.py,sha256=
|
33
|
+
pyopenapi_gen/core/loader/responses/parser.py,sha256=F9otAn6ncQiY3C25Fq4mEJ5UFNgN83eujcX20Vd_nPU,4069
|
34
34
|
pyopenapi_gen/core/loader/schemas/__init__.py,sha256=rlhujYfw_IzWgzhVhYMJ3eIhE6C5Vi1Ylba-BHEVqOg,296
|
35
|
-
pyopenapi_gen/core/loader/schemas/extractor.py,sha256=
|
35
|
+
pyopenapi_gen/core/loader/schemas/extractor.py,sha256=n2jF_g8ZjA4GQiSKEWGAt35Hw7Ek93cnxFaS4Ld21Sc,9889
|
36
36
|
pyopenapi_gen/core/parsing/__init__.py,sha256=RJsIR6cHaNoI4tBcpMlAa0JsY64vsHb9sPxPg6rd8FQ,486
|
37
|
-
pyopenapi_gen/core/parsing/context.py,sha256=
|
37
|
+
pyopenapi_gen/core/parsing/context.py,sha256=8cM8mPItvDvJr8ZiukvdHBumlQl9hK1gUZL4BDpHaBk,8005
|
38
38
|
pyopenapi_gen/core/parsing/cycle_helpers.py,sha256=nG5ysNavL_6lpnHWFUZR9qraBxqOzuNfI6NgSEa8a5M,5939
|
39
|
-
pyopenapi_gen/core/parsing/schema_finalizer.py,sha256=
|
40
|
-
pyopenapi_gen/core/parsing/schema_parser.py,sha256=
|
39
|
+
pyopenapi_gen/core/parsing/schema_finalizer.py,sha256=qRTHUoVBQTgGmdfLuBuWxtWdj_SG71STGC3rn-tJvnA,6914
|
40
|
+
pyopenapi_gen/core/parsing/schema_parser.py,sha256=W0kCV0ucq6Wybr7x1wZl-nMSRXVWAqI4T57ASSabZsM,30557
|
41
41
|
pyopenapi_gen/core/parsing/unified_cycle_detection.py,sha256=3nplaCVh2dFwBPbmDc2kiU0SzTPXXktdQ5Rc0Q9Uu9s,10873
|
42
42
|
pyopenapi_gen/core/parsing/common/__init__.py,sha256=U3sHMO-l6S3Cm04CVOYmBCpqLEZvCylUI7yQfcTwxYU,27
|
43
43
|
pyopenapi_gen/core/parsing/common/type_parser.py,sha256=cK7xtxhoD43K2WjLP9TGip3As3akYeYW7L2XztXCecg,2562
|
@@ -52,10 +52,10 @@ pyopenapi_gen/core/parsing/common/ref_resolution/helpers/missing_ref.py,sha256=B
|
|
52
52
|
pyopenapi_gen/core/parsing/common/ref_resolution/helpers/new_schema.py,sha256=MTaxiyqoPEaNFqF7tQHii3IH41M05wPKt6lBQi1SCDU,1625
|
53
53
|
pyopenapi_gen/core/parsing/common/ref_resolution/helpers/stripped_suffix.py,sha256=ukKU64ozHINPiVlHE9YBDz1Nq2i6Xh64jhoWSEqbK5c,1774
|
54
54
|
pyopenapi_gen/core/parsing/keywords/__init__.py,sha256=enTLacWXGXLIOjSJ3j7KNUDzU27Kq3Ww79sFElz02cM,27
|
55
|
-
pyopenapi_gen/core/parsing/keywords/all_of_parser.py,sha256=
|
56
|
-
pyopenapi_gen/core/parsing/keywords/any_of_parser.py,sha256=
|
57
|
-
pyopenapi_gen/core/parsing/keywords/array_items_parser.py,sha256=
|
58
|
-
pyopenapi_gen/core/parsing/keywords/one_of_parser.py,sha256=
|
55
|
+
pyopenapi_gen/core/parsing/keywords/all_of_parser.py,sha256=ZH8rkxdDjldd226UGw5tv853ONXKaoqvWj5FfbgSoRY,3699
|
56
|
+
pyopenapi_gen/core/parsing/keywords/any_of_parser.py,sha256=HQ4iBBPgox48KIPPhM6VVZTXHIUVqpMoHkCbqcebXOw,2982
|
57
|
+
pyopenapi_gen/core/parsing/keywords/array_items_parser.py,sha256=nk9AFbx5ANLdDeGsxSTeYHhd8MqqrQwDRMzJ8FJmJxY,2723
|
58
|
+
pyopenapi_gen/core/parsing/keywords/one_of_parser.py,sha256=TUiT1dH1r8GEPExzlR3zdgnYpGxilH8yvnSWOvQmKmY,2727
|
59
59
|
pyopenapi_gen/core/parsing/keywords/properties_parser.py,sha256=bm248ApsNskFPQF4fXq7mT5oJf6FF9yAcdVLmK6el3E,4426
|
60
60
|
pyopenapi_gen/core/parsing/transformers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
61
61
|
pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py,sha256=hXuht-t0Syi5vPO8qn9GcjEpyDcZnfFVHVIlwfMa8L0,13461
|
@@ -70,11 +70,11 @@ pyopenapi_gen/emitters/CLAUDE.md,sha256=iZYEZq1a1h033rxuh97cMpsKUElv72ysvTm3-QQU
|
|
70
70
|
pyopenapi_gen/emitters/client_emitter.py,sha256=kmMVnG-wAOJm7TUm0xOQ5YnSJfYxz1SwtpiyoUCbcCA,1939
|
71
71
|
pyopenapi_gen/emitters/core_emitter.py,sha256=RcBsAYQ3ZKcWwtkzQmyHkL7VtDQjbIObFLXD9M_GdpI,8020
|
72
72
|
pyopenapi_gen/emitters/docs_emitter.py,sha256=aouKqhRdtVvYfGVsye_uqM80nONRy0SqN06cr1l3OgA,1137
|
73
|
-
pyopenapi_gen/emitters/endpoints_emitter.py,sha256=
|
73
|
+
pyopenapi_gen/emitters/endpoints_emitter.py,sha256=tzSLUzlZle2Lih_aZc4cJ-Y1ItjN5H_rABEWcDwECXA,9586
|
74
74
|
pyopenapi_gen/emitters/exceptions_emitter.py,sha256=qPTIPXDyqSUtpmBIp-V4ap1uMHUPmYziCSN62t7qcAE,1918
|
75
|
-
pyopenapi_gen/emitters/models_emitter.py,sha256=
|
75
|
+
pyopenapi_gen/emitters/models_emitter.py,sha256=Gd0z2Xoze1XkVnajkOptW90ti7197wQ15I7vIITnULM,22243
|
76
76
|
pyopenapi_gen/generator/CLAUDE.md,sha256=BS9KkmLvk2WD-Io-_apoWjGNeMU4q4LKy4UOxYF9WxM,10870
|
77
|
-
pyopenapi_gen/generator/client_generator.py,sha256=
|
77
|
+
pyopenapi_gen/generator/client_generator.py,sha256=MULKJY9SdRuYjt_R4XCXh3vJSW-92rsxOu-MVpIklho,29333
|
78
78
|
pyopenapi_gen/helpers/CLAUDE.md,sha256=GyIJ0grp4SkD3plAUzyycW4nTUZf9ewtvvsdAGkmIZw,10609
|
79
79
|
pyopenapi_gen/helpers/__init__.py,sha256=m4jSQ1sDH6CesIcqIl_kox4LcDFabGxBpSIWVwbHK0M,39
|
80
80
|
pyopenapi_gen/helpers/endpoint_utils.py,sha256=bkRu6YddIPQQD3rZLbB8L5WYzG-2Bd_JgMbxMUYY2wY,22198
|
@@ -85,8 +85,8 @@ pyopenapi_gen/helpers/type_resolution/__init__.py,sha256=TbaQZp7jvBiYgmuzuG8Wp56
|
|
85
85
|
pyopenapi_gen/helpers/type_resolution/array_resolver.py,sha256=dFppBtA6CxmiWAMR6rwGnQPv4AibL3nxtzw1LbeXVn4,2023
|
86
86
|
pyopenapi_gen/helpers/type_resolution/composition_resolver.py,sha256=wq6CRGxGgOKK470ln5Tpk9SzHtMuwB22TXHsRLtUFyw,3015
|
87
87
|
pyopenapi_gen/helpers/type_resolution/finalizer.py,sha256=_BcOBmOvadhBTUAvIc0Ak8FNxFw1uYL4rkKWtU68_m4,4332
|
88
|
-
pyopenapi_gen/helpers/type_resolution/named_resolver.py,sha256=
|
89
|
-
pyopenapi_gen/helpers/type_resolution/object_resolver.py,sha256=
|
88
|
+
pyopenapi_gen/helpers/type_resolution/named_resolver.py,sha256=hXu6Gao92EVV0jyHRqf9cIhoAFn3suuEw5ye9nRTFks,9423
|
89
|
+
pyopenapi_gen/helpers/type_resolution/object_resolver.py,sha256=Jm4m5QgzWU-4joPqfUSC0Y-za3Yx0ROdJjyv5J8VNk0,12557
|
90
90
|
pyopenapi_gen/helpers/type_resolution/primitive_resolver.py,sha256=qTshZaye5ohibVfcJYCzh4v3CAshguMGWPt2FgvQeNM,1960
|
91
91
|
pyopenapi_gen/helpers/type_resolution/resolver.py,sha256=qQY6wAitBluA-tofiyJ67Gxx8ol1W528zDWd9izYN5s,1982
|
92
92
|
pyopenapi_gen/types/CLAUDE.md,sha256=xRYlHdLhw3QGIfIlWqPt9pewRs736H1YCzwmslKtzZc,4255
|
@@ -97,7 +97,7 @@ pyopenapi_gen/types/contracts/types.py,sha256=-Qvbx3N_14AaN-1BeyocrvsjiwXPn_eWQh
|
|
97
97
|
pyopenapi_gen/types/resolvers/__init__.py,sha256=_5kA49RvyOTyXgt0GbbOfHJcdQw2zHxvU9af8GGyNWc,295
|
98
98
|
pyopenapi_gen/types/resolvers/reference_resolver.py,sha256=qnaZeLmtyh4_NBMcKib58s6o5ycUJaattYt8F38_qIo,2053
|
99
99
|
pyopenapi_gen/types/resolvers/response_resolver.py,sha256=Kb1a2803lyoukoZy06ztPBlUw-A1lHiZ6NlJmsixxA8,6500
|
100
|
-
pyopenapi_gen/types/resolvers/schema_resolver.py,sha256=
|
100
|
+
pyopenapi_gen/types/resolvers/schema_resolver.py,sha256=PsSF-DE8-GDXmvbAJz-tHlTCR-1UATGhT4dHf6kUDQQ,17988
|
101
101
|
pyopenapi_gen/types/services/__init__.py,sha256=inSUKmY_Vnuym6tC-AhvjCTj16GbkfxCGLESRr_uQPE,123
|
102
102
|
pyopenapi_gen/types/services/type_service.py,sha256=-LQj7oSx1mxb10Zi6DpawS8uyoUrUbnYhmUA0GuKZTc,4402
|
103
103
|
pyopenapi_gen/types/strategies/__init__.py,sha256=bju8_KEPNIow1-woMO-zJCgK_E0M6JnFq0NFsK1R4Ss,171
|
@@ -113,19 +113,19 @@ pyopenapi_gen/visit/endpoint/generators/__init__.py,sha256=-X-GYnJZ9twiEBr_U0obW
|
|
113
113
|
pyopenapi_gen/visit/endpoint/generators/docstring_generator.py,sha256=U02qvuYtFElQNEtOHuTNXFl2NxUriIiuZMkmUsapOg4,5913
|
114
114
|
pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py,sha256=wUJ4_gaA1gRrFCHYFCObBIankxGQu0MNqiOSoZOZmoA,4352
|
115
115
|
pyopenapi_gen/visit/endpoint/generators/request_generator.py,sha256=OnkrkRk39_BrK9ZDvyWqJYLz1mocD2zY7j70yIpS0J4,5374
|
116
|
-
pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py,sha256=
|
117
|
-
pyopenapi_gen/visit/endpoint/generators/signature_generator.py,sha256=
|
116
|
+
pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py,sha256=xbh4GQcA-fA98mOZxVSylLbZADse-7RLcLIbjnVAmlE,22834
|
117
|
+
pyopenapi_gen/visit/endpoint/generators/signature_generator.py,sha256=CYtfsPMlTZN95g2WxrdnTloGx2RmqeNQRiyP9fOkUEQ,3892
|
118
118
|
pyopenapi_gen/visit/endpoint/generators/url_args_generator.py,sha256=EsmNuVSkGfUqrmV7-1GiLPzdN86V5UqLfs1SVY0jsf0,9590
|
119
119
|
pyopenapi_gen/visit/endpoint/processors/__init__.py,sha256=_6RqpOdDuDheArqDBi3ykhsaetACny88WUuuAJvr_ME,29
|
120
120
|
pyopenapi_gen/visit/endpoint/processors/import_analyzer.py,sha256=tNmhgWwt-CjLE774TC8sPVH1-yaTKKm6JmfgBT2-iRk,3386
|
121
121
|
pyopenapi_gen/visit/endpoint/processors/parameter_processor.py,sha256=BygP8yzSTrxfvNpEQIHERjd2NpEXDiwpCtmMseJKRq0,7700
|
122
122
|
pyopenapi_gen/visit/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
123
|
-
pyopenapi_gen/visit/model/alias_generator.py,sha256=
|
124
|
-
pyopenapi_gen/visit/model/dataclass_generator.py,sha256=
|
125
|
-
pyopenapi_gen/visit/model/enum_generator.py,sha256=
|
126
|
-
pyopenapi_gen/visit/model/model_visitor.py,sha256=
|
127
|
-
pyopenapi_gen-0.
|
128
|
-
pyopenapi_gen-0.
|
129
|
-
pyopenapi_gen-0.
|
130
|
-
pyopenapi_gen-0.
|
131
|
-
pyopenapi_gen-0.
|
123
|
+
pyopenapi_gen/visit/model/alias_generator.py,sha256=TGL3AMq_PkBWFWeeXbNnA8hgO9hvp0flwBA00Gr6S3o,3744
|
124
|
+
pyopenapi_gen/visit/model/dataclass_generator.py,sha256=nyTvBph6rtbJlCwTiDW_Y2UJmLLiA6D2QJUpA2xE0m8,10289
|
125
|
+
pyopenapi_gen/visit/model/enum_generator.py,sha256=AXqKUFuWUUjUF_6_HqBKY8vB5GYu35Pb2C2WPFrOw1k,10061
|
126
|
+
pyopenapi_gen/visit/model/model_visitor.py,sha256=4kAQSWsI4XumVYB3aAE7Ts_31hGfDlbytRalxyMFV3g,9510
|
127
|
+
pyopenapi_gen-0.12.0.dist-info/METADATA,sha256=do9Qt8fZ_bOvewCqxXYsCYbNE0qs7BtvC5Xe-AhBnBQ,14025
|
128
|
+
pyopenapi_gen-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
129
|
+
pyopenapi_gen-0.12.0.dist-info/entry_points.txt,sha256=gxSlNiwom50T3OEZnlocA6qRjGdV0bn6hN_Xr-Ub5wA,56
|
130
|
+
pyopenapi_gen-0.12.0.dist-info/licenses/LICENSE,sha256=UFAyTWKa4w10-QerlJaHJeep7G2gcwpf-JmvI2dS2Gc,1088
|
131
|
+
pyopenapi_gen-0.12.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|