pyopenapi-gen 0.8.3__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 +114 -0
- pyopenapi_gen/__main__.py +6 -0
- pyopenapi_gen/cli.py +86 -0
- pyopenapi_gen/context/file_manager.py +52 -0
- pyopenapi_gen/context/import_collector.py +382 -0
- pyopenapi_gen/context/render_context.py +630 -0
- pyopenapi_gen/core/__init__.py +0 -0
- pyopenapi_gen/core/auth/base.py +22 -0
- pyopenapi_gen/core/auth/plugins.py +89 -0
- pyopenapi_gen/core/exceptions.py +25 -0
- pyopenapi_gen/core/http_transport.py +219 -0
- pyopenapi_gen/core/loader/__init__.py +12 -0
- pyopenapi_gen/core/loader/loader.py +158 -0
- pyopenapi_gen/core/loader/operations/__init__.py +12 -0
- pyopenapi_gen/core/loader/operations/parser.py +155 -0
- pyopenapi_gen/core/loader/operations/post_processor.py +60 -0
- pyopenapi_gen/core/loader/operations/request_body.py +85 -0
- pyopenapi_gen/core/loader/parameters/__init__.py +10 -0
- pyopenapi_gen/core/loader/parameters/parser.py +121 -0
- pyopenapi_gen/core/loader/responses/__init__.py +10 -0
- pyopenapi_gen/core/loader/responses/parser.py +104 -0
- pyopenapi_gen/core/loader/schemas/__init__.py +11 -0
- pyopenapi_gen/core/loader/schemas/extractor.py +184 -0
- pyopenapi_gen/core/pagination.py +64 -0
- pyopenapi_gen/core/parsing/__init__.py +13 -0
- pyopenapi_gen/core/parsing/common/__init__.py +1 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/__init__.py +9 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/__init__.py +0 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/cyclic_properties.py +66 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/direct_cycle.py +33 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/existing_schema.py +22 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/list_response.py +54 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/missing_ref.py +52 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/new_schema.py +50 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/stripped_suffix.py +51 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/resolve_schema_ref.py +86 -0
- pyopenapi_gen/core/parsing/common/type_parser.py +74 -0
- pyopenapi_gen/core/parsing/context.py +184 -0
- pyopenapi_gen/core/parsing/cycle_helpers.py +123 -0
- pyopenapi_gen/core/parsing/keywords/__init__.py +1 -0
- pyopenapi_gen/core/parsing/keywords/all_of_parser.py +77 -0
- pyopenapi_gen/core/parsing/keywords/any_of_parser.py +79 -0
- pyopenapi_gen/core/parsing/keywords/array_items_parser.py +69 -0
- pyopenapi_gen/core/parsing/keywords/one_of_parser.py +72 -0
- pyopenapi_gen/core/parsing/keywords/properties_parser.py +98 -0
- pyopenapi_gen/core/parsing/schema_finalizer.py +166 -0
- pyopenapi_gen/core/parsing/schema_parser.py +610 -0
- pyopenapi_gen/core/parsing/transformers/__init__.py +0 -0
- pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +285 -0
- pyopenapi_gen/core/parsing/transformers/inline_object_promoter.py +117 -0
- pyopenapi_gen/core/parsing/unified_cycle_detection.py +293 -0
- pyopenapi_gen/core/postprocess_manager.py +161 -0
- pyopenapi_gen/core/schemas.py +40 -0
- pyopenapi_gen/core/streaming_helpers.py +86 -0
- pyopenapi_gen/core/telemetry.py +67 -0
- pyopenapi_gen/core/utils.py +409 -0
- pyopenapi_gen/core/warning_collector.py +83 -0
- pyopenapi_gen/core/writers/code_writer.py +135 -0
- pyopenapi_gen/core/writers/documentation_writer.py +222 -0
- pyopenapi_gen/core/writers/line_writer.py +217 -0
- pyopenapi_gen/core/writers/python_construct_renderer.py +274 -0
- pyopenapi_gen/core_package_template/README.md +21 -0
- pyopenapi_gen/emit/models_emitter.py +143 -0
- pyopenapi_gen/emitters/client_emitter.py +51 -0
- pyopenapi_gen/emitters/core_emitter.py +181 -0
- pyopenapi_gen/emitters/docs_emitter.py +44 -0
- pyopenapi_gen/emitters/endpoints_emitter.py +223 -0
- pyopenapi_gen/emitters/exceptions_emitter.py +52 -0
- pyopenapi_gen/emitters/models_emitter.py +428 -0
- pyopenapi_gen/generator/client_generator.py +562 -0
- pyopenapi_gen/helpers/__init__.py +1 -0
- pyopenapi_gen/helpers/endpoint_utils.py +552 -0
- pyopenapi_gen/helpers/type_cleaner.py +341 -0
- pyopenapi_gen/helpers/type_helper.py +112 -0
- pyopenapi_gen/helpers/type_resolution/__init__.py +1 -0
- pyopenapi_gen/helpers/type_resolution/array_resolver.py +57 -0
- pyopenapi_gen/helpers/type_resolution/composition_resolver.py +79 -0
- pyopenapi_gen/helpers/type_resolution/finalizer.py +89 -0
- pyopenapi_gen/helpers/type_resolution/named_resolver.py +174 -0
- pyopenapi_gen/helpers/type_resolution/object_resolver.py +212 -0
- pyopenapi_gen/helpers/type_resolution/primitive_resolver.py +57 -0
- pyopenapi_gen/helpers/type_resolution/resolver.py +48 -0
- pyopenapi_gen/helpers/url_utils.py +14 -0
- pyopenapi_gen/http_types.py +20 -0
- pyopenapi_gen/ir.py +167 -0
- pyopenapi_gen/py.typed +1 -0
- pyopenapi_gen/types/__init__.py +11 -0
- pyopenapi_gen/types/contracts/__init__.py +13 -0
- pyopenapi_gen/types/contracts/protocols.py +106 -0
- pyopenapi_gen/types/contracts/types.py +30 -0
- pyopenapi_gen/types/resolvers/__init__.py +7 -0
- pyopenapi_gen/types/resolvers/reference_resolver.py +71 -0
- pyopenapi_gen/types/resolvers/response_resolver.py +203 -0
- pyopenapi_gen/types/resolvers/schema_resolver.py +367 -0
- pyopenapi_gen/types/services/__init__.py +5 -0
- pyopenapi_gen/types/services/type_service.py +133 -0
- pyopenapi_gen/visit/client_visitor.py +228 -0
- pyopenapi_gen/visit/docs_visitor.py +38 -0
- pyopenapi_gen/visit/endpoint/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/endpoint_visitor.py +103 -0
- pyopenapi_gen/visit/endpoint/generators/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +121 -0
- pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py +87 -0
- pyopenapi_gen/visit/endpoint/generators/request_generator.py +103 -0
- pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +497 -0
- pyopenapi_gen/visit/endpoint/generators/signature_generator.py +88 -0
- pyopenapi_gen/visit/endpoint/generators/url_args_generator.py +183 -0
- pyopenapi_gen/visit/endpoint/processors/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +76 -0
- pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +171 -0
- pyopenapi_gen/visit/exception_visitor.py +52 -0
- pyopenapi_gen/visit/model/__init__.py +0 -0
- pyopenapi_gen/visit/model/alias_generator.py +89 -0
- pyopenapi_gen/visit/model/dataclass_generator.py +197 -0
- pyopenapi_gen/visit/model/enum_generator.py +200 -0
- pyopenapi_gen/visit/model/model_visitor.py +197 -0
- pyopenapi_gen/visit/visitor.py +97 -0
- pyopenapi_gen-0.8.3.dist-info/METADATA +224 -0
- pyopenapi_gen-0.8.3.dist-info/RECORD +122 -0
- pyopenapi_gen-0.8.3.dist-info/WHEEL +4 -0
- pyopenapi_gen-0.8.3.dist-info/entry_points.txt +2 -0
- pyopenapi_gen-0.8.3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,285 @@
|
|
1
|
+
"""
|
2
|
+
Handles the extraction of inline enums from schema definitions.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Any, Mapping, Optional
|
9
|
+
|
10
|
+
from .... import IRSchema
|
11
|
+
from ...utils import NameSanitizer
|
12
|
+
from ..context import ParsingContext
|
13
|
+
|
14
|
+
|
15
|
+
def _extract_enum_from_property_node(
|
16
|
+
parent_schema_name: Optional[str],
|
17
|
+
property_key: str,
|
18
|
+
property_node_data: Mapping[str, Any],
|
19
|
+
context: ParsingContext,
|
20
|
+
logger: logging.Logger,
|
21
|
+
) -> Optional[IRSchema]:
|
22
|
+
"""
|
23
|
+
Checks a property's schema node for an inline enum definition.
|
24
|
+
|
25
|
+
If an inline enum is found (i.e., the node is a dict, has "enum", and is not a $ref),
|
26
|
+
this function:
|
27
|
+
1. Creates a new global IRSchema for the enum.
|
28
|
+
2. Adds this new enum schema to context.parsed_schemas.
|
29
|
+
3. Returns a new IRSchema representing the property, which refers (by type)
|
30
|
+
to the globally registered enum.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
parent_schema_name: The name of the parent schema containing the property.
|
34
|
+
Used for generating a descriptive enum name.
|
35
|
+
property_key: The key (name) of the property being processed.
|
36
|
+
property_node_data: The raw schema definition for the property.
|
37
|
+
context: The global parsing context.
|
38
|
+
logger: Logger instance for logging messages.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
An IRSchema for the property referring to the new global enum if an inline
|
42
|
+
enum was extracted. Otherwise, returns None.
|
43
|
+
"""
|
44
|
+
if not (isinstance(property_node_data, dict) and "enum" in property_node_data and "$ref" not in property_node_data):
|
45
|
+
return None
|
46
|
+
|
47
|
+
enum_values = property_node_data["enum"]
|
48
|
+
# Default to "string" if type is not specified, as per OpenAPI for enums
|
49
|
+
enum_type_from_node = property_node_data.get("type", "string")
|
50
|
+
enum_description = property_node_data.get("description")
|
51
|
+
|
52
|
+
# Improved naming for inline enums - create more descriptive and semantic names
|
53
|
+
# First, process the parent schema name
|
54
|
+
if parent_schema_name:
|
55
|
+
parent_class_name_for_enum = NameSanitizer.sanitize_class_name(parent_schema_name)
|
56
|
+
else:
|
57
|
+
# Instead of generic "AnonymousSchema", try to give a more descriptive context name
|
58
|
+
# based on common patterns like "status" or "type" fields
|
59
|
+
if property_key.lower() in ["status", "state", "type", "category", "role", "permission", "level"]:
|
60
|
+
# These are common fields that often have enums - use a descriptive prefix based on the property
|
61
|
+
parent_class_name_for_enum = {
|
62
|
+
"status": "Status",
|
63
|
+
"state": "State",
|
64
|
+
"type": "Type",
|
65
|
+
"category": "Category",
|
66
|
+
"role": "Role",
|
67
|
+
"permission": "Permission",
|
68
|
+
"level": "Level",
|
69
|
+
}.get(property_key.lower(), "Resource")
|
70
|
+
else:
|
71
|
+
# Use a more semantic name than "AnonymousSchema"
|
72
|
+
parent_class_name_for_enum = "AnonymousSchema"
|
73
|
+
|
74
|
+
# Process the property name for the enum
|
75
|
+
prop_class_name_for_enum = NameSanitizer.sanitize_class_name(property_key)
|
76
|
+
|
77
|
+
# Special handling for common property names - make the enum name more specific
|
78
|
+
if prop_class_name_for_enum.lower() in ["status", "state", "type", "category"]:
|
79
|
+
# Example: Convert "Status" property in "Order" class to "OrderStatusEnum"
|
80
|
+
# Instead of just "OrderStatusEnum" which doesn't indicate what type of status it is
|
81
|
+
base_generated_enum_name = f"{parent_class_name_for_enum}{prop_class_name_for_enum}Enum"
|
82
|
+
else:
|
83
|
+
# For other properties, keep the standard naming pattern but ensure "Enum" suffix
|
84
|
+
if not prop_class_name_for_enum.endswith("Enum"):
|
85
|
+
base_generated_enum_name = f"{parent_class_name_for_enum}{prop_class_name_for_enum}Enum"
|
86
|
+
else:
|
87
|
+
base_generated_enum_name = f"{parent_class_name_for_enum}{prop_class_name_for_enum}"
|
88
|
+
|
89
|
+
# Ensure uniqueness with counter if needed
|
90
|
+
generated_enum_name = base_generated_enum_name
|
91
|
+
counter = 1
|
92
|
+
while generated_enum_name in context.parsed_schemas:
|
93
|
+
# Ensure the schema in context isn't the exact same definition
|
94
|
+
# For now, simple name collision avoidance is sufficient
|
95
|
+
generated_enum_name = f"{base_generated_enum_name}{counter}"
|
96
|
+
counter += 1
|
97
|
+
|
98
|
+
# Construct the name for logging/description before sanitization for clarity
|
99
|
+
prop_schema_context_name = f"{parent_schema_name}.{property_key}" if parent_schema_name else property_key
|
100
|
+
|
101
|
+
new_enum_ir = IRSchema(
|
102
|
+
name=generated_enum_name,
|
103
|
+
type=enum_type_from_node,
|
104
|
+
enum=enum_values,
|
105
|
+
description=property_node_data.get("description") or f"An enumeration for {prop_schema_context_name}",
|
106
|
+
properties={}, # Enums do not have properties in this context
|
107
|
+
required=[], # Enums do not have required fields
|
108
|
+
)
|
109
|
+
context.parsed_schemas[generated_enum_name] = new_enum_ir
|
110
|
+
logger.debug(
|
111
|
+
f"INLINE_ENUM_EXTRACT: Extracted inline enum for '{prop_schema_context_name}' "
|
112
|
+
f"to global schema '{generated_enum_name}'. In context: {generated_enum_name in context.parsed_schemas}"
|
113
|
+
)
|
114
|
+
|
115
|
+
# Return an IRSchema for the property that refers to this new global enum
|
116
|
+
property_ir_referencing_enum = IRSchema(
|
117
|
+
name=property_key, # The property keeps its original name
|
118
|
+
type=generated_enum_name, # Type is the name of the new global enum
|
119
|
+
description=enum_description, # Property's original description
|
120
|
+
is_nullable=property_node_data.get("nullable", False)
|
121
|
+
or ("null" in enum_type_from_node if isinstance(enum_type_from_node, list) else False),
|
122
|
+
# Other fields like format, etc., are not typically on the property ref if it's just a type ref to an enum.
|
123
|
+
# The enum definition itself holds those.
|
124
|
+
)
|
125
|
+
return property_ir_referencing_enum
|
126
|
+
|
127
|
+
|
128
|
+
def _process_standalone_inline_enum(
|
129
|
+
schema_name: Optional[str], # The original intended name for this schema
|
130
|
+
node_data: Mapping[str, Any], # The raw node data for this schema
|
131
|
+
schema_obj: IRSchema, # The IRSchema object already partially parsed for this node
|
132
|
+
context: ParsingContext,
|
133
|
+
logger: logging.Logger,
|
134
|
+
) -> IRSchema:
|
135
|
+
"""
|
136
|
+
Processes a schema node that might itself be an inline enum, not nested within a property.
|
137
|
+
|
138
|
+
If the provided `node_data` defines an enum (has "enum" key, is not a $ref),
|
139
|
+
and `schema_obj` doesn't yet have its enum values populated or a proper name,
|
140
|
+
this function will:
|
141
|
+
1. Ensure `schema_obj.name` is correctly sanitized and globally unique if it's an enum.
|
142
|
+
2. Populate `schema_obj.enum` and `schema_obj.type` from `node_data`.
|
143
|
+
3. Ensure the potentially renamed/finalized `schema_obj` is in `context.parsed_schemas`.
|
144
|
+
|
145
|
+
This is for cases like:
|
146
|
+
components:
|
147
|
+
schemas:
|
148
|
+
MyTopLevelEnum:
|
149
|
+
type: string
|
150
|
+
enum: ["A", "B"]
|
151
|
+
AnotherObject:
|
152
|
+
properties:
|
153
|
+
some_field:
|
154
|
+
type: string # Not an enum
|
155
|
+
another_enum_prop: # Handled by _extract_enum_from_property_node
|
156
|
+
type: string
|
157
|
+
enum: ["X", "Y"]
|
158
|
+
requestBodies:
|
159
|
+
EnumBody:
|
160
|
+
content:
|
161
|
+
application/json:
|
162
|
+
schema:
|
163
|
+
type: string # This schema itself is an inline enum
|
164
|
+
enum: ["C", "D"]
|
165
|
+
|
166
|
+
Args:
|
167
|
+
schema_name: The original name hint for this schema (e.g. from components.schemas key or a synthesized name).
|
168
|
+
node_data: The raw OpenAPI node dictionary for the schema.
|
169
|
+
schema_obj: The IRSchema instance created from initial parsing of `node_data`.
|
170
|
+
context: The global parsing context.
|
171
|
+
logger: Logger instance.
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
The (potentially updated) IRSchema object.
|
175
|
+
"""
|
176
|
+
# If it's not an enum defined directly in this node, or if schema_obj already has enum values, do nothing more here.
|
177
|
+
if not (isinstance(node_data, dict) and "enum" in node_data and "$ref" not in node_data) or schema_obj.enum:
|
178
|
+
return schema_obj
|
179
|
+
|
180
|
+
logger.debug(
|
181
|
+
f"STANDALONE_ENUM_CHECK: Processing node for "
|
182
|
+
f"'{schema_name or schema_obj.name or 'anonymous_schema'}' for direct enum properties."
|
183
|
+
)
|
184
|
+
|
185
|
+
# Ensure basic enum properties are on schema_obj if not already there from initial _parse_schema pass
|
186
|
+
if not schema_obj.enum:
|
187
|
+
schema_obj.enum = node_data.get("enum")
|
188
|
+
if not schema_obj.type and node_data.get("type"):
|
189
|
+
# TODO: Handle type extraction more robustly here, possibly using extract_primary_type_and_nullability
|
190
|
+
# For now, simple type assignment for enums.
|
191
|
+
raw_type = node_data.get("type", "string")
|
192
|
+
if isinstance(raw_type, list):
|
193
|
+
# For enums like type: [string, null], the primary type is string for the enum values themselves.
|
194
|
+
# Nullability should be handled by schema_obj.is_nullable elsewhere.
|
195
|
+
primary_enum_type = next((t for t in raw_type if t != "null"), "string")
|
196
|
+
schema_obj.type = primary_enum_type
|
197
|
+
else:
|
198
|
+
schema_obj.type = raw_type
|
199
|
+
elif not schema_obj.type: # Default type for enum if not specified
|
200
|
+
schema_obj.type = "string"
|
201
|
+
|
202
|
+
# Ensure a proper, unique name if this is indeed an enum and needs one.
|
203
|
+
# schema_obj.name might be None if it was parsed from, e.g., a requestBody schema directly.
|
204
|
+
if not schema_obj.name and schema_name:
|
205
|
+
schema_obj.name = NameSanitizer.sanitize_class_name(schema_name)
|
206
|
+
elif not schema_obj.name:
|
207
|
+
# Create a more descriptive name based on enum values if possible
|
208
|
+
enum_values = schema_obj.enum or []
|
209
|
+
|
210
|
+
# Try to determine a semantic name from the enum values
|
211
|
+
if enum_values and all(isinstance(v, str) for v in enum_values):
|
212
|
+
# For string enums, look for common patterns in the values
|
213
|
+
enum_values_str = [str(v).lower() for v in enum_values]
|
214
|
+
|
215
|
+
# Check for common patterns in enum values
|
216
|
+
if any(v in ["pending", "active", "completed", "cancelled", "failed"] for v in enum_values_str):
|
217
|
+
base_name = "StatusEnum"
|
218
|
+
elif any(v in ["admin", "user", "guest", "moderator"] for v in enum_values_str):
|
219
|
+
base_name = "RoleEnum"
|
220
|
+
elif any(v in ["read", "write", "admin", "owner"] for v in enum_values_str):
|
221
|
+
base_name = "PermissionEnum"
|
222
|
+
elif any(v in ["asc", "desc", "ascending", "descending"] for v in enum_values_str):
|
223
|
+
base_name = "SortOrderEnum"
|
224
|
+
elif any(v in ["true", "false", "yes", "no"] for v in enum_values_str):
|
225
|
+
base_name = "BooleanEnum"
|
226
|
+
else:
|
227
|
+
# Default to more descriptive name than just "UnnamedEnum"
|
228
|
+
base_name = "ResourceTypeEnum"
|
229
|
+
else:
|
230
|
+
# If we can't determine from values, use a generic but still meaningful name
|
231
|
+
base_name = "ResourceTypeEnum"
|
232
|
+
|
233
|
+
# Ensure uniqueness
|
234
|
+
counter = 1
|
235
|
+
candidate_name = base_name
|
236
|
+
while candidate_name in context.parsed_schemas:
|
237
|
+
candidate_name = f"{base_name}{counter}"
|
238
|
+
counter += 1
|
239
|
+
|
240
|
+
schema_obj.name = candidate_name
|
241
|
+
|
242
|
+
# If the name (now sanitized and potentially unique) exists in context.parsed_schemas
|
243
|
+
# but refers to a different object, we need to ensure this schema_obj gets a unique name.
|
244
|
+
if (
|
245
|
+
schema_obj.name
|
246
|
+
and schema_obj.name in context.parsed_schemas
|
247
|
+
and context.parsed_schemas[schema_obj.name] is not schema_obj
|
248
|
+
):
|
249
|
+
original_name_attempt = schema_obj.name
|
250
|
+
counter = 1
|
251
|
+
new_name_base = original_name_attempt
|
252
|
+
# Avoid potential infinite loop if somehow new_name_base ends up empty or just a number.
|
253
|
+
if not new_name_base or new_name_base.isnumeric():
|
254
|
+
new_name_base = "Enum" # Fallback to a generic base name
|
255
|
+
|
256
|
+
while schema_obj.name in context.parsed_schemas and context.parsed_schemas[schema_obj.name] is not schema_obj:
|
257
|
+
schema_obj.name = f"{new_name_base}{counter}"
|
258
|
+
counter += 1
|
259
|
+
logger.warning(
|
260
|
+
f"STANDALONE_ENUM_NAME_COLLISION: Renamed schema from '{original_name_attempt}' to '{schema_obj.name}' "
|
261
|
+
f"due to name collision with a different existing schema."
|
262
|
+
)
|
263
|
+
|
264
|
+
# Ensure the final named schema_obj is in the context
|
265
|
+
if schema_obj.name and schema_obj.name not in context.parsed_schemas:
|
266
|
+
context.parsed_schemas[schema_obj.name] = schema_obj
|
267
|
+
logger.debug(
|
268
|
+
f"STANDALONE_ENUM_FINALIZED: Added/updated schema '{schema_obj.name}' "
|
269
|
+
f"in context after processing as standalone enum."
|
270
|
+
)
|
271
|
+
elif schema_obj.name and context.parsed_schemas[schema_obj.name] is not schema_obj:
|
272
|
+
# This case should ideally be caught by the renaming logic above.
|
273
|
+
# If we are here, it means a schema with this name exists, but it's not our schema_obj.
|
274
|
+
# This indicates a problem if schema_obj was supposed to be *the* definition for that name.
|
275
|
+
logger.error(
|
276
|
+
f"STANDALONE_ENUM_ERROR: Schema '{schema_obj.name}' exists in context "
|
277
|
+
f"but is not the current schema_obj. This is unexpected."
|
278
|
+
)
|
279
|
+
elif not schema_obj.name:
|
280
|
+
logger.warning(
|
281
|
+
"STANDALONE_ENUM_UNNAMED: Processed a standalone enum but it ended up "
|
282
|
+
"without a name. This might be an issue."
|
283
|
+
)
|
284
|
+
|
285
|
+
return schema_obj
|
@@ -0,0 +1,117 @@
|
|
1
|
+
"""
|
2
|
+
Handles the promotion of inline object schemas to global schemas.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Optional
|
9
|
+
|
10
|
+
from .... import IRSchema
|
11
|
+
from ...utils import NameSanitizer
|
12
|
+
from ..context import ParsingContext
|
13
|
+
|
14
|
+
|
15
|
+
def _attempt_promote_inline_object(
|
16
|
+
parent_schema_name: Optional[str], # Name of the schema containing the property
|
17
|
+
property_key: str, # The key (name) of the property being processed
|
18
|
+
property_schema_obj: IRSchema, # The IRSchema of the property itself (already parsed)
|
19
|
+
context: ParsingContext,
|
20
|
+
logger: logging.Logger,
|
21
|
+
) -> Optional[IRSchema]:
|
22
|
+
logger.debug(
|
23
|
+
f"PROMO_ATTEMPT: parent='{parent_schema_name}', prop_key='{property_key}', "
|
24
|
+
f"prop_schema_name='{property_schema_obj.name}', prop_schema_type='{property_schema_obj.type}', "
|
25
|
+
f"prop_is_enum='{property_schema_obj.enum is not None}', "
|
26
|
+
f"prop_is_ref='{property_schema_obj._from_unresolved_ref}'"
|
27
|
+
)
|
28
|
+
"""
|
29
|
+
Checks if a given property's schema (`property_schema_obj`) represents an inline object
|
30
|
+
that should be "promoted" to a global schema definition.
|
31
|
+
|
32
|
+
Conditions for promotion:
|
33
|
+
- `property_schema_obj.type` is "object".
|
34
|
+
- `property_schema_obj.enum` is None (i.e., it's not an enum).
|
35
|
+
- `property_schema_obj._from_unresolved_ref` is False (it wasn't from a $ref).
|
36
|
+
|
37
|
+
If promoted:
|
38
|
+
1. A unique global name is generated for `property_schema_obj`.
|
39
|
+
2. `property_schema_obj.name` is updated to this new global name.
|
40
|
+
3. `property_schema_obj` is added/updated in `context.parsed_schemas` under its new global name.
|
41
|
+
4. A new IRSchema is returned for the original property, which now *refers* (by type)
|
42
|
+
to the globally registered `property_schema_obj`.
|
43
|
+
This new IRSchema preserves the original property's description, nullability etc.
|
44
|
+
"""
|
45
|
+
if property_schema_obj.type != "object":
|
46
|
+
logger.debug(
|
47
|
+
f"PROMO_SKIP: parent='{parent_schema_name}', prop_key='{property_key}' "
|
48
|
+
f"not promoted, type is '{property_schema_obj.type}', not 'object'."
|
49
|
+
)
|
50
|
+
return None
|
51
|
+
|
52
|
+
if property_schema_obj.enum is not None:
|
53
|
+
logger.debug(
|
54
|
+
f"PROMO_SKIP: parent='{parent_schema_name}', prop_key='{property_key}' not promoted, it is an enum."
|
55
|
+
)
|
56
|
+
return None
|
57
|
+
|
58
|
+
if property_schema_obj._from_unresolved_ref:
|
59
|
+
logger.debug(
|
60
|
+
f"PROMO_SKIP: parent='{parent_schema_name}', prop_key='{property_key}' not promoted, it was from a $ref."
|
61
|
+
)
|
62
|
+
return None
|
63
|
+
|
64
|
+
# Improved naming strategy for more intuitive and descriptive schema naming
|
65
|
+
sanitized_prop_key_class_name = NameSanitizer.sanitize_class_name(property_key)
|
66
|
+
|
67
|
+
# Primary candidate name: ParentName + PropertyName (sanitized)
|
68
|
+
# Example: parent="User", prop_key="address" (sanitized_prop_key_class_name="Address") -> UserAddress
|
69
|
+
# Example: parent=None, prop_key="user_profile" -> UserProfile
|
70
|
+
if parent_schema_name:
|
71
|
+
# First sanitize the parent name to ensure it's in PascalCase
|
72
|
+
sanitized_parent_name = NameSanitizer.sanitize_class_name(parent_schema_name)
|
73
|
+
# Then combine with the sanitized property key
|
74
|
+
base_name_candidate = f"{sanitized_parent_name}{sanitized_prop_key_class_name}"
|
75
|
+
else:
|
76
|
+
base_name_candidate = sanitized_prop_key_class_name
|
77
|
+
|
78
|
+
chosen_global_name: Optional[str] = None
|
79
|
+
|
80
|
+
# Check if the primary candidate name is available or already points to this object
|
81
|
+
if base_name_candidate in context.parsed_schemas:
|
82
|
+
existing_schema = context.parsed_schemas[base_name_candidate]
|
83
|
+
# If the existing schema is identical to our property schema, reuse it
|
84
|
+
if existing_schema.properties == property_schema_obj.properties:
|
85
|
+
chosen_global_name = base_name_candidate
|
86
|
+
else:
|
87
|
+
# If different schema, try with counter
|
88
|
+
counter = 1
|
89
|
+
temp_name = f"{base_name_candidate}{counter}"
|
90
|
+
while temp_name in context.parsed_schemas:
|
91
|
+
existing_schema = context.parsed_schemas[temp_name]
|
92
|
+
if existing_schema.properties == property_schema_obj.properties:
|
93
|
+
chosen_global_name = temp_name
|
94
|
+
break
|
95
|
+
counter += 1
|
96
|
+
temp_name = f"{base_name_candidate}{counter}"
|
97
|
+
if chosen_global_name is None:
|
98
|
+
chosen_global_name = temp_name
|
99
|
+
else:
|
100
|
+
chosen_global_name = base_name_candidate
|
101
|
+
|
102
|
+
original_name_of_promoted_obj = property_schema_obj.name
|
103
|
+
property_schema_obj.name = chosen_global_name
|
104
|
+
context.parsed_schemas[chosen_global_name] = property_schema_obj
|
105
|
+
|
106
|
+
# Corrected logger call for clarity and f-string safety
|
107
|
+
parent_display_name = parent_schema_name or "<None>"
|
108
|
+
|
109
|
+
property_ref_ir = IRSchema(
|
110
|
+
name=property_key,
|
111
|
+
type=chosen_global_name,
|
112
|
+
description=property_schema_obj.description,
|
113
|
+
is_nullable=property_schema_obj.is_nullable,
|
114
|
+
)
|
115
|
+
property_ref_ir._refers_to_schema = property_schema_obj
|
116
|
+
|
117
|
+
return property_ref_ir
|