pyopenapi-gen 0.8.3__py3-none-any.whl → 0.8.6__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/cli.py +5 -22
- pyopenapi_gen/context/import_collector.py +8 -8
- pyopenapi_gen/core/loader/operations/parser.py +1 -1
- pyopenapi_gen/core/parsing/context.py +2 -1
- pyopenapi_gen/core/parsing/cycle_helpers.py +1 -1
- pyopenapi_gen/core/parsing/keywords/properties_parser.py +4 -4
- pyopenapi_gen/core/parsing/schema_parser.py +4 -4
- pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +1 -1
- pyopenapi_gen/core/postprocess_manager.py +39 -13
- pyopenapi_gen/core/schemas.py +101 -16
- pyopenapi_gen/core/utils.py +8 -3
- pyopenapi_gen/core/writers/python_construct_renderer.py +57 -9
- pyopenapi_gen/emitters/endpoints_emitter.py +1 -1
- pyopenapi_gen/helpers/endpoint_utils.py +4 -22
- pyopenapi_gen/helpers/type_cleaner.py +1 -1
- pyopenapi_gen/helpers/type_resolution/composition_resolver.py +1 -1
- pyopenapi_gen/helpers/type_resolution/finalizer.py +1 -1
- pyopenapi_gen/types/contracts/types.py +0 -1
- pyopenapi_gen/types/resolvers/response_resolver.py +5 -33
- pyopenapi_gen/types/resolvers/schema_resolver.py +2 -2
- pyopenapi_gen/types/services/type_service.py +0 -18
- pyopenapi_gen/types/strategies/__init__.py +5 -0
- pyopenapi_gen/types/strategies/response_strategy.py +187 -0
- pyopenapi_gen/visit/endpoint/endpoint_visitor.py +1 -20
- pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +5 -3
- pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py +12 -6
- pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +352 -343
- pyopenapi_gen/visit/endpoint/generators/signature_generator.py +7 -4
- pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +4 -2
- pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +1 -1
- pyopenapi_gen/visit/model/dataclass_generator.py +32 -1
- pyopenapi_gen-0.8.6.dist-info/METADATA +383 -0
- {pyopenapi_gen-0.8.3.dist-info → pyopenapi_gen-0.8.6.dist-info}/RECORD +36 -34
- pyopenapi_gen-0.8.3.dist-info/METADATA +0 -224
- {pyopenapi_gen-0.8.3.dist-info → pyopenapi_gen-0.8.6.dist-info}/WHEEL +0 -0
- {pyopenapi_gen-0.8.3.dist-info → pyopenapi_gen-0.8.6.dist-info}/entry_points.txt +0 -0
- {pyopenapi_gen-0.8.3.dist-info → pyopenapi_gen-0.8.6.dist-info}/licenses/LICENSE +0 -0
@@ -58,28 +58,21 @@ class OpenAPIResponseResolver(ResponseTypeResolver):
|
|
58
58
|
if hasattr(response, "ref") and response.ref:
|
59
59
|
return self._resolve_response_reference(response.ref, context)
|
60
60
|
|
61
|
+
# Handle streaming responses (check before content validation)
|
62
|
+
if hasattr(response, "stream") and response.stream:
|
63
|
+
return self._resolve_streaming_response(response, context)
|
64
|
+
|
61
65
|
# Handle responses without content (e.g., 204)
|
62
66
|
if not hasattr(response, "content") or not response.content:
|
63
67
|
return ResolvedType(python_type="None")
|
64
68
|
|
65
|
-
# Handle streaming responses
|
66
|
-
if hasattr(response, "stream") and response.stream:
|
67
|
-
return self._resolve_streaming_response(response, context)
|
68
|
-
|
69
69
|
# Get the content schema
|
70
70
|
schema = self._get_response_schema(response)
|
71
71
|
if not schema:
|
72
72
|
return ResolvedType(python_type="None")
|
73
73
|
|
74
|
-
# Resolve the schema
|
74
|
+
# Resolve the schema directly (no unwrapping)
|
75
75
|
resolved = self.schema_resolver.resolve_schema(schema, context, required=True)
|
76
|
-
|
77
|
-
# Check for data unwrapping
|
78
|
-
unwrapped = self._try_unwrap_data_property(schema, context)
|
79
|
-
if unwrapped:
|
80
|
-
unwrapped.was_unwrapped = True
|
81
|
-
return unwrapped
|
82
|
-
|
83
76
|
return resolved
|
84
77
|
|
85
78
|
def _resolve_response_reference(self, ref: str, context: TypeContext) -> ResolvedType:
|
@@ -136,27 +129,6 @@ class OpenAPIResponseResolver(ResponseTypeResolver):
|
|
136
129
|
|
137
130
|
return response.content.get(content_type)
|
138
131
|
|
139
|
-
def _try_unwrap_data_property(self, schema: IRSchema | None, context: TypeContext) -> Optional[ResolvedType]:
|
140
|
-
"""
|
141
|
-
Try to unwrap a 'data' property if the schema is a wrapper.
|
142
|
-
|
143
|
-
Returns unwrapped type or None if not applicable.
|
144
|
-
"""
|
145
|
-
if not schema or not hasattr(schema, "type") or schema.type != "object":
|
146
|
-
return None
|
147
|
-
|
148
|
-
properties = getattr(schema, "properties", None)
|
149
|
-
if not properties or len(properties) != 1:
|
150
|
-
return None
|
151
|
-
|
152
|
-
# Check for 'data' property
|
153
|
-
data_property = properties.get("data")
|
154
|
-
if not data_property:
|
155
|
-
return None
|
156
|
-
|
157
|
-
logger.info(f"Unwrapping 'data' property from response schema")
|
158
|
-
return self.schema_resolver.resolve_schema(data_property, context, required=True)
|
159
|
-
|
160
132
|
def _resolve_streaming_response(self, response: IRResponse, context: TypeContext) -> ResolvedType:
|
161
133
|
"""
|
162
134
|
Resolve a streaming response to an AsyncIterator type.
|
@@ -307,7 +307,7 @@ class OpenAPISchemaResolver(SchemaTypeResolver):
|
|
307
307
|
# Sort types for consistent ordering
|
308
308
|
resolved_types.sort()
|
309
309
|
context.add_import("typing", "Union")
|
310
|
-
union_type = f"Union[{
|
310
|
+
union_type = f"Union[{", ".join(resolved_types)}]"
|
311
311
|
|
312
312
|
return ResolvedType(python_type=union_type, is_optional=not required)
|
313
313
|
|
@@ -362,6 +362,6 @@ class OpenAPISchemaResolver(SchemaTypeResolver):
|
|
362
362
|
# Sort types for consistent ordering
|
363
363
|
resolved_types.sort()
|
364
364
|
context.add_import("typing", "Union")
|
365
|
-
union_type = f"Union[{
|
365
|
+
union_type = f"Union[{", ".join(resolved_types)}]"
|
366
366
|
|
367
367
|
return ResolvedType(python_type=union_type, is_optional=not required)
|
@@ -85,24 +85,6 @@ class UnifiedTypeService:
|
|
85
85
|
resolved = self.response_resolver.resolve_operation_response(operation, type_context)
|
86
86
|
return self._format_resolved_type(resolved, context)
|
87
87
|
|
88
|
-
def resolve_operation_response_with_unwrap_info(
|
89
|
-
self, operation: IROperation, context: RenderContext
|
90
|
-
) -> tuple[str, bool]:
|
91
|
-
"""
|
92
|
-
Resolve an operation's response to a Python type string with unwrapping information.
|
93
|
-
|
94
|
-
Args:
|
95
|
-
operation: The operation to resolve
|
96
|
-
context: Render context for imports
|
97
|
-
|
98
|
-
Returns:
|
99
|
-
Tuple of (Python type string, was_unwrapped flag)
|
100
|
-
"""
|
101
|
-
type_context = RenderContextAdapter(context)
|
102
|
-
resolved = self.response_resolver.resolve_operation_response(operation, type_context)
|
103
|
-
python_type = self._format_resolved_type(resolved, context)
|
104
|
-
return python_type, resolved.was_unwrapped
|
105
|
-
|
106
88
|
def resolve_response_type(self, response: IRResponse, context: RenderContext) -> str:
|
107
89
|
"""
|
108
90
|
Resolve a specific response to a Python type string.
|
@@ -0,0 +1,187 @@
|
|
1
|
+
"""Unified response handling strategy.
|
2
|
+
|
3
|
+
This module provides a single source of truth for how operation responses should be handled,
|
4
|
+
eliminating the scattered responsibility that was causing Data_ vs proper schema name issues.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from __future__ import annotations
|
8
|
+
|
9
|
+
import logging
|
10
|
+
from dataclasses import dataclass
|
11
|
+
from typing import Dict, Optional
|
12
|
+
|
13
|
+
from pyopenapi_gen import IROperation, IRResponse, IRSchema
|
14
|
+
from pyopenapi_gen.context.render_context import RenderContext
|
15
|
+
from pyopenapi_gen.types.services.type_service import UnifiedTypeService
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
@dataclass
|
21
|
+
class ResponseStrategy:
|
22
|
+
"""Unified strategy for handling a specific operation's response.
|
23
|
+
|
24
|
+
This class encapsulates all decisions about how to handle a response:
|
25
|
+
- What type to use in method signatures (matches OpenAPI schema exactly)
|
26
|
+
- Which schema to use for deserialization
|
27
|
+
- How to generate the response handling code
|
28
|
+
"""
|
29
|
+
|
30
|
+
return_type: str # The Python type for method signature
|
31
|
+
response_schema: Optional[IRSchema] # The response schema as defined in OpenAPI spec
|
32
|
+
is_streaming: bool # Whether this is a streaming response
|
33
|
+
|
34
|
+
# Additional context for code generation
|
35
|
+
response_ir: Optional[IRResponse] # The original response IR
|
36
|
+
|
37
|
+
|
38
|
+
class ResponseStrategyResolver:
|
39
|
+
"""Single source of truth for response handling decisions.
|
40
|
+
|
41
|
+
This resolver examines an operation and its responses to determine the optimal
|
42
|
+
strategy for handling the response. It replaces the scattered logic that was
|
43
|
+
previously spread across multiple components.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def __init__(self, schemas: Dict[str, IRSchema]):
|
47
|
+
self.schemas = schemas
|
48
|
+
self.type_service = UnifiedTypeService(schemas)
|
49
|
+
|
50
|
+
def resolve(self, operation: IROperation, context: RenderContext) -> ResponseStrategy:
|
51
|
+
"""Determine how to handle this operation's response.
|
52
|
+
|
53
|
+
Uses the response schema exactly as defined in the OpenAPI spec,
|
54
|
+
with no unwrapping logic. What you see in the spec is what you get.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
operation: The operation to analyze
|
58
|
+
context: Render context for type resolution
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
A ResponseStrategy that all components should use consistently
|
62
|
+
"""
|
63
|
+
primary_response = self._get_primary_response(operation)
|
64
|
+
|
65
|
+
if not primary_response:
|
66
|
+
return ResponseStrategy(return_type="None", response_schema=None, is_streaming=False, response_ir=None)
|
67
|
+
|
68
|
+
# Handle responses without content (e.g., 204)
|
69
|
+
if not hasattr(primary_response, "content") or not primary_response.content:
|
70
|
+
return ResponseStrategy(
|
71
|
+
return_type="None", response_schema=None, is_streaming=False, response_ir=primary_response
|
72
|
+
)
|
73
|
+
|
74
|
+
# Handle streaming responses
|
75
|
+
if hasattr(primary_response, "stream") and primary_response.stream:
|
76
|
+
return self._resolve_streaming_strategy(primary_response, context)
|
77
|
+
|
78
|
+
# Get the response schema
|
79
|
+
response_schema = self._get_response_schema(primary_response)
|
80
|
+
if not response_schema:
|
81
|
+
return ResponseStrategy(
|
82
|
+
return_type="None", response_schema=None, is_streaming=False, response_ir=primary_response
|
83
|
+
)
|
84
|
+
|
85
|
+
# Use the response schema as-is from the OpenAPI spec
|
86
|
+
return_type = self.type_service.resolve_schema_type(response_schema, context, required=True)
|
87
|
+
|
88
|
+
return ResponseStrategy(
|
89
|
+
return_type=return_type, response_schema=response_schema, is_streaming=False, response_ir=primary_response
|
90
|
+
)
|
91
|
+
|
92
|
+
def _get_primary_response(self, operation: IROperation) -> Optional[IRResponse]:
|
93
|
+
"""Get the primary success response from an operation."""
|
94
|
+
if not operation.responses:
|
95
|
+
return None
|
96
|
+
|
97
|
+
# Priority order: 200, 201, 202, 204, other 2xx, default
|
98
|
+
for code in ["200", "201", "202", "204"]:
|
99
|
+
for response in operation.responses:
|
100
|
+
if response.status_code == code:
|
101
|
+
return response
|
102
|
+
|
103
|
+
# Other 2xx responses
|
104
|
+
for response in operation.responses:
|
105
|
+
if response.status_code.startswith("2"):
|
106
|
+
return response
|
107
|
+
|
108
|
+
# Default response
|
109
|
+
for response in operation.responses:
|
110
|
+
if response.status_code == "default":
|
111
|
+
return response
|
112
|
+
|
113
|
+
# First response as fallback
|
114
|
+
return operation.responses[0] if operation.responses else None
|
115
|
+
|
116
|
+
def _get_response_schema(self, response: IRResponse) -> Optional[IRSchema]:
|
117
|
+
"""Get the schema from a response's content."""
|
118
|
+
if not response.content:
|
119
|
+
return None
|
120
|
+
|
121
|
+
# Prefer application/json
|
122
|
+
content_types = list(response.content.keys())
|
123
|
+
content_type = None
|
124
|
+
|
125
|
+
if "application/json" in content_types:
|
126
|
+
content_type = "application/json"
|
127
|
+
elif any("json" in ct for ct in content_types):
|
128
|
+
content_type = next(ct for ct in content_types if "json" in ct)
|
129
|
+
elif content_types:
|
130
|
+
content_type = content_types[0]
|
131
|
+
|
132
|
+
if not content_type:
|
133
|
+
return None
|
134
|
+
|
135
|
+
return response.content.get(content_type)
|
136
|
+
|
137
|
+
def _resolve_streaming_strategy(self, response: IRResponse, context: RenderContext) -> ResponseStrategy:
|
138
|
+
"""Resolve strategy for streaming responses."""
|
139
|
+
# Add AsyncIterator import
|
140
|
+
context.add_import("typing", "AsyncIterator")
|
141
|
+
|
142
|
+
# Determine the item type for the stream
|
143
|
+
if not response.content:
|
144
|
+
# Binary stream with no specific content type
|
145
|
+
return ResponseStrategy(
|
146
|
+
return_type="AsyncIterator[bytes]", response_schema=None, is_streaming=True, response_ir=response
|
147
|
+
)
|
148
|
+
|
149
|
+
# Check for binary content types
|
150
|
+
content_types = list(response.content.keys())
|
151
|
+
is_binary = any(
|
152
|
+
ct in ["application/octet-stream", "application/pdf"] or ct.startswith(("image/", "audio/", "video/"))
|
153
|
+
for ct in content_types
|
154
|
+
)
|
155
|
+
|
156
|
+
if is_binary:
|
157
|
+
return ResponseStrategy(
|
158
|
+
return_type="AsyncIterator[bytes]", response_schema=None, is_streaming=True, response_ir=response
|
159
|
+
)
|
160
|
+
|
161
|
+
# For event streams (text/event-stream) or JSON streams
|
162
|
+
is_event_stream = any("event-stream" in ct for ct in content_types)
|
163
|
+
if is_event_stream:
|
164
|
+
context.add_import("typing", "Dict")
|
165
|
+
context.add_import("typing", "Any")
|
166
|
+
return ResponseStrategy(
|
167
|
+
return_type="AsyncIterator[Dict[str, Any]]",
|
168
|
+
response_schema=None,
|
169
|
+
is_streaming=True,
|
170
|
+
response_ir=response,
|
171
|
+
)
|
172
|
+
|
173
|
+
# For other streaming content, try to resolve the schema
|
174
|
+
schema = self._get_response_schema(response)
|
175
|
+
if schema:
|
176
|
+
schema_type = self.type_service.resolve_schema_type(schema, context, required=True)
|
177
|
+
return ResponseStrategy(
|
178
|
+
return_type=f"AsyncIterator[{schema_type}]",
|
179
|
+
response_schema=schema,
|
180
|
+
is_streaming=True,
|
181
|
+
response_ir=response,
|
182
|
+
)
|
183
|
+
|
184
|
+
# Default to bytes if we can't determine the type
|
185
|
+
return ResponseStrategy(
|
186
|
+
return_type="AsyncIterator[bytes]", response_schema=None, is_streaming=True, response_ir=response
|
187
|
+
)
|
@@ -2,10 +2,8 @@ import logging
|
|
2
2
|
from typing import Any
|
3
3
|
|
4
4
|
from pyopenapi_gen import IROperation
|
5
|
-
from pyopenapi_gen.helpers.endpoint_utils import (
|
6
|
-
get_return_type_unified,
|
7
|
-
)
|
8
5
|
|
6
|
+
# No longer need endpoint utils helpers - using ResponseStrategy pattern
|
9
7
|
from ...context.render_context import RenderContext
|
10
8
|
from ...core.utils import NameSanitizer
|
11
9
|
from ...core.writers.code_writer import CodeWriter
|
@@ -84,20 +82,3 @@ class EndpointVisitor(Visitor[IROperation, str]):
|
|
84
82
|
|
85
83
|
writer.dedent() # Dedent to close the class block
|
86
84
|
return writer.get_code()
|
87
|
-
|
88
|
-
def _get_response_return_type_details(self, context: RenderContext, op: IROperation) -> tuple[str, bool, bool, str]:
|
89
|
-
"""Gets type details for the endpoint response."""
|
90
|
-
# Check if this is a streaming response (either at op level or in schema)
|
91
|
-
is_streaming = any(getattr(resp, "stream", False) for resp in op.responses if resp.status_code.startswith("2"))
|
92
|
-
|
93
|
-
# Get the primary Python type for the operation's success response using unified service
|
94
|
-
return_type = get_return_type_unified(op, context, self.schemas)
|
95
|
-
should_unwrap = False # Unified service handles unwrapping internally
|
96
|
-
|
97
|
-
# Determine the summary description (for docstring)
|
98
|
-
success_resp = next((r for r in op.responses if r.status_code.startswith("2")), None)
|
99
|
-
return_description = (
|
100
|
-
success_resp.description if success_resp and success_resp.description else "Successful operation"
|
101
|
-
)
|
102
|
-
|
103
|
-
return return_type, should_unwrap, is_streaming, return_description
|
@@ -10,11 +10,12 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
10
10
|
|
11
11
|
from pyopenapi_gen.core.writers.code_writer import CodeWriter
|
12
12
|
from pyopenapi_gen.core.writers.documentation_writer import DocumentationBlock, DocumentationWriter
|
13
|
-
from pyopenapi_gen.helpers.endpoint_utils import get_param_type, get_request_body_type
|
13
|
+
from pyopenapi_gen.helpers.endpoint_utils import get_param_type, get_request_body_type
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
16
16
|
from pyopenapi_gen import IROperation
|
17
17
|
from pyopenapi_gen.context.render_context import RenderContext
|
18
|
+
from pyopenapi_gen.types.strategies.response_strategy import ResponseStrategy
|
18
19
|
|
19
20
|
logger = logging.getLogger(__name__)
|
20
21
|
|
@@ -50,6 +51,7 @@ class EndpointDocstringGenerator:
|
|
50
51
|
op: IROperation,
|
51
52
|
context: RenderContext,
|
52
53
|
primary_content_type: Optional[str],
|
54
|
+
response_strategy: ResponseStrategy,
|
53
55
|
) -> None:
|
54
56
|
"""Writes the method docstring to the provided CodeWriter."""
|
55
57
|
summary = op.summary or None
|
@@ -75,7 +77,7 @@ class EndpointDocstringGenerator:
|
|
75
77
|
else: # Fallback for other types like application/octet-stream
|
76
78
|
args.append(("bytes_content", "bytes", body_desc + f" ({primary_content_type})"))
|
77
79
|
|
78
|
-
return_type =
|
80
|
+
return_type = response_strategy.return_type
|
79
81
|
response_desc = None
|
80
82
|
# Prioritize 2xx success codes for the main response description
|
81
83
|
for code in ("200", "201", "202", "default"): # Include default as it might be the success response
|
@@ -97,7 +99,7 @@ class EndpointDocstringGenerator:
|
|
97
99
|
for resp in error_codes:
|
98
100
|
# Using a generic HTTPError, specific error classes could be mapped later
|
99
101
|
code_to_raise = "HTTPError"
|
100
|
-
desc = f"{resp.status_code}: {resp.description.strip() if resp.description else
|
102
|
+
desc = f"{resp.status_code}: {resp.description.strip() if resp.description else "HTTP error."}"
|
101
103
|
raises.append((code_to_raise, desc))
|
102
104
|
else:
|
103
105
|
raises.append(("HTTPError", "If the server returns a non-2xx HTTP response."))
|
@@ -6,6 +6,7 @@ from pyopenapi_gen import IROperation
|
|
6
6
|
from ....context.render_context import RenderContext
|
7
7
|
from ....core.utils import Formatter
|
8
8
|
from ....core.writers.code_writer import CodeWriter
|
9
|
+
from ....types.strategies import ResponseStrategyResolver
|
9
10
|
from ..processors.import_analyzer import EndpointImportAnalyzer
|
10
11
|
from ..processors.parameter_processor import EndpointParameterProcessor
|
11
12
|
from .docstring_generator import EndpointDocstringGenerator
|
@@ -43,16 +44,21 @@ class EndpointMethodGenerator:
|
|
43
44
|
context.add_import(f"{context.core_package_name}.http_transport", "HttpTransport")
|
44
45
|
context.add_import(f"{context.core_package_name}.exceptions", "HTTPError")
|
45
46
|
|
46
|
-
#
|
47
|
+
# UNIFIED RESPONSE STRATEGY: Resolve once, use everywhere
|
48
|
+
strategy_resolver = ResponseStrategyResolver(self.schemas)
|
49
|
+
response_strategy = strategy_resolver.resolve(op, context)
|
47
50
|
|
48
|
-
|
51
|
+
# Pass the response strategy to import analyzer for consistent import resolution
|
52
|
+
self.import_analyzer.analyze_and_register_imports(op, context, response_strategy)
|
49
53
|
|
50
54
|
ordered_params, primary_content_type, resolved_body_type = self.parameter_processor.process_parameters(
|
51
55
|
op, context
|
52
56
|
)
|
53
|
-
self.signature_generator.generate_signature(writer, op, context, ordered_params)
|
54
57
|
|
55
|
-
|
58
|
+
# Pass strategy to generators for consistent behavior
|
59
|
+
self.signature_generator.generate_signature(writer, op, context, ordered_params, response_strategy)
|
60
|
+
|
61
|
+
self.docstring_generator.generate_docstring(writer, op, context, primary_content_type, response_strategy)
|
56
62
|
|
57
63
|
# Snapshot of code *before* main body parts are written
|
58
64
|
# This includes signature and docstring.
|
@@ -63,8 +69,8 @@ class EndpointMethodGenerator:
|
|
63
69
|
)
|
64
70
|
self.request_generator.generate_request_call(writer, op, context, has_header_params, primary_content_type)
|
65
71
|
|
66
|
-
# Call the new response handler generator
|
67
|
-
self.response_handler_generator.generate_response_handling(writer, op, context)
|
72
|
+
# Call the new response handler generator with strategy
|
73
|
+
self.response_handler_generator.generate_response_handling(writer, op, context, response_strategy)
|
68
74
|
|
69
75
|
# Check if any actual statements were added for the body
|
70
76
|
current_full_code = writer.get_code()
|