pyopenapi-gen 2.7.2__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 +224 -0
- pyopenapi_gen/__main__.py +6 -0
- pyopenapi_gen/cli.py +62 -0
- pyopenapi_gen/context/CLAUDE.md +284 -0
- pyopenapi_gen/context/file_manager.py +52 -0
- pyopenapi_gen/context/import_collector.py +382 -0
- pyopenapi_gen/context/render_context.py +726 -0
- pyopenapi_gen/core/CLAUDE.md +224 -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/cattrs_converter.py +810 -0
- pyopenapi_gen/core/exceptions.py +20 -0
- pyopenapi_gen/core/http_status_codes.py +218 -0
- pyopenapi_gen/core/http_transport.py +222 -0
- pyopenapi_gen/core/loader/__init__.py +12 -0
- pyopenapi_gen/core/loader/loader.py +174 -0
- pyopenapi_gen/core/loader/operations/__init__.py +12 -0
- pyopenapi_gen/core/loader/operations/parser.py +161 -0
- pyopenapi_gen/core/loader/operations/post_processor.py +62 -0
- pyopenapi_gen/core/loader/operations/request_body.py +90 -0
- pyopenapi_gen/core/loader/parameters/__init__.py +10 -0
- pyopenapi_gen/core/loader/parameters/parser.py +186 -0
- pyopenapi_gen/core/loader/responses/__init__.py +10 -0
- pyopenapi_gen/core/loader/responses/parser.py +111 -0
- pyopenapi_gen/core/loader/schemas/__init__.py +11 -0
- pyopenapi_gen/core/loader/schemas/extractor.py +275 -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 +73 -0
- pyopenapi_gen/core/parsing/context.py +187 -0
- pyopenapi_gen/core/parsing/cycle_helpers.py +126 -0
- pyopenapi_gen/core/parsing/keywords/__init__.py +1 -0
- pyopenapi_gen/core/parsing/keywords/all_of_parser.py +81 -0
- pyopenapi_gen/core/parsing/keywords/any_of_parser.py +84 -0
- pyopenapi_gen/core/parsing/keywords/array_items_parser.py +72 -0
- pyopenapi_gen/core/parsing/keywords/one_of_parser.py +77 -0
- pyopenapi_gen/core/parsing/keywords/properties_parser.py +98 -0
- pyopenapi_gen/core/parsing/schema_finalizer.py +169 -0
- pyopenapi_gen/core/parsing/schema_parser.py +804 -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 +120 -0
- pyopenapi_gen/core/parsing/unified_cycle_detection.py +293 -0
- pyopenapi_gen/core/postprocess_manager.py +260 -0
- pyopenapi_gen/core/spec_fetcher.py +148 -0
- pyopenapi_gen/core/streaming_helpers.py +84 -0
- pyopenapi_gen/core/telemetry.py +69 -0
- pyopenapi_gen/core/utils.py +456 -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 +321 -0
- pyopenapi_gen/core_package_template/README.md +21 -0
- pyopenapi_gen/emit/models_emitter.py +143 -0
- pyopenapi_gen/emitters/CLAUDE.md +286 -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 +247 -0
- pyopenapi_gen/emitters/exceptions_emitter.py +187 -0
- pyopenapi_gen/emitters/mocks_emitter.py +185 -0
- pyopenapi_gen/emitters/models_emitter.py +426 -0
- pyopenapi_gen/generator/CLAUDE.md +352 -0
- pyopenapi_gen/generator/client_generator.py +567 -0
- pyopenapi_gen/generator/exceptions.py +7 -0
- pyopenapi_gen/helpers/CLAUDE.md +325 -0
- pyopenapi_gen/helpers/__init__.py +1 -0
- pyopenapi_gen/helpers/endpoint_utils.py +532 -0
- pyopenapi_gen/helpers/type_cleaner.py +334 -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 +105 -0
- pyopenapi_gen/helpers/type_resolution/named_resolver.py +172 -0
- pyopenapi_gen/helpers/type_resolution/object_resolver.py +216 -0
- pyopenapi_gen/helpers/type_resolution/primitive_resolver.py +109 -0
- pyopenapi_gen/helpers/type_resolution/resolver.py +47 -0
- pyopenapi_gen/helpers/url_utils.py +14 -0
- pyopenapi_gen/http_types.py +20 -0
- pyopenapi_gen/ir.py +165 -0
- pyopenapi_gen/py.typed +1 -0
- pyopenapi_gen/types/CLAUDE.md +140 -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 +28 -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 +177 -0
- pyopenapi_gen/types/resolvers/schema_resolver.py +498 -0
- pyopenapi_gen/types/services/__init__.py +5 -0
- pyopenapi_gen/types/services/type_service.py +165 -0
- pyopenapi_gen/types/strategies/__init__.py +5 -0
- pyopenapi_gen/types/strategies/response_strategy.py +310 -0
- pyopenapi_gen/visit/CLAUDE.md +272 -0
- pyopenapi_gen/visit/client_visitor.py +477 -0
- pyopenapi_gen/visit/docs_visitor.py +38 -0
- pyopenapi_gen/visit/endpoint/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/endpoint_visitor.py +292 -0
- pyopenapi_gen/visit/endpoint/generators/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +123 -0
- pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py +222 -0
- pyopenapi_gen/visit/endpoint/generators/mock_generator.py +140 -0
- pyopenapi_gen/visit/endpoint/generators/overload_generator.py +252 -0
- pyopenapi_gen/visit/endpoint/generators/request_generator.py +103 -0
- pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +705 -0
- pyopenapi_gen/visit/endpoint/generators/signature_generator.py +83 -0
- pyopenapi_gen/visit/endpoint/generators/url_args_generator.py +207 -0
- pyopenapi_gen/visit/endpoint/processors/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +78 -0
- pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +171 -0
- pyopenapi_gen/visit/exception_visitor.py +90 -0
- pyopenapi_gen/visit/model/__init__.py +0 -0
- pyopenapi_gen/visit/model/alias_generator.py +93 -0
- pyopenapi_gen/visit/model/dataclass_generator.py +553 -0
- pyopenapi_gen/visit/model/enum_generator.py +212 -0
- pyopenapi_gen/visit/model/model_visitor.py +198 -0
- pyopenapi_gen/visit/visitor.py +97 -0
- pyopenapi_gen-2.7.2.dist-info/METADATA +1169 -0
- pyopenapi_gen-2.7.2.dist-info/RECORD +137 -0
- pyopenapi_gen-2.7.2.dist-info/WHEEL +4 -0
- pyopenapi_gen-2.7.2.dist-info/entry_points.txt +2 -0
- pyopenapi_gen-2.7.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parser for 'anyOf' keyword in OpenAPI schemas.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, List, Mapping
|
|
8
|
+
|
|
9
|
+
from pyopenapi_gen import IRSchema # Main IR model
|
|
10
|
+
|
|
11
|
+
from ..context import ParsingContext # Context object - MOVED
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
# from ..context import ParsingContext # No longer here
|
|
15
|
+
# No direct import of _parse_schema from schema_parser to avoid circularity
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _parse_any_of_schemas(
|
|
20
|
+
any_of_nodes: List[Mapping[str, Any]],
|
|
21
|
+
context: ParsingContext,
|
|
22
|
+
max_depth: int,
|
|
23
|
+
parse_fn: Callable[ # Accepts the main schema parsing function
|
|
24
|
+
[str | None, Mapping[str, Any] | None, ParsingContext, int], IRSchema
|
|
25
|
+
],
|
|
26
|
+
) -> tuple[List[IRSchema] | None, bool, str | None]:
|
|
27
|
+
"""Parses 'anyOf' sub-schemas using a provided parsing function.
|
|
28
|
+
|
|
29
|
+
Contracts:
|
|
30
|
+
Pre-conditions:
|
|
31
|
+
- any_of_nodes is a list of schema node mappings.
|
|
32
|
+
- context is a valid ParsingContext instance.
|
|
33
|
+
- max_depth >= 0.
|
|
34
|
+
- parse_fn is a callable that can parse a schema node.
|
|
35
|
+
Post-conditions:
|
|
36
|
+
- Returns a tuple: (parsed_schemas, is_nullable, effective_schema_type)
|
|
37
|
+
- parsed_schemas: List of IRSchema for non-null sub-schemas, or None.
|
|
38
|
+
- is_nullable: True if a null type was present.
|
|
39
|
+
- effective_schema_type: Potential schema_type if list becomes empty/None (currently always None).
|
|
40
|
+
"""
|
|
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")
|
|
51
|
+
|
|
52
|
+
parsed_schemas_list: List[IRSchema] = [] # Renamed to avoid confusion with module name
|
|
53
|
+
is_nullable_from_any_of = False
|
|
54
|
+
effective_schema_type: str | None = None
|
|
55
|
+
|
|
56
|
+
for sub_node in any_of_nodes:
|
|
57
|
+
if isinstance(sub_node, dict) and sub_node.get("type") == "null":
|
|
58
|
+
is_nullable_from_any_of = True
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
parsed_schemas_list.append(parse_fn(None, sub_node, context, max_depth))
|
|
62
|
+
|
|
63
|
+
filtered_schemas = [
|
|
64
|
+
s
|
|
65
|
+
for s in parsed_schemas_list
|
|
66
|
+
if not (
|
|
67
|
+
s.type is None
|
|
68
|
+
and not s.properties
|
|
69
|
+
and not s.items
|
|
70
|
+
and not s.enum
|
|
71
|
+
and not s.any_of
|
|
72
|
+
and not s.one_of
|
|
73
|
+
and not s.all_of
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
if not filtered_schemas:
|
|
78
|
+
effective_schema_type = None
|
|
79
|
+
return None, is_nullable_from_any_of, effective_schema_type
|
|
80
|
+
|
|
81
|
+
return filtered_schemas, is_nullable_from_any_of, effective_schema_type
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ... existing code ...
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dedicated parser for handling 'items' within an array schema.
|
|
3
|
+
Renamed from array_parser.py for clarity.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Callable, Mapping
|
|
9
|
+
|
|
10
|
+
from pyopenapi_gen import IRSchema
|
|
11
|
+
|
|
12
|
+
from ..context import ParsingContext
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
# from pyopenapi_gen import IRSchema # Already above or handled by context
|
|
16
|
+
# from ..context import ParsingContext # No longer here
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _parse_array_items_schema(
|
|
21
|
+
parent_schema_name: str | None,
|
|
22
|
+
items_node_data: Mapping[str, Any],
|
|
23
|
+
context: ParsingContext,
|
|
24
|
+
parse_fn: Callable[ # Accepts the main schema parsing function
|
|
25
|
+
[str | None, Mapping[str, Any] | None, ParsingContext, int], IRSchema
|
|
26
|
+
],
|
|
27
|
+
max_depth: int,
|
|
28
|
+
) -> IRSchema | None:
|
|
29
|
+
"""Parses the 'items' sub-schema of an array.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
parent_schema_name: The name of the parent array schema (if any).
|
|
33
|
+
items_node_data: The raw dictionary of the 'items' schema.
|
|
34
|
+
context: The parsing context.
|
|
35
|
+
parse_fn: The main schema parsing function to call recursively (_parse_schema from schema_parser.py).
|
|
36
|
+
max_depth: Maximum recursion depth.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The parsed IRSchema for the items, or None if items_node_data is not suitable.
|
|
40
|
+
|
|
41
|
+
Contracts:
|
|
42
|
+
Pre-conditions:
|
|
43
|
+
- items_node_data is a mapping representing the items schema.
|
|
44
|
+
- context is a valid ParsingContext instance.
|
|
45
|
+
- parse_fn is a callable function.
|
|
46
|
+
- max_depth is a non-negative integer.
|
|
47
|
+
Post-conditions:
|
|
48
|
+
- Returns an IRSchema if items_node_data is a valid schema mapping.
|
|
49
|
+
- Returns None if items_node_data is not a mapping.
|
|
50
|
+
- Calls parse_fn with an appropriate name for the item schema.
|
|
51
|
+
"""
|
|
52
|
+
# Pre-conditions
|
|
53
|
+
# items_node_data is checked later, as it can be non-Mapping to return None
|
|
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")
|
|
60
|
+
|
|
61
|
+
item_name_for_parse = f"{parent_schema_name}Item" if parent_schema_name else None
|
|
62
|
+
if (
|
|
63
|
+
isinstance(items_node_data, dict)
|
|
64
|
+
and "$ref" in items_node_data
|
|
65
|
+
and items_node_data["$ref"].startswith("#/components/schemas/")
|
|
66
|
+
):
|
|
67
|
+
item_name_for_parse = items_node_data["$ref"].split("/")[-1]
|
|
68
|
+
|
|
69
|
+
if not isinstance(items_node_data, Mapping):
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
return parse_fn(item_name_for_parse, items_node_data, context, max_depth)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parser for 'oneOf' keyword in OpenAPI schemas.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, List, Mapping
|
|
8
|
+
|
|
9
|
+
from pyopenapi_gen import IRSchema
|
|
10
|
+
|
|
11
|
+
from ..context import ParsingContext
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
# from ..context import ParsingContext # No longer here
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _parse_one_of_schemas(
|
|
19
|
+
one_of_nodes: List[Mapping[str, Any]],
|
|
20
|
+
context: ParsingContext,
|
|
21
|
+
max_depth: int,
|
|
22
|
+
parse_fn: Callable[[str | None, Mapping[str, Any] | None, ParsingContext, int], IRSchema],
|
|
23
|
+
) -> tuple[List[IRSchema] | None, bool, str | None]:
|
|
24
|
+
"""Parses 'oneOf' sub-schemas using a provided parsing function.
|
|
25
|
+
|
|
26
|
+
Contracts:
|
|
27
|
+
Pre-conditions:
|
|
28
|
+
- one_of_nodes is a list of schema node mappings.
|
|
29
|
+
- context is a valid ParsingContext instance.
|
|
30
|
+
- max_depth >= 0.
|
|
31
|
+
- parse_fn is a callable that can parse a schema node.
|
|
32
|
+
Post-conditions:
|
|
33
|
+
- Returns a tuple: (parsed_schemas, is_nullable, effective_schema_type)
|
|
34
|
+
- parsed_schemas: List of IRSchema for non-null sub-schemas, or None.
|
|
35
|
+
- is_nullable: True if a null type was present.
|
|
36
|
+
- effective_schema_type: Potential schema_type if list becomes empty/None (currently always None).
|
|
37
|
+
"""
|
|
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")
|
|
48
|
+
|
|
49
|
+
parsed_schemas_list: List[IRSchema] = []
|
|
50
|
+
is_nullable_from_one_of = False
|
|
51
|
+
effective_schema_type: str | None = None
|
|
52
|
+
|
|
53
|
+
for sub_node in one_of_nodes:
|
|
54
|
+
if isinstance(sub_node, dict) and sub_node.get("type") == "null":
|
|
55
|
+
is_nullable_from_one_of = True
|
|
56
|
+
continue
|
|
57
|
+
parsed_schemas_list.append(parse_fn(None, sub_node, context, max_depth))
|
|
58
|
+
|
|
59
|
+
filtered_schemas = [
|
|
60
|
+
s
|
|
61
|
+
for s in parsed_schemas_list
|
|
62
|
+
if not (
|
|
63
|
+
s.type is None
|
|
64
|
+
and not s.properties
|
|
65
|
+
and not s.items
|
|
66
|
+
and not s.enum
|
|
67
|
+
and not s.any_of
|
|
68
|
+
and not s.one_of
|
|
69
|
+
and not s.all_of
|
|
70
|
+
)
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
if not filtered_schemas:
|
|
74
|
+
effective_schema_type = None
|
|
75
|
+
return None, is_nullable_from_one_of, effective_schema_type
|
|
76
|
+
|
|
77
|
+
return filtered_schemas, is_nullable_from_one_of, effective_schema_type
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, Mapping
|
|
5
|
+
|
|
6
|
+
# Import NameSanitizer for use in name generation
|
|
7
|
+
from pyopenapi_gen.core.utils import NameSanitizer
|
|
8
|
+
|
|
9
|
+
# REVERTED: TEMPORARY DEBUG LOGGER REMOVED
|
|
10
|
+
# properties_logger = logging.getLogger("pyopenapi_gen.core.parsing.keywords.properties_parser_DEBUG")
|
|
11
|
+
# properties_logger.setLevel(logging.DEBUG)
|
|
12
|
+
# # Ensure there's a handler if running in some environments
|
|
13
|
+
# if not properties_logger.handlers:
|
|
14
|
+
# properties_logger.addHandler(logging.StreamHandler())
|
|
15
|
+
from ..context import ParsingContext
|
|
16
|
+
from ..transformers.inline_object_promoter import _attempt_promote_inline_object
|
|
17
|
+
|
|
18
|
+
# Specific helpers needed by _parse_properties
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from pyopenapi_gen import IRSchema # Main IR model
|
|
22
|
+
|
|
23
|
+
# from ..context import ParsingContext # No longer here
|
|
24
|
+
# No direct import of _parse_schema from schema_parser to avoid circularity
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _parse_properties(
|
|
29
|
+
properties_node: Mapping[str, Any],
|
|
30
|
+
parent_schema_name: str | None,
|
|
31
|
+
context: ParsingContext,
|
|
32
|
+
max_depth: int,
|
|
33
|
+
parse_fn: Callable[[str | None, Mapping[str, Any] | None, ParsingContext, int | None], IRSchema],
|
|
34
|
+
logger: logging.Logger,
|
|
35
|
+
) -> dict[str, IRSchema]:
|
|
36
|
+
"""Parse properties from a schema node.
|
|
37
|
+
|
|
38
|
+
Contracts:
|
|
39
|
+
Pre-conditions:
|
|
40
|
+
- properties_node is a valid Mapping
|
|
41
|
+
- context is a valid ParsingContext instance
|
|
42
|
+
- max_depth >= 0
|
|
43
|
+
- parse_fn is a callable for parsing schemas
|
|
44
|
+
Post-conditions:
|
|
45
|
+
- Returns a dictionary mapping property names to IRSchema instances
|
|
46
|
+
- Property references are properly maintained
|
|
47
|
+
"""
|
|
48
|
+
properties_map: dict[str, IRSchema] = {}
|
|
49
|
+
|
|
50
|
+
for prop_key, prop_schema_node in properties_node.items():
|
|
51
|
+
# Skip invalid property names
|
|
52
|
+
if not prop_key or not isinstance(prop_key, str):
|
|
53
|
+
logger.warning(f"Skipping property with invalid name: {prop_key}")
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
# Align with _parse_schema property naming logic:
|
|
57
|
+
# ParentNamePropName or just PropName if parent is anonymous.
|
|
58
|
+
sanitized_prop_key_for_name = NameSanitizer.sanitize_class_name(prop_key)
|
|
59
|
+
if parent_schema_name:
|
|
60
|
+
# Ensure parent_schema_name is also sanitized if it's part of the name
|
|
61
|
+
# This is implicitly handled if parent_schema_name comes from an IRSchema.name already.
|
|
62
|
+
# For direct use, ensure it's PascalCase for consistency,
|
|
63
|
+
# though _parse_schema currently doesn't re-sanitize it.
|
|
64
|
+
# Assuming parent_schema_name is already a valid schema name (e.g. "ParentSchema")
|
|
65
|
+
prop_schema_context_name = f"{parent_schema_name}{sanitized_prop_key_for_name}"
|
|
66
|
+
else:
|
|
67
|
+
prop_schema_context_name = sanitized_prop_key_for_name
|
|
68
|
+
|
|
69
|
+
# Parse the property schema
|
|
70
|
+
prop_schema_ir = parse_fn(prop_schema_context_name, prop_schema_node, context, max_depth)
|
|
71
|
+
|
|
72
|
+
# Handle $ref in property schema
|
|
73
|
+
if isinstance(prop_schema_node, Mapping) and "$ref" in prop_schema_node:
|
|
74
|
+
ref_value = prop_schema_node["$ref"]
|
|
75
|
+
ref_name = ref_value.split("/")[-1]
|
|
76
|
+
if ref_name in context.parsed_schemas:
|
|
77
|
+
prop_schema_ir._refers_to_schema = context.parsed_schemas[ref_name]
|
|
78
|
+
|
|
79
|
+
# Attempt to promote inline object
|
|
80
|
+
promoted_ir = _attempt_promote_inline_object(parent_schema_name, prop_key, prop_schema_ir, context, logger)
|
|
81
|
+
if promoted_ir is not None:
|
|
82
|
+
properties_map[prop_key] = promoted_ir
|
|
83
|
+
logger.debug(
|
|
84
|
+
f"Added promoted '{prop_key}' (name: {getattr(promoted_ir, 'name', 'N/A')}) "
|
|
85
|
+
f"to properties_map for '{parent_schema_name}'"
|
|
86
|
+
)
|
|
87
|
+
else:
|
|
88
|
+
properties_map[prop_key] = prop_schema_ir
|
|
89
|
+
logger.debug(
|
|
90
|
+
f"Added original '{prop_key}' (name: {getattr(prop_schema_ir, 'name', 'N/A')}, "
|
|
91
|
+
f"type: {getattr(prop_schema_ir, 'type', 'N/A')}, "
|
|
92
|
+
f"circular: {getattr(prop_schema_ir, '_is_circular_ref', 'N/A')}) "
|
|
93
|
+
f"to properties_map for '{parent_schema_name}'"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
logger.debug(f"_parse_properties FINALLY returning for parent '{parent_schema_name}': {properties_map}")
|
|
97
|
+
# import pdb; pdb.set_trace() # For local debugging if needed
|
|
98
|
+
return properties_map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Finalization an IRSchema object during OpenAPI parsing.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Set, Union
|
|
8
|
+
|
|
9
|
+
from pyopenapi_gen import IRSchema
|
|
10
|
+
from pyopenapi_gen.core.utils import NameSanitizer
|
|
11
|
+
|
|
12
|
+
from .context import ParsingContext
|
|
13
|
+
|
|
14
|
+
# Specific helpers needed
|
|
15
|
+
from .transformers.inline_enum_extractor import _process_standalone_inline_enum
|
|
16
|
+
|
|
17
|
+
# Note: _parse_schema will be passed as parse_fn, not imported directly
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _finalize_schema_object(
|
|
24
|
+
name: str | None,
|
|
25
|
+
node: Mapping[str, Any],
|
|
26
|
+
context: ParsingContext,
|
|
27
|
+
schema_type: str | None,
|
|
28
|
+
is_nullable: bool,
|
|
29
|
+
any_of_schemas: List[IRSchema] | None,
|
|
30
|
+
one_of_schemas: List[IRSchema] | None,
|
|
31
|
+
parsed_all_of_components: List[IRSchema] | None,
|
|
32
|
+
final_properties_map: dict[str, IRSchema],
|
|
33
|
+
merged_required_set: Set[str],
|
|
34
|
+
final_items_schema: IRSchema | None,
|
|
35
|
+
additional_properties_node: Union[bool, Mapping[str, Any]] | None,
|
|
36
|
+
enum_node: List[Any] | None,
|
|
37
|
+
format_node: str | None,
|
|
38
|
+
description_node: str | None,
|
|
39
|
+
from_unresolved_ref_node: bool,
|
|
40
|
+
max_depth: int,
|
|
41
|
+
parse_fn: Callable[[str | None, Mapping[str, Any] | None, ParsingContext, int], IRSchema],
|
|
42
|
+
logger: Any, # Changed to Any to support both real and mock loggers
|
|
43
|
+
) -> IRSchema:
|
|
44
|
+
"""Constructs the IRSchema object, performs final adjustments, and updates context.
|
|
45
|
+
|
|
46
|
+
Contracts:
|
|
47
|
+
Pre-conditions:
|
|
48
|
+
- All input arguments are appropriately populated.
|
|
49
|
+
- context is valid.
|
|
50
|
+
- parse_fn is a callable for parsing (e.g. additionalProperties).
|
|
51
|
+
- logger is a logging.Logger instance or a mock logger for testing.
|
|
52
|
+
Post-conditions:
|
|
53
|
+
- Returns a finalized IRSchema instance.
|
|
54
|
+
- If schema has a name and isn't a placeholder, it's in context.parsed_schemas.
|
|
55
|
+
"""
|
|
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")
|
|
62
|
+
# Remove logger type check to support mock loggers in tests
|
|
63
|
+
|
|
64
|
+
# If a placeholder for this schema name already exists due to a cycle detected deeper,
|
|
65
|
+
# return that placeholder immediately to preserve the cycle information.
|
|
66
|
+
if name and name in context.parsed_schemas:
|
|
67
|
+
existing_schema = context.parsed_schemas[name]
|
|
68
|
+
if getattr(existing_schema, "_is_circular_ref", False) or getattr(
|
|
69
|
+
existing_schema, "_from_unresolved_ref", False
|
|
70
|
+
):
|
|
71
|
+
# Early return of cycle placeholder
|
|
72
|
+
return existing_schema
|
|
73
|
+
|
|
74
|
+
final_enum_values: List[Any] | None = enum_node if isinstance(enum_node, list) else None
|
|
75
|
+
final_required_fields_list: List[str] = sorted(list(merged_required_set))
|
|
76
|
+
|
|
77
|
+
final_additional_properties: Union[bool, IRSchema] | None = None
|
|
78
|
+
if isinstance(additional_properties_node, bool):
|
|
79
|
+
final_additional_properties = additional_properties_node
|
|
80
|
+
elif isinstance(additional_properties_node, dict):
|
|
81
|
+
final_additional_properties = parse_fn(None, additional_properties_node, context, max_depth)
|
|
82
|
+
|
|
83
|
+
final_schema_name_for_obj = NameSanitizer.sanitize_class_name(name) if name else None
|
|
84
|
+
current_schema_type = schema_type
|
|
85
|
+
if current_schema_type is None and final_properties_map:
|
|
86
|
+
current_schema_type = "object"
|
|
87
|
+
|
|
88
|
+
is_data_wrapper_flag = (
|
|
89
|
+
current_schema_type == "object"
|
|
90
|
+
and "data" in final_properties_map
|
|
91
|
+
and "data" in final_required_fields_list
|
|
92
|
+
and len(final_properties_map) == 1
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
schema_obj = IRSchema(
|
|
96
|
+
name=final_schema_name_for_obj,
|
|
97
|
+
type=current_schema_type,
|
|
98
|
+
format=format_node,
|
|
99
|
+
description=description_node,
|
|
100
|
+
required=final_required_fields_list,
|
|
101
|
+
properties=final_properties_map,
|
|
102
|
+
items=final_items_schema,
|
|
103
|
+
enum=final_enum_values,
|
|
104
|
+
additional_properties=final_additional_properties,
|
|
105
|
+
is_nullable=is_nullable,
|
|
106
|
+
any_of=any_of_schemas,
|
|
107
|
+
one_of=one_of_schemas,
|
|
108
|
+
all_of=parsed_all_of_components,
|
|
109
|
+
is_data_wrapper=is_data_wrapper_flag,
|
|
110
|
+
_from_unresolved_ref=from_unresolved_ref_node,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
schema_obj.name
|
|
115
|
+
and schema_obj.name in context.parsed_schemas
|
|
116
|
+
and context.parsed_schemas[schema_obj.name].type is None
|
|
117
|
+
and schema_obj.type is not None
|
|
118
|
+
):
|
|
119
|
+
# Named schema in context has no type, adopting type
|
|
120
|
+
# schema_obj will be placed in context later, overwriting if necessary.
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
if schema_obj.type is None and (
|
|
124
|
+
schema_obj.properties
|
|
125
|
+
or (
|
|
126
|
+
isinstance(node.get("_def"), dict)
|
|
127
|
+
and node["_def"].get("typeName") == "ZodObject"
|
|
128
|
+
and node["_def"].get("shape")
|
|
129
|
+
)
|
|
130
|
+
):
|
|
131
|
+
# Schema has properties but no type; setting to 'object'
|
|
132
|
+
schema_obj.type = "object"
|
|
133
|
+
|
|
134
|
+
if name and "." in name:
|
|
135
|
+
is_explicitly_simple = node.get("type") in ["string", "integer", "number", "boolean", "array"]
|
|
136
|
+
is_explicitly_enum = "enum" in node
|
|
137
|
+
if schema_obj.type is None and not is_explicitly_simple and not is_explicitly_enum:
|
|
138
|
+
# Inline property lacks complex type, defaulting to 'object'
|
|
139
|
+
schema_obj.type = "object"
|
|
140
|
+
|
|
141
|
+
# Update the context with the finalized schema object
|
|
142
|
+
# This should only happen if we didn't return an existing placeholder above.
|
|
143
|
+
if name:
|
|
144
|
+
# Never overwrite a cycle placeholder!
|
|
145
|
+
if name in context.parsed_schemas and (
|
|
146
|
+
getattr(context.parsed_schemas[name], "_is_circular_ref", False)
|
|
147
|
+
or getattr(context.parsed_schemas[name], "_from_unresolved_ref", False)
|
|
148
|
+
):
|
|
149
|
+
# Not overwriting cycle placeholder
|
|
150
|
+
pass # Do not overwrite
|
|
151
|
+
elif name not in context.parsed_schemas:
|
|
152
|
+
context.parsed_schemas[name] = schema_obj
|
|
153
|
+
# If name is in context and is already a full schema, and schema_obj is also full,
|
|
154
|
+
# current one is considered fresher.
|
|
155
|
+
elif not (
|
|
156
|
+
getattr(context.parsed_schemas[name], "_is_circular_ref", False)
|
|
157
|
+
or getattr(context.parsed_schemas[name], "_from_unresolved_ref", False)
|
|
158
|
+
):
|
|
159
|
+
context.parsed_schemas[name] = schema_obj # Overwrite existing full with new full
|
|
160
|
+
|
|
161
|
+
if schema_obj:
|
|
162
|
+
schema_obj = _process_standalone_inline_enum(name, node, schema_obj, context, logger)
|
|
163
|
+
|
|
164
|
+
if schema_obj and schema_obj.name and context.parsed_schemas.get(schema_obj.name) is not schema_obj:
|
|
165
|
+
context.parsed_schemas[schema_obj.name] = schema_obj
|
|
166
|
+
# Ensured schema is in context post-standalone-enum processing
|
|
167
|
+
|
|
168
|
+
# Returning finalized schema object
|
|
169
|
+
return schema_obj
|