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
File without changes
|
@@ -0,0 +1,66 @@
|
|
1
|
+
"""
|
2
|
+
Helper module for handling cyclic property references in schemas.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Set
|
7
|
+
|
8
|
+
from pyopenapi_gen.ir import IRSchema
|
9
|
+
|
10
|
+
from ....context import ParsingContext
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
def mark_cyclic_property_references(schema_obj: IRSchema, ref_name: str, context: ParsingContext) -> None:
|
16
|
+
"""
|
17
|
+
Marks properties in a schema that form cycles as unresolved.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
schema_obj: The schema object to check for cycles
|
21
|
+
ref_name: The name of the schema being referenced
|
22
|
+
context: The parsing context
|
23
|
+
|
24
|
+
Pre-conditions:
|
25
|
+
- schema_obj is a valid IRSchema instance
|
26
|
+
- ref_name is a non-empty string
|
27
|
+
- context is a valid ParsingContext instance
|
28
|
+
|
29
|
+
Post-conditions:
|
30
|
+
- Properties that form cycles are marked as unresolved
|
31
|
+
- Non-cyclic properties remain unchanged
|
32
|
+
"""
|
33
|
+
if not schema_obj.properties:
|
34
|
+
return
|
35
|
+
|
36
|
+
visited: Set[str] = set()
|
37
|
+
|
38
|
+
def _check_cycle(prop_name: str) -> bool:
|
39
|
+
if prop_name in visited:
|
40
|
+
return True
|
41
|
+
visited.add(prop_name)
|
42
|
+
|
43
|
+
prop_schema = schema_obj.properties.get(prop_name)
|
44
|
+
if not prop_schema or not prop_schema._refers_to_schema:
|
45
|
+
return False
|
46
|
+
|
47
|
+
if prop_schema._refers_to_schema.name == ref_name:
|
48
|
+
return True
|
49
|
+
|
50
|
+
if prop_schema._refers_to_schema.properties:
|
51
|
+
for nested_prop_name in prop_schema._refers_to_schema.properties:
|
52
|
+
nested_prop = prop_schema._refers_to_schema.properties[nested_prop_name]
|
53
|
+
if nested_prop._refers_to_schema and nested_prop._refers_to_schema.name == ref_name:
|
54
|
+
return True
|
55
|
+
if _check_cycle(nested_prop_name):
|
56
|
+
return True
|
57
|
+
|
58
|
+
return False
|
59
|
+
|
60
|
+
# Check each property for cycles
|
61
|
+
for prop_name, prop_schema in schema_obj.properties.items():
|
62
|
+
if _check_cycle(prop_name):
|
63
|
+
prop_schema._from_unresolved_ref = True
|
64
|
+
prop_schema._is_circular_ref = True
|
65
|
+
context.cycle_detected = True
|
66
|
+
logger.debug(f"Cyclic property reference detected: {ref_name}.{prop_name}")
|
@@ -0,0 +1,33 @@
|
|
1
|
+
"""
|
2
|
+
Module for handling direct cycle detection.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from pyopenapi_gen.ir import IRSchema
|
8
|
+
|
9
|
+
from ....context import ParsingContext
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
def handle_direct_cycle(ref_name: str, context: ParsingContext) -> IRSchema:
|
15
|
+
"""
|
16
|
+
Handles a direct cycle in schema references.
|
17
|
+
|
18
|
+
Contracts:
|
19
|
+
Pre-conditions:
|
20
|
+
- ref_name must be a valid schema name
|
21
|
+
- context must be a valid ParsingContext instance
|
22
|
+
- ref_name must exist in context.parsed_schemas
|
23
|
+
Post-conditions:
|
24
|
+
- Returns the existing schema from context.parsed_schemas
|
25
|
+
- The schema's _from_unresolved_ref flag is set to True
|
26
|
+
- The schema's _is_circular_ref flag is set to True (for harmonized cycle detection)
|
27
|
+
"""
|
28
|
+
existing_schema = context.parsed_schemas[ref_name]
|
29
|
+
existing_schema._from_unresolved_ref = True
|
30
|
+
existing_schema._is_circular_ref = True # Harmonize with cycle detection contract
|
31
|
+
context.cycle_detected = True # Mark cycle in context
|
32
|
+
logger.debug(f"Direct cycle detected for schema '{ref_name}'")
|
33
|
+
return existing_schema
|
@@ -0,0 +1,22 @@
|
|
1
|
+
"""
|
2
|
+
Module for handling existing schema references.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from pyopenapi_gen.ir import IRSchema
|
6
|
+
|
7
|
+
from ....context import ParsingContext
|
8
|
+
|
9
|
+
|
10
|
+
def handle_existing_schema(ref_name: str, context: ParsingContext) -> IRSchema:
|
11
|
+
"""
|
12
|
+
Handles an existing schema reference.
|
13
|
+
|
14
|
+
Contracts:
|
15
|
+
Pre-conditions:
|
16
|
+
- ref_name must be a valid schema name
|
17
|
+
- context must be a valid ParsingContext instance
|
18
|
+
- ref_name must exist in context.parsed_schemas
|
19
|
+
Post-conditions:
|
20
|
+
- Returns the existing schema from context.parsed_schemas
|
21
|
+
"""
|
22
|
+
return context.parsed_schemas[ref_name]
|
@@ -0,0 +1,54 @@
|
|
1
|
+
"""
|
2
|
+
Module for handling ListResponse fallback strategy.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any, Callable, Mapping, Optional
|
7
|
+
|
8
|
+
from pyopenapi_gen.ir import IRSchema
|
9
|
+
|
10
|
+
from ....context import ParsingContext
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
def try_list_response_fallback(
|
16
|
+
ref_name: str,
|
17
|
+
ref_value: str,
|
18
|
+
context: ParsingContext,
|
19
|
+
max_depth: int,
|
20
|
+
parse_fn: Callable[[Optional[str], Optional[Mapping[str, Any]], ParsingContext, int], IRSchema],
|
21
|
+
) -> Optional[IRSchema]:
|
22
|
+
"""
|
23
|
+
Attempts to resolve a reference by treating it as a list of a base type.
|
24
|
+
|
25
|
+
Contracts:
|
26
|
+
Pre-conditions:
|
27
|
+
- ref_name must end with "ListResponse"
|
28
|
+
- parse_fn must be a callable that parses schemas
|
29
|
+
- context must be a valid ParsingContext instance
|
30
|
+
Post-conditions:
|
31
|
+
- If successful, returns an array IRSchema with items of the base type
|
32
|
+
- If unsuccessful, returns None
|
33
|
+
- Successful resolutions are added to context.parsed_schemas
|
34
|
+
"""
|
35
|
+
list_response_suffix = "ListResponse"
|
36
|
+
if not ref_name.endswith(list_response_suffix):
|
37
|
+
return None
|
38
|
+
|
39
|
+
base_name = ref_name[: -len(list_response_suffix)]
|
40
|
+
referenced_node_data_fallback = context.raw_spec_schemas.get(base_name)
|
41
|
+
|
42
|
+
if not referenced_node_data_fallback:
|
43
|
+
return None
|
44
|
+
|
45
|
+
item_schema = parse_fn(base_name, referenced_node_data_fallback, context, max_depth)
|
46
|
+
if item_schema._from_unresolved_ref:
|
47
|
+
return None
|
48
|
+
|
49
|
+
warning_msg = f"Resolved $ref: {ref_value} by falling back to LIST of base name '{base_name}'."
|
50
|
+
context.collected_warnings.append(warning_msg)
|
51
|
+
|
52
|
+
resolved_schema = IRSchema(name=ref_name, type="array", items=item_schema)
|
53
|
+
context.parsed_schemas[ref_name] = resolved_schema
|
54
|
+
return resolved_schema
|
@@ -0,0 +1,52 @@
|
|
1
|
+
"""
|
2
|
+
Module for handling missing schema references.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any, Callable, Mapping, Optional
|
7
|
+
|
8
|
+
from pyopenapi_gen.ir import IRSchema
|
9
|
+
|
10
|
+
from ....context import ParsingContext
|
11
|
+
from .list_response import try_list_response_fallback
|
12
|
+
from .stripped_suffix import try_stripped_suffix_fallback
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
def handle_missing_ref(
|
18
|
+
ref_value: str,
|
19
|
+
ref_name: str,
|
20
|
+
context: ParsingContext,
|
21
|
+
max_depth: int,
|
22
|
+
parse_fn: Callable[[Optional[str], Optional[Mapping[str, Any]], ParsingContext, int], IRSchema],
|
23
|
+
) -> IRSchema:
|
24
|
+
"""
|
25
|
+
Handles a missing schema reference by attempting fallback strategies.
|
26
|
+
|
27
|
+
Contracts:
|
28
|
+
Pre-conditions:
|
29
|
+
- ref_value must be a valid reference string
|
30
|
+
- ref_name must be a valid schema name
|
31
|
+
- context must be a valid ParsingContext instance
|
32
|
+
- max_depth must be a non-negative integer
|
33
|
+
- parse_fn must be a callable that parses schemas
|
34
|
+
Post-conditions:
|
35
|
+
- Returns a valid IRSchema instance
|
36
|
+
- The schema is registered in context.parsed_schemas
|
37
|
+
- If no fallback succeeds, returns an unresolved schema
|
38
|
+
"""
|
39
|
+
# Try ListResponse fallback
|
40
|
+
list_response_schema = try_list_response_fallback(ref_name, ref_value, context, max_depth, parse_fn)
|
41
|
+
if list_response_schema is not None:
|
42
|
+
return list_response_schema
|
43
|
+
|
44
|
+
# Try stripped suffix fallback
|
45
|
+
stripped_schema = try_stripped_suffix_fallback(ref_name, ref_value, context, max_depth, parse_fn)
|
46
|
+
if stripped_schema is not None:
|
47
|
+
return stripped_schema
|
48
|
+
|
49
|
+
# If all fallbacks fail, create an unresolved schema
|
50
|
+
unresolved_schema = IRSchema(name=ref_name, _from_unresolved_ref=True)
|
51
|
+
context.parsed_schemas[ref_name] = unresolved_schema
|
52
|
+
return unresolved_schema
|
@@ -0,0 +1,50 @@
|
|
1
|
+
"""
|
2
|
+
Module for handling new schema references.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any, Callable, Dict, Mapping, Optional
|
7
|
+
|
8
|
+
from pyopenapi_gen.ir import IRSchema
|
9
|
+
|
10
|
+
from ....context import ParsingContext
|
11
|
+
from .cyclic_properties import mark_cyclic_property_references
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
def parse_new_schema(
|
17
|
+
ref_name: str,
|
18
|
+
node_data: Dict[str, Any],
|
19
|
+
context: ParsingContext,
|
20
|
+
max_depth: int,
|
21
|
+
parse_fn: Callable[[Optional[str], Optional[Mapping[str, Any]], ParsingContext, int], IRSchema],
|
22
|
+
) -> IRSchema:
|
23
|
+
"""
|
24
|
+
Parses a new schema from raw data.
|
25
|
+
|
26
|
+
Contracts:
|
27
|
+
Pre-conditions:
|
28
|
+
- ref_name must be a valid schema name not already fully parsed
|
29
|
+
- node_data must contain raw schema definition
|
30
|
+
- parse_fn must be a callable that parses schemas
|
31
|
+
- context must be a valid ParsingContext instance
|
32
|
+
Post-conditions:
|
33
|
+
- Returns a valid parsed IRSchema instance
|
34
|
+
- The schema is registered in context.parsed_schemas
|
35
|
+
- Cyclic property references are marked correctly
|
36
|
+
"""
|
37
|
+
# Create stub to prevent infinite recursion during parsing
|
38
|
+
stub_schema = IRSchema(name=ref_name)
|
39
|
+
context.parsed_schemas[ref_name] = stub_schema
|
40
|
+
|
41
|
+
# Parse the actual schema
|
42
|
+
schema_obj = parse_fn(ref_name, node_data, context, max_depth)
|
43
|
+
|
44
|
+
# Update the entry in parsed_schemas with the fully parsed schema
|
45
|
+
context.parsed_schemas[ref_name] = schema_obj
|
46
|
+
|
47
|
+
# Mark any property references involved in cycles
|
48
|
+
mark_cyclic_property_references(schema_obj, ref_name, context)
|
49
|
+
|
50
|
+
return schema_obj
|
@@ -0,0 +1,51 @@
|
|
1
|
+
"""
|
2
|
+
Module for handling stripped suffix fallback strategy.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any, Callable, Mapping, Optional
|
7
|
+
|
8
|
+
from pyopenapi_gen.ir import IRSchema
|
9
|
+
|
10
|
+
from ....context import ParsingContext
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
def try_stripped_suffix_fallback(
|
16
|
+
ref_name: str,
|
17
|
+
ref_value: str,
|
18
|
+
context: ParsingContext,
|
19
|
+
max_depth: int,
|
20
|
+
parse_fn: Callable[[Optional[str], Optional[Mapping[str, Any]], ParsingContext, int], IRSchema],
|
21
|
+
) -> Optional[IRSchema]:
|
22
|
+
"""
|
23
|
+
Attempts to resolve a reference by stripping common suffixes.
|
24
|
+
|
25
|
+
Contracts:
|
26
|
+
Pre-conditions:
|
27
|
+
- ref_name must be a valid schema name
|
28
|
+
- parse_fn must be a callable that parses schemas
|
29
|
+
- context must be a valid ParsingContext instance
|
30
|
+
Post-conditions:
|
31
|
+
- If successful, returns the resolved IRSchema
|
32
|
+
- If unsuccessful, returns None
|
33
|
+
- Successful resolutions are added to context.parsed_schemas
|
34
|
+
"""
|
35
|
+
suffixes = ["Response", "Request", "Input", "Output"]
|
36
|
+
|
37
|
+
for suffix in suffixes:
|
38
|
+
if ref_name.endswith(suffix):
|
39
|
+
base_name = ref_name[: -len(suffix)]
|
40
|
+
referenced_node_data_fallback = context.raw_spec_schemas.get(base_name)
|
41
|
+
|
42
|
+
if referenced_node_data_fallback:
|
43
|
+
resolved_schema = parse_fn(base_name, referenced_node_data_fallback, context, max_depth)
|
44
|
+
if not resolved_schema._from_unresolved_ref:
|
45
|
+
warning_msg = f"Resolved $ref: {ref_value} by falling back to base name '{base_name}'."
|
46
|
+
context.collected_warnings.append(warning_msg)
|
47
|
+
|
48
|
+
context.parsed_schemas[ref_name] = resolved_schema
|
49
|
+
return resolved_schema
|
50
|
+
|
51
|
+
return None
|
@@ -0,0 +1,86 @@
|
|
1
|
+
"""
|
2
|
+
Main module for schema reference resolution.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any, Callable, Mapping, Optional
|
7
|
+
|
8
|
+
from pyopenapi_gen.ir import IRSchema
|
9
|
+
|
10
|
+
from ...context import ParsingContext
|
11
|
+
from .helpers.direct_cycle import handle_direct_cycle
|
12
|
+
from .helpers.existing_schema import handle_existing_schema
|
13
|
+
from .helpers.missing_ref import handle_missing_ref
|
14
|
+
from .helpers.new_schema import parse_new_schema
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
def resolve_schema_ref(
|
20
|
+
ref_value: str,
|
21
|
+
ref_name: str,
|
22
|
+
context: ParsingContext,
|
23
|
+
max_depth: int,
|
24
|
+
_parse_schema: Callable[[Optional[str], Optional[Mapping[str, Any]], ParsingContext, int], IRSchema],
|
25
|
+
) -> IRSchema:
|
26
|
+
"""
|
27
|
+
Resolves a schema reference in an OpenAPI specification.
|
28
|
+
|
29
|
+
Contracts:
|
30
|
+
Pre-conditions:
|
31
|
+
- ref_value must be a valid reference string (e.g., "#/components/schemas/MySchema")
|
32
|
+
- ref_name must be a valid schema name
|
33
|
+
- context must be a valid ParsingContext instance
|
34
|
+
- max_depth must be a non-negative integer
|
35
|
+
- _parse_schema must be a callable that parses schemas
|
36
|
+
Post-conditions:
|
37
|
+
- Returns a valid IRSchema instance
|
38
|
+
- The schema is registered in context.parsed_schemas
|
39
|
+
- Cyclic references are handled appropriately
|
40
|
+
"""
|
41
|
+
# Extract the actual schema name from the reference
|
42
|
+
actual_schema_name = ref_value.split("/")[-1]
|
43
|
+
|
44
|
+
# Check for direct cycles or circular placeholders
|
45
|
+
if actual_schema_name in context.parsed_schemas:
|
46
|
+
existing_schema = context.parsed_schemas[actual_schema_name]
|
47
|
+
if getattr(existing_schema, "_is_circular_ref", False):
|
48
|
+
# logger.debug(f"Returning existing circular reference for '{actual_schema_name}'")
|
49
|
+
return existing_schema
|
50
|
+
# logger.debug(f"Direct cycle detected for '{actual_schema_name}', handling...")
|
51
|
+
return handle_direct_cycle(actual_schema_name, context)
|
52
|
+
|
53
|
+
# Check for existing fully parsed schema
|
54
|
+
if actual_schema_name in context.parsed_schemas:
|
55
|
+
# logger.debug(f"Returning existing fully parsed schema for '{actual_schema_name}'")
|
56
|
+
return handle_existing_schema(actual_schema_name, context)
|
57
|
+
|
58
|
+
# Get referenced node data
|
59
|
+
referenced_node_data = context.raw_spec_schemas.get(actual_schema_name)
|
60
|
+
|
61
|
+
# Handle missing references with stripped suffix fallback
|
62
|
+
if referenced_node_data is None:
|
63
|
+
# Try stripping common suffixes
|
64
|
+
base_name = actual_schema_name
|
65
|
+
for suffix in ["Response", "Request", "Input", "Output"]:
|
66
|
+
if base_name.endswith(suffix):
|
67
|
+
base_name = base_name[: -len(suffix)]
|
68
|
+
if base_name in context.raw_spec_schemas:
|
69
|
+
# logger.debug(f"Found schema '{base_name}' after stripping suffix '{suffix}'")
|
70
|
+
referenced_node_data = context.raw_spec_schemas[base_name]
|
71
|
+
break
|
72
|
+
|
73
|
+
if referenced_node_data is None:
|
74
|
+
logger.warning(f"Missing reference '{ref_value}' for schema '{ref_name}'")
|
75
|
+
return handle_missing_ref(ref_value, ref_name, context, max_depth, _parse_schema)
|
76
|
+
|
77
|
+
# Standard parsing path for a new schema
|
78
|
+
# logger.debug(f"Parsing new schema '{actual_schema_name}'")
|
79
|
+
schema = parse_new_schema(actual_schema_name, dict(referenced_node_data), context, max_depth, _parse_schema)
|
80
|
+
|
81
|
+
# Store the schema under the requested reference name if different
|
82
|
+
# Don't mutate the original schema name to avoid affecting other references
|
83
|
+
if schema.name != ref_name:
|
84
|
+
context.parsed_schemas[ref_name] = schema
|
85
|
+
|
86
|
+
return schema
|
@@ -0,0 +1,74 @@
|
|
1
|
+
"""
|
2
|
+
Dedicated parser for determining primary type and nullability from a schema's 'type' field.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
from typing import (
|
8
|
+
Any,
|
9
|
+
List,
|
10
|
+
Optional,
|
11
|
+
Tuple,
|
12
|
+
)
|
13
|
+
|
14
|
+
# Note: IRSchema is not needed here as this function doesn't construct it.
|
15
|
+
|
16
|
+
|
17
|
+
def extract_primary_type_and_nullability(
|
18
|
+
type_node: Any, schema_name: Optional[str] = None
|
19
|
+
) -> Tuple[Optional[str], bool, List[str]]:
|
20
|
+
"""Extract the primary type and nullability from a schema's 'type' field.
|
21
|
+
|
22
|
+
Contracts:
|
23
|
+
Pre-conditions:
|
24
|
+
- type_node is the value of the 'type' field from a schema
|
25
|
+
Post-conditions:
|
26
|
+
- Returns a tuple of (primary_type, is_nullable, warnings)
|
27
|
+
- primary_type is None if type_node is None or invalid
|
28
|
+
- is_nullable is True if type_node is 'null' or contains 'null'
|
29
|
+
- warnings contains any warnings about type handling
|
30
|
+
"""
|
31
|
+
warnings: List[str] = []
|
32
|
+
is_nullable = False
|
33
|
+
|
34
|
+
if type_node is None:
|
35
|
+
return None, False, warnings
|
36
|
+
|
37
|
+
# Handle array of types
|
38
|
+
if isinstance(type_node, list):
|
39
|
+
if not type_node:
|
40
|
+
warnings.append(f"Empty type array in schema '{schema_name}'")
|
41
|
+
return None, False, warnings
|
42
|
+
|
43
|
+
# Check for nullability
|
44
|
+
if "null" in type_node:
|
45
|
+
is_nullable = True
|
46
|
+
type_node = [t for t in type_node if t != "null"]
|
47
|
+
|
48
|
+
if not type_node:
|
49
|
+
warnings.append(f"Only 'null' type in array for schema '{schema_name}'")
|
50
|
+
return None, True, warnings
|
51
|
+
|
52
|
+
# Use the first non-null type
|
53
|
+
primary_type = type_node[0]
|
54
|
+
if len(type_node) > 1:
|
55
|
+
warnings.append(f"Multiple types in array for schema '{schema_name}'. Using first type: {primary_type}")
|
56
|
+
else:
|
57
|
+
primary_type = type_node
|
58
|
+
|
59
|
+
# Validate the type
|
60
|
+
if not isinstance(primary_type, str):
|
61
|
+
warnings.append(f"Invalid type value '{primary_type}' in schema '{schema_name}'")
|
62
|
+
return None, is_nullable, warnings
|
63
|
+
|
64
|
+
# Normalize the type
|
65
|
+
primary_type = primary_type.lower()
|
66
|
+
if primary_type not in {"string", "number", "integer", "boolean", "object", "array", "null"}:
|
67
|
+
warnings.append(f"Unknown type '{primary_type}' in schema '{schema_name}'")
|
68
|
+
return None, is_nullable, warnings
|
69
|
+
|
70
|
+
# If the determined primary_type is "null", it means the actual type is None, but it IS nullable.
|
71
|
+
if primary_type == "null":
|
72
|
+
return None, True, warnings # Ensure is_nullable is True
|
73
|
+
|
74
|
+
return primary_type, is_nullable, warnings
|