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
pyopenapi_gen/__init__.py
CHANGED
pyopenapi_gen/core/exceptions.py
CHANGED
@@ -1,12 +1,7 @@
|
|
1
|
-
from typing import Optional
|
2
|
-
|
3
|
-
import httpx
|
4
|
-
|
5
|
-
|
6
1
|
class HTTPError(Exception):
|
7
2
|
"""Base HTTP error with status code and message."""
|
8
3
|
|
9
|
-
def __init__(self, status_code: int, message: str, response:
|
4
|
+
def __init__(self, status_code: int, message: str, response: object | None = None) -> None:
|
10
5
|
super().__init__(f"{status_code}: {message}")
|
11
6
|
self.status_code = status_code
|
12
7
|
self.message = message
|
@@ -87,8 +87,22 @@ class SpecLoader:
|
|
87
87
|
validate_spec(cast(Mapping[Hashable, Any], self.spec))
|
88
88
|
except Exception as e:
|
89
89
|
warning_msg = f"OpenAPI spec validation error: {e}"
|
90
|
+
# Always collect the message
|
90
91
|
warnings_list.append(warning_msg)
|
91
|
-
|
92
|
+
|
93
|
+
# Heuristic: if this error originates from jsonschema or
|
94
|
+
# openapi_spec_validator, prefer logging over global warnings
|
95
|
+
# to avoid noisy test output while still surfacing the issue.
|
96
|
+
origin_module = getattr(e.__class__, "__module__", "")
|
97
|
+
if (
|
98
|
+
isinstance(e, RecursionError)
|
99
|
+
or origin_module.startswith("jsonschema")
|
100
|
+
or origin_module.startswith("openapi_spec_validator")
|
101
|
+
):
|
102
|
+
logger.warning(warning_msg)
|
103
|
+
else:
|
104
|
+
# Preserve explicit warning behavior for unexpected failures
|
105
|
+
warnings.warn(warning_msg, UserWarning)
|
92
106
|
|
93
107
|
return warnings_list
|
94
108
|
|
@@ -134,8 +148,10 @@ class SpecLoader:
|
|
134
148
|
)
|
135
149
|
|
136
150
|
# Post-condition check
|
137
|
-
|
138
|
-
|
151
|
+
if ir_spec.schemas != schemas_dict:
|
152
|
+
raise RuntimeError("Schemas mismatch in IRSpec")
|
153
|
+
if ir_spec.operations != operations:
|
154
|
+
raise RuntimeError("Operations mismatch in IRSpec")
|
139
155
|
|
140
156
|
return ir_spec
|
141
157
|
|
@@ -39,11 +39,16 @@ def parse_operations(
|
|
39
39
|
- All operations have correct path, method, parameters, responses, etc.
|
40
40
|
- All referenced schemas are properly stored in context
|
41
41
|
"""
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
42
|
+
if not isinstance(paths, Mapping):
|
43
|
+
raise TypeError("paths must be a Mapping")
|
44
|
+
if not isinstance(raw_parameters, Mapping):
|
45
|
+
raise TypeError("raw_parameters must be a Mapping")
|
46
|
+
if not isinstance(raw_responses, Mapping):
|
47
|
+
raise TypeError("raw_responses must be a Mapping")
|
48
|
+
if not isinstance(raw_request_bodies, Mapping):
|
49
|
+
raise TypeError("raw_request_bodies must be a Mapping")
|
50
|
+
if not isinstance(context, ParsingContext):
|
51
|
+
raise TypeError("context must be a ParsingContext")
|
47
52
|
|
48
53
|
ops: List[IROperation] = []
|
49
54
|
|
@@ -150,6 +155,7 @@ def parse_operations(
|
|
150
155
|
ops.append(op)
|
151
156
|
|
152
157
|
# Post-condition check
|
153
|
-
|
158
|
+
if not all(isinstance(op, IROperation) for op in ops):
|
159
|
+
raise TypeError("All items must be IROperation objects")
|
154
160
|
|
155
161
|
return ops
|
@@ -24,8 +24,10 @@ def post_process_operation(op: IROperation, context: ParsingContext) -> None:
|
|
24
24
|
Postconditions:
|
25
25
|
- All request body and response schemas are properly named and registered
|
26
26
|
"""
|
27
|
-
|
28
|
-
|
27
|
+
if not isinstance(op, IROperation):
|
28
|
+
raise TypeError("op must be an IROperation")
|
29
|
+
if not isinstance(context, ParsingContext):
|
30
|
+
raise TypeError("context must be a ParsingContext")
|
29
31
|
|
30
32
|
# Handle request body schemas
|
31
33
|
if op.request_body:
|
@@ -33,10 +33,14 @@ def parse_request_body(
|
|
33
33
|
- Returns a properly populated IRRequestBody or None if invalid
|
34
34
|
- All content media types are properly mapped to schemas
|
35
35
|
"""
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
if not isinstance(rb_node, Mapping):
|
37
|
+
raise TypeError("rb_node must be a Mapping")
|
38
|
+
if not isinstance(raw_request_bodies, Mapping):
|
39
|
+
raise TypeError("raw_request_bodies must be a Mapping")
|
40
|
+
if not isinstance(context, ParsingContext):
|
41
|
+
raise TypeError("context must be a ParsingContext")
|
42
|
+
if not operation_id:
|
43
|
+
raise ValueError("operation_id must be provided")
|
40
44
|
|
41
45
|
# Handle $ref in request body
|
42
46
|
if (
|
@@ -80,6 +84,7 @@ def parse_request_body(
|
|
80
84
|
request_body = IRRequestBody(required=required_flag, content=content_map, description=desc)
|
81
85
|
|
82
86
|
# Post-condition check
|
83
|
-
|
87
|
+
if request_body.content != content_map:
|
88
|
+
raise RuntimeError("Request body content mismatch")
|
84
89
|
|
85
90
|
return request_body
|
@@ -27,8 +27,10 @@ def resolve_parameter_node_if_ref(param_node_data: Mapping[str, Any], context: P
|
|
27
27
|
- Returns the resolved parameter node or the original if not a ref
|
28
28
|
- If a reference, the parameter is looked up in components
|
29
29
|
"""
|
30
|
-
|
31
|
-
|
30
|
+
if not isinstance(param_node_data, Mapping):
|
31
|
+
raise TypeError("param_node_data must be a Mapping")
|
32
|
+
if not isinstance(context, ParsingContext):
|
33
|
+
raise TypeError("context must be a ParsingContext")
|
32
34
|
|
33
35
|
if "$ref" in param_node_data and isinstance(param_node_data.get("$ref"), str):
|
34
36
|
ref_path = param_node_data["$ref"]
|
@@ -62,9 +64,12 @@ def parse_parameter(
|
|
62
64
|
- Returns a properly populated IRParameter
|
63
65
|
- Complex parameter schemas are given appropriate names
|
64
66
|
"""
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
if not isinstance(node, Mapping):
|
68
|
+
raise TypeError("node must be a Mapping")
|
69
|
+
if "name" not in node:
|
70
|
+
raise ValueError("Parameter node must have a name")
|
71
|
+
if not isinstance(context, ParsingContext):
|
72
|
+
raise TypeError("context must be a ParsingContext")
|
68
73
|
|
69
74
|
sch = node.get("schema")
|
70
75
|
param_name = node["name"]
|
@@ -79,6 +84,29 @@ def parse_parameter(
|
|
79
84
|
base_param_promo_name = f"{operation_id_for_promo}Param" if operation_id_for_promo else ""
|
80
85
|
name_for_inline_param_schema = f"{base_param_promo_name}{NameSanitizer.sanitize_class_name(param_name)}"
|
81
86
|
|
87
|
+
# General rule: if a parameter is defined inline but a components parameter exists with the
|
88
|
+
# same name and location, prefer the components schema (often richer: arrays/enums/refs).
|
89
|
+
try:
|
90
|
+
if isinstance(context, ParsingContext):
|
91
|
+
components_params = context.raw_spec_components.get("parameters", {})
|
92
|
+
if isinstance(components_params, Mapping):
|
93
|
+
for comp_key, comp_param in components_params.items():
|
94
|
+
if not isinstance(comp_param, Mapping):
|
95
|
+
continue
|
96
|
+
if comp_param.get("name") == param_name and comp_param.get("in") == node.get("in"):
|
97
|
+
comp_schema = comp_param.get("schema")
|
98
|
+
if isinstance(comp_schema, Mapping):
|
99
|
+
# Prefer component schema if inline is missing or clearly less specific
|
100
|
+
inline_is_specific = isinstance(sch, Mapping) and (
|
101
|
+
sch.get("type") in {"array", "object"} or "$ref" in sch or "enum" in sch
|
102
|
+
)
|
103
|
+
if not inline_is_specific:
|
104
|
+
sch = comp_schema
|
105
|
+
break
|
106
|
+
except Exception:
|
107
|
+
# Be conservative on any unexpected structure
|
108
|
+
pass
|
109
|
+
|
82
110
|
# For parameters, we want to avoid creating complex schemas for simple enum arrays
|
83
111
|
# Check if this is a simple enum array and handle it specially
|
84
112
|
if (
|
@@ -91,12 +119,38 @@ def parse_parameter(
|
|
91
119
|
and "enum" in sch["items"]
|
92
120
|
and "$ref" not in sch["items"]
|
93
121
|
):
|
94
|
-
# This is an array of string enums -
|
95
|
-
#
|
122
|
+
# This is an array of string enums - create a proper enum schema for the items
|
123
|
+
# Give it a name based on the parameter and operation
|
124
|
+
enum_name = None
|
125
|
+
if operation_id_for_promo and param_name:
|
126
|
+
# Create a name for this inline enum when we have operation context
|
127
|
+
enum_name = f"{operation_id_for_promo}Param{NameSanitizer.sanitize_class_name(param_name)}Item"
|
128
|
+
elif param_name:
|
129
|
+
# For component parameters without operation context, use just the parameter name
|
130
|
+
enum_name = f"{NameSanitizer.sanitize_class_name(param_name)}Item"
|
131
|
+
|
132
|
+
if enum_name:
|
133
|
+
items_schema = IRSchema(
|
134
|
+
name=enum_name,
|
135
|
+
type="string",
|
136
|
+
enum=sch["items"]["enum"],
|
137
|
+
generation_name=enum_name, # Mark it as promoted
|
138
|
+
)
|
139
|
+
logger.debug(
|
140
|
+
f"Created enum schema '{enum_name}' for array parameter '{param_name}' with values {sch['items']['enum'][:3]}..."
|
141
|
+
)
|
142
|
+
else:
|
143
|
+
# Fallback if we don't have enough info to create a good name
|
144
|
+
items_schema = IRSchema(name=None, type="string", enum=sch["items"]["enum"])
|
145
|
+
logger.warning(
|
146
|
+
f"Could not create proper enum name for parameter array items with values {sch['items']['enum'][:3]}... "
|
147
|
+
f"This will generate a warning during type resolution."
|
148
|
+
)
|
149
|
+
|
96
150
|
schema_ir = IRSchema(
|
97
151
|
name=None,
|
98
152
|
type="array",
|
99
|
-
items=
|
153
|
+
items=items_schema,
|
100
154
|
description=sch.get("description"),
|
101
155
|
)
|
102
156
|
else:
|
@@ -115,7 +169,9 @@ def parse_parameter(
|
|
115
169
|
)
|
116
170
|
|
117
171
|
# Post-condition check
|
118
|
-
|
119
|
-
|
172
|
+
if param.name != node["name"]:
|
173
|
+
raise RuntimeError("Parameter name mismatch")
|
174
|
+
if param.schema is None:
|
175
|
+
raise RuntimeError("Parameter schema must be created")
|
120
176
|
|
121
177
|
return param
|
@@ -34,10 +34,14 @@ def parse_response(
|
|
34
34
|
- All content media types are properly mapped to schemas
|
35
35
|
- Stream flags are correctly set based on media types
|
36
36
|
"""
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
if not isinstance(code, str):
|
38
|
+
raise TypeError("code must be a string")
|
39
|
+
if not isinstance(node, Mapping):
|
40
|
+
raise TypeError("node must be a Mapping")
|
41
|
+
if not isinstance(context, ParsingContext):
|
42
|
+
raise TypeError("context must be a ParsingContext")
|
43
|
+
if not operation_id_for_promo:
|
44
|
+
raise ValueError("operation_id_for_promo must be provided")
|
41
45
|
|
42
46
|
content: Dict[str, IRSchema] = {}
|
43
47
|
STREAM_FORMATS = {
|
@@ -97,8 +101,11 @@ def parse_response(
|
|
97
101
|
)
|
98
102
|
|
99
103
|
# Post-condition checks
|
100
|
-
|
101
|
-
|
102
|
-
|
104
|
+
if response.status_code != code:
|
105
|
+
raise RuntimeError("Response status code mismatch")
|
106
|
+
if response.content != content:
|
107
|
+
raise RuntimeError("Response content mismatch")
|
108
|
+
if response.stream != stream_flag:
|
109
|
+
raise RuntimeError("Response stream flag mismatch")
|
103
110
|
|
104
111
|
return response
|
@@ -28,8 +28,10 @@ def build_schemas(raw_schemas: Dict[str, Mapping[str, Any]], raw_components: Map
|
|
28
28
|
- A ParsingContext is returned with all schemas parsed
|
29
29
|
- All schemas in raw_schemas are populated in context.parsed_schemas
|
30
30
|
"""
|
31
|
-
|
32
|
-
|
31
|
+
if not isinstance(raw_schemas, dict):
|
32
|
+
raise TypeError("raw_schemas must be a dict")
|
33
|
+
if not isinstance(raw_components, Mapping):
|
34
|
+
raise TypeError("raw_components must be a Mapping")
|
33
35
|
|
34
36
|
context = ParsingContext(raw_spec_schemas=raw_schemas, raw_spec_components=raw_components)
|
35
37
|
|
@@ -39,7 +41,8 @@ def build_schemas(raw_schemas: Dict[str, Mapping[str, Any]], raw_components: Map
|
|
39
41
|
_parse_schema(n, nd, context, allow_self_reference=True)
|
40
42
|
|
41
43
|
# Post-condition check
|
42
|
-
|
44
|
+
if not all(n in context.parsed_schemas for n in raw_schemas):
|
45
|
+
raise RuntimeError("Not all schemas were parsed")
|
43
46
|
|
44
47
|
return context
|
45
48
|
|
@@ -55,8 +58,10 @@ def extract_inline_array_items(schemas: Dict[str, IRSchema]) -> Dict[str, IRSche
|
|
55
58
|
- All array item schemas have proper names
|
56
59
|
- No duplicate schema names are created
|
57
60
|
"""
|
58
|
-
|
59
|
-
|
61
|
+
if not isinstance(schemas, dict):
|
62
|
+
raise TypeError("schemas must be a dict")
|
63
|
+
if not all(isinstance(s, IRSchema) for s in schemas.values()):
|
64
|
+
raise TypeError("all values must be IRSchema objects")
|
60
65
|
|
61
66
|
# Store original schema count for post-condition validation
|
62
67
|
original_schema_count = len(schemas)
|
@@ -119,8 +124,10 @@ def extract_inline_array_items(schemas: Dict[str, IRSchema]) -> Dict[str, IRSche
|
|
119
124
|
schemas.update(new_item_schemas)
|
120
125
|
|
121
126
|
# Post-condition checks
|
122
|
-
|
123
|
-
|
127
|
+
if len(schemas) < original_schema_count:
|
128
|
+
raise RuntimeError("Schemas count should not decrease")
|
129
|
+
if not original_schemas.issubset(set(schemas.keys())):
|
130
|
+
raise RuntimeError("Original schemas should still be present")
|
124
131
|
|
125
132
|
return schemas
|
126
133
|
|
@@ -128,6 +135,8 @@ def extract_inline_array_items(schemas: Dict[str, IRSchema]) -> Dict[str, IRSche
|
|
128
135
|
def extract_inline_enums(schemas: Dict[str, IRSchema]) -> Dict[str, IRSchema]:
|
129
136
|
"""Extract inline property enums as unique schemas and update property references.
|
130
137
|
|
138
|
+
Also ensures top-level enum schemas are properly marked for generation.
|
139
|
+
|
131
140
|
Contracts:
|
132
141
|
Preconditions:
|
133
142
|
- schemas is a dict of IRSchema objects
|
@@ -136,9 +145,12 @@ def extract_inline_enums(schemas: Dict[str, IRSchema]) -> Dict[str, IRSchema]:
|
|
136
145
|
- All property schemas with enums have proper names
|
137
146
|
- All array item schemas have proper names
|
138
147
|
- No duplicate schema names are created
|
148
|
+
- Top-level enum schemas have generation_name set
|
139
149
|
"""
|
140
|
-
|
141
|
-
|
150
|
+
if not isinstance(schemas, dict):
|
151
|
+
raise TypeError("schemas must be a dict")
|
152
|
+
if not all(isinstance(s, IRSchema) for s in schemas.values()):
|
153
|
+
raise TypeError("all values must be IRSchema objects")
|
142
154
|
|
143
155
|
# Store original schema count for post-condition validation
|
144
156
|
original_schema_count = len(schemas)
|
@@ -149,6 +161,22 @@ def extract_inline_enums(schemas: Dict[str, IRSchema]) -> Dict[str, IRSchema]:
|
|
149
161
|
|
150
162
|
new_enums = {}
|
151
163
|
for schema_name, schema in list(schemas.items()):
|
164
|
+
# Handle top-level enum schemas (those defined directly in components/schemas)
|
165
|
+
# These are already enums but need generation_name set
|
166
|
+
if schema.enum and schema.type in ["string", "integer", "number"]:
|
167
|
+
# This is a top-level enum schema
|
168
|
+
# Ensure it has generation_name set (will be properly set by emitter later,
|
169
|
+
# but we can set it here to avoid the warning)
|
170
|
+
if not hasattr(schema, "generation_name") or not schema.generation_name:
|
171
|
+
schema.generation_name = schema.name
|
172
|
+
logger.info(
|
173
|
+
f"Set generation_name for top-level enum schema: {schema_name} with values {schema.enum[:3]}..."
|
174
|
+
)
|
175
|
+
# Mark this as a properly processed enum by ensuring generation_name is set
|
176
|
+
# This serves as the marker that this enum was properly processed
|
177
|
+
logger.debug(f"Marked top-level enum schema: {schema_name}")
|
178
|
+
|
179
|
+
# Extract inline enums from properties
|
152
180
|
for prop_name, prop_schema in list(schema.properties.items()):
|
153
181
|
if prop_schema.enum and not prop_schema.name:
|
154
182
|
enum_name = (
|
@@ -167,6 +195,7 @@ def extract_inline_enums(schemas: Dict[str, IRSchema]) -> Dict[str, IRSchema]:
|
|
167
195
|
enum=copy.deepcopy(prop_schema.enum),
|
168
196
|
description=prop_schema.description or f"Enum for {schema_name}.{prop_name}",
|
169
197
|
)
|
198
|
+
enum_schema.generation_name = enum_name # Set generation_name for extracted enums
|
170
199
|
new_enums[enum_name] = enum_schema
|
171
200
|
|
172
201
|
# Update the original property to reference the extracted enum
|
@@ -178,7 +207,9 @@ def extract_inline_enums(schemas: Dict[str, IRSchema]) -> Dict[str, IRSchema]:
|
|
178
207
|
schemas.update(new_enums)
|
179
208
|
|
180
209
|
# Post-condition checks
|
181
|
-
|
182
|
-
|
210
|
+
if len(schemas) < original_schema_count:
|
211
|
+
raise RuntimeError("Schemas count should not decrease")
|
212
|
+
if not original_schemas.issubset(set(schemas.keys())):
|
213
|
+
raise RuntimeError("Original schemas should still be present")
|
183
214
|
|
184
215
|
return schemas
|
@@ -169,7 +169,8 @@ class ParsingContext:
|
|
169
169
|
Postconditions:
|
170
170
|
- Returns True if the schema exists in parsed_schemas, False otherwise
|
171
171
|
"""
|
172
|
-
|
172
|
+
if not isinstance(schema_name, str):
|
173
|
+
raise TypeError("schema_name must be a string")
|
173
174
|
return schema_name in self.parsed_schemas
|
174
175
|
|
175
176
|
def get_parsed_schema(self, schema_name: str) -> Optional["IRSchema"]:
|
@@ -181,5 +182,6 @@ class ParsingContext:
|
|
181
182
|
Postconditions:
|
182
183
|
- Returns the IRSchema if it exists, None otherwise
|
183
184
|
"""
|
184
|
-
|
185
|
+
if not isinstance(schema_name, str):
|
186
|
+
raise TypeError("schema_name must be a string")
|
185
187
|
return self.parsed_schemas.get(schema_name)
|
@@ -43,10 +43,14 @@ def _process_all_of(
|
|
43
43
|
- parsed_all_of_components: List of IRSchema for each item in 'allOf' (empty if 'allOf' not present).
|
44
44
|
"""
|
45
45
|
# Pre-conditions
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
if not (isinstance(node, Mapping) and node):
|
47
|
+
raise TypeError("node must be a non-empty Mapping")
|
48
|
+
if not isinstance(context, ParsingContext):
|
49
|
+
raise TypeError("context must be a ParsingContext instance")
|
50
|
+
if not callable(_parse_schema_func):
|
51
|
+
raise TypeError("_parse_schema_func must be callable")
|
52
|
+
if not (isinstance(max_depth, int) and max_depth >= 0):
|
53
|
+
raise ValueError("max_depth must be a non-negative integer")
|
50
54
|
|
51
55
|
parsed_all_of_components: List[IRSchema] = []
|
52
56
|
merged_required: Set[str] = set(node.get("required", []))
|
@@ -38,11 +38,16 @@ def _parse_any_of_schemas(
|
|
38
38
|
- is_nullable: True if a null type was present.
|
39
39
|
- effective_schema_type: Potential schema_type if list becomes empty/None (currently always None).
|
40
40
|
"""
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
if not isinstance(any_of_nodes, list):
|
42
|
+
raise TypeError("any_of_nodes must be a list")
|
43
|
+
if not all(isinstance(n, Mapping) for n in any_of_nodes):
|
44
|
+
raise TypeError("all items in any_of_nodes must be Mappings")
|
45
|
+
if not isinstance(context, ParsingContext):
|
46
|
+
raise TypeError("context must be a ParsingContext instance")
|
47
|
+
if not max_depth >= 0:
|
48
|
+
raise ValueError("max_depth must be non-negative")
|
49
|
+
if not callable(parse_fn):
|
50
|
+
raise TypeError("parse_fn must be a callable")
|
46
51
|
|
47
52
|
parsed_schemas_list: List[IRSchema] = [] # Renamed to avoid confusion with module name
|
48
53
|
is_nullable_from_any_of = False
|
@@ -51,9 +51,12 @@ def _parse_array_items_schema(
|
|
51
51
|
"""
|
52
52
|
# Pre-conditions
|
53
53
|
# items_node_data is checked later, as it can be non-Mapping to return None
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
if not isinstance(context, ParsingContext):
|
55
|
+
raise TypeError("context must be a ParsingContext instance")
|
56
|
+
if not callable(parse_fn):
|
57
|
+
raise TypeError("parse_fn must be callable")
|
58
|
+
if not (isinstance(max_depth, int) and max_depth >= 0):
|
59
|
+
raise ValueError("max_depth must be a non-negative integer")
|
57
60
|
|
58
61
|
item_name_for_parse = f"{parent_schema_name}Item" if parent_schema_name else None
|
59
62
|
if (
|
@@ -35,11 +35,16 @@ def _parse_one_of_schemas(
|
|
35
35
|
- is_nullable: True if a null type was present.
|
36
36
|
- effective_schema_type: Potential schema_type if list becomes empty/None (currently always None).
|
37
37
|
"""
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
if not isinstance(one_of_nodes, list):
|
39
|
+
raise TypeError("one_of_nodes must be a list")
|
40
|
+
if not all(isinstance(n, Mapping) for n in one_of_nodes):
|
41
|
+
raise TypeError("all items in one_of_nodes must be Mappings")
|
42
|
+
if not isinstance(context, ParsingContext):
|
43
|
+
raise TypeError("context must be a ParsingContext instance")
|
44
|
+
if not max_depth >= 0:
|
45
|
+
raise ValueError("max_depth must be non-negative")
|
46
|
+
if not callable(parse_fn):
|
47
|
+
raise TypeError("parse_fn must be a callable")
|
43
48
|
|
44
49
|
parsed_schemas_list: List[IRSchema] = []
|
45
50
|
is_nullable_from_one_of = False
|
@@ -53,9 +53,12 @@ def _finalize_schema_object(
|
|
53
53
|
- Returns a finalized IRSchema instance.
|
54
54
|
- If schema has a name and isn't a placeholder, it's in context.parsed_schemas.
|
55
55
|
"""
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
if not isinstance(node, Mapping):
|
57
|
+
raise TypeError("node must be a Mapping")
|
58
|
+
if not isinstance(context, ParsingContext):
|
59
|
+
raise TypeError("context must be a ParsingContext instance")
|
60
|
+
if not callable(parse_fn):
|
61
|
+
raise TypeError("parse_fn must be callable")
|
59
62
|
# Remove logger type check to support mock loggers in tests
|
60
63
|
|
61
64
|
# If a placeholder for this schema name already exists due to a cycle detected deeper,
|
@@ -326,7 +326,8 @@ def _parse_schema(
|
|
326
326
|
Parse a schema node and return an IRSchema object.
|
327
327
|
"""
|
328
328
|
# Pre-conditions
|
329
|
-
|
329
|
+
if context is None:
|
330
|
+
raise ValueError("Context cannot be None for _parse_schema")
|
330
331
|
|
331
332
|
# Set allow_self_reference flag on unified context
|
332
333
|
context.unified_cycle_context.allow_self_reference = allow_self_reference
|
@@ -377,9 +378,10 @@ def _parse_schema(
|
|
377
378
|
if schema_node is None:
|
378
379
|
return IRSchema(name=NameSanitizer.sanitize_class_name(schema_name) if schema_name else None)
|
379
380
|
|
380
|
-
|
381
|
-
|
382
|
-
|
381
|
+
if not isinstance(schema_node, Mapping):
|
382
|
+
raise TypeError(
|
383
|
+
f"Schema node for '{schema_name or 'anonymous'}' must be a Mapping (e.g., dict), got {type(schema_node)}"
|
384
|
+
)
|
383
385
|
|
384
386
|
# If the current schema_node itself is a $ref, resolve it.
|
385
387
|
if "$ref" in schema_node:
|
@@ -197,7 +197,8 @@ class EndpointsEmitter:
|
|
197
197
|
self._deduplicate_operation_ids(ops_for_tag)
|
198
198
|
|
199
199
|
# EndpointVisitor must exist here due to check above
|
200
|
-
|
200
|
+
if self.visitor is None:
|
201
|
+
raise RuntimeError("EndpointVisitor not initialized")
|
201
202
|
methods = [self.visitor.visit(op, self.context) for op in ops_for_tag]
|
202
203
|
class_content = self.visitor.emit_endpoint_client_class(canonical_tag_name, methods, self.context)
|
203
204
|
|
@@ -42,12 +42,10 @@ class ModelsEmitter:
|
|
42
42
|
# )
|
43
43
|
|
44
44
|
# Assert that de-collided names have been set by the emit() method's preprocessing.
|
45
|
-
|
46
|
-
schema_ir.generation_name
|
47
|
-
|
48
|
-
|
49
|
-
schema_ir.final_module_stem is not None
|
50
|
-
), f"Schema '{schema_ir.name}' must have final_module_stem set before file generation."
|
45
|
+
if schema_ir.generation_name is None:
|
46
|
+
raise RuntimeError(f"Schema '{schema_ir.name}' must have generation_name set before file generation.")
|
47
|
+
if schema_ir.final_module_stem is None:
|
48
|
+
raise RuntimeError(f"Schema '{schema_ir.name}' must have final_module_stem set before file generation.")
|
51
49
|
|
52
50
|
file_path = models_dir / f"{schema_ir.final_module_stem}.py"
|
53
51
|
|
@@ -138,12 +136,10 @@ class ModelsEmitter:
|
|
138
136
|
|
139
137
|
for s_schema in sorted_schemas_for_init:
|
140
138
|
# These should have been set in the emit() preprocessing step.
|
141
|
-
|
142
|
-
s_schema.generation_name
|
143
|
-
|
144
|
-
|
145
|
-
s_schema.final_module_stem is not None
|
146
|
-
), f"Schema '{s_schema.name}' missing final_module_stem in __init__ generation."
|
139
|
+
if s_schema.generation_name is None:
|
140
|
+
raise RuntimeError(f"Schema '{s_schema.name}' missing generation_name in __init__ generation.")
|
141
|
+
if s_schema.final_module_stem is None:
|
142
|
+
raise RuntimeError(f"Schema '{s_schema.name}' missing final_module_stem in __init__ generation.")
|
147
143
|
|
148
144
|
if s_schema._from_unresolved_ref: # Check this flag if it's relevant
|
149
145
|
# logger.debug(
|
@@ -184,8 +180,10 @@ class ModelsEmitter:
|
|
184
180
|
- All models are properly formatted and type-annotated
|
185
181
|
- Returns a list of file paths generated
|
186
182
|
"""
|
187
|
-
|
188
|
-
|
183
|
+
if not isinstance(spec, IRSpec):
|
184
|
+
raise TypeError("spec must be an IRSpec")
|
185
|
+
if not output_root:
|
186
|
+
raise ValueError("output_root must be a non-empty string")
|
189
187
|
|
190
188
|
output_dir = Path(output_root.rstrip("/"))
|
191
189
|
models_dir = output_dir / "models"
|