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,352 @@
|
|
|
1
|
+
# generator/ - Main Orchestration
|
|
2
|
+
|
|
3
|
+
## Why This Folder?
|
|
4
|
+
High-level orchestration of the entire code generation pipeline. Coordinates loader → parser → visitors → emitters → post-processing flow.
|
|
5
|
+
|
|
6
|
+
## Key Dependencies
|
|
7
|
+
- **Input**: CLI arguments, OpenAPI spec path
|
|
8
|
+
- **Output**: Complete generated client package
|
|
9
|
+
- **Orchestrates**: All other system components
|
|
10
|
+
- **Error Handling**: `GenerationError` for CLI reporting
|
|
11
|
+
|
|
12
|
+
## Essential Architecture
|
|
13
|
+
|
|
14
|
+
### 1. Generation Pipeline
|
|
15
|
+
```python
|
|
16
|
+
# client_generator.py
|
|
17
|
+
def generate(self, spec_path: str, project_root: Path, output_package: str,
|
|
18
|
+
force: bool = False, no_postprocess: bool = False,
|
|
19
|
+
core_package: str = None) -> None:
|
|
20
|
+
|
|
21
|
+
# 1. Load OpenAPI spec
|
|
22
|
+
spec = self.load_spec(spec_path)
|
|
23
|
+
|
|
24
|
+
# 2. Parse to IR
|
|
25
|
+
ir_spec = self.parse_to_ir(spec)
|
|
26
|
+
|
|
27
|
+
# 3. Generate code
|
|
28
|
+
self.generate_code(ir_spec, project_root, output_package, core_package)
|
|
29
|
+
|
|
30
|
+
# 4. Post-process (format, typecheck)
|
|
31
|
+
if not no_postprocess:
|
|
32
|
+
self.post_process(project_root, output_package)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Diff Checking
|
|
36
|
+
```python
|
|
37
|
+
def generate_with_diff_check(self, ...) -> None:
|
|
38
|
+
if not force and self.output_exists():
|
|
39
|
+
# Generate to temporary location
|
|
40
|
+
temp_output = self.create_temp_output()
|
|
41
|
+
self.generate_to_path(temp_output)
|
|
42
|
+
|
|
43
|
+
# Compare with existing
|
|
44
|
+
if self.has_changes(temp_output, self.final_output):
|
|
45
|
+
self.prompt_user_for_confirmation()
|
|
46
|
+
|
|
47
|
+
# Move temp to final location
|
|
48
|
+
self.move_temp_to_final(temp_output, self.final_output)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Critical Components
|
|
52
|
+
|
|
53
|
+
### client_generator.py
|
|
54
|
+
**Purpose**: Main entry point for all generation operations
|
|
55
|
+
```python
|
|
56
|
+
class ClientGenerator:
|
|
57
|
+
def __init__(self):
|
|
58
|
+
self.loader = SpecLoader()
|
|
59
|
+
self.parser = SpecParser()
|
|
60
|
+
self.type_service = None # Created per generation
|
|
61
|
+
self.visitors = self.create_visitors()
|
|
62
|
+
self.emitters = self.create_emitters()
|
|
63
|
+
self.post_processor = PostProcessManager()
|
|
64
|
+
|
|
65
|
+
def generate(self, spec_path: str, project_root: Path, output_package: str,
|
|
66
|
+
force: bool = False, no_postprocess: bool = False,
|
|
67
|
+
core_package: str = None) -> None:
|
|
68
|
+
"""Main generation method called by CLI"""
|
|
69
|
+
try:
|
|
70
|
+
self.validate_inputs(spec_path, project_root, output_package)
|
|
71
|
+
self.run_generation_pipeline(...)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
raise GenerationError(f"Generation failed: {e}")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Generation Workflow
|
|
77
|
+
```python
|
|
78
|
+
def run_generation_pipeline(self, spec_path: str, project_root: Path,
|
|
79
|
+
output_package: str, core_package: str) -> None:
|
|
80
|
+
|
|
81
|
+
# 1. Load and validate OpenAPI spec
|
|
82
|
+
raw_spec = self.loader.load(spec_path)
|
|
83
|
+
|
|
84
|
+
# 2. Parse to intermediate representation
|
|
85
|
+
ir_spec = self.parser.parse(raw_spec)
|
|
86
|
+
|
|
87
|
+
# 3. Create context for generation
|
|
88
|
+
context = RenderContext(project_root, output_package)
|
|
89
|
+
|
|
90
|
+
# 4. Initialize type service
|
|
91
|
+
self.type_service = UnifiedTypeService(ir_spec.schemas, ir_spec.responses)
|
|
92
|
+
|
|
93
|
+
# 5. Generate code using visitors
|
|
94
|
+
self.generate_models(ir_spec.schemas, context)
|
|
95
|
+
self.generate_endpoints(ir_spec.operations, context)
|
|
96
|
+
self.generate_client(ir_spec, context)
|
|
97
|
+
self.generate_exceptions(ir_spec.responses, context)
|
|
98
|
+
|
|
99
|
+
# 6. Emit files using emitters
|
|
100
|
+
self.emit_all_files(ir_spec, context, core_package)
|
|
101
|
+
|
|
102
|
+
# 7. Post-process (format, typecheck)
|
|
103
|
+
self.post_process_if_enabled(project_root, output_package)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Error Handling Strategy
|
|
107
|
+
|
|
108
|
+
### 1. Structured Error Hierarchy
|
|
109
|
+
```python
|
|
110
|
+
class GenerationError(Exception):
|
|
111
|
+
"""Top-level error for CLI reporting"""
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
class ValidationError(GenerationError):
|
|
115
|
+
"""Input validation failures"""
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
class ParsingError(GenerationError):
|
|
119
|
+
"""OpenAPI parsing failures"""
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
class CodeGenerationError(GenerationError):
|
|
123
|
+
"""Code generation failures"""
|
|
124
|
+
pass
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2. Error Context Collection
|
|
128
|
+
```python
|
|
129
|
+
def handle_generation_error(self, error: Exception, context: Dict[str, Any]) -> None:
|
|
130
|
+
"""Add context to errors for better debugging"""
|
|
131
|
+
error_context = {
|
|
132
|
+
"spec_path": context.get("spec_path"),
|
|
133
|
+
"output_package": context.get("output_package"),
|
|
134
|
+
"current_stage": context.get("current_stage"),
|
|
135
|
+
"current_schema": context.get("current_schema")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
detailed_message = f"Generation failed at {error_context['current_stage']}: {error}"
|
|
139
|
+
if error_context.get("current_schema"):
|
|
140
|
+
detailed_message += f" (processing schema: {error_context['current_schema']})"
|
|
141
|
+
|
|
142
|
+
raise GenerationError(detailed_message) from error
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Visitor Coordination
|
|
146
|
+
|
|
147
|
+
### 1. Visitor Initialization
|
|
148
|
+
```python
|
|
149
|
+
def create_visitors(self) -> Dict[str, Visitor]:
|
|
150
|
+
"""Create all visitors with proper dependencies"""
|
|
151
|
+
return {
|
|
152
|
+
"model": ModelVisitor(self.type_service),
|
|
153
|
+
"endpoint": EndpointVisitor(self.type_service),
|
|
154
|
+
"client": ClientVisitor(self.type_service),
|
|
155
|
+
"exception": ExceptionVisitor(self.type_service),
|
|
156
|
+
"docs": DocsVisitor()
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 2. Visitor Execution
|
|
161
|
+
```python
|
|
162
|
+
def generate_models(self, schemas: Dict[str, IRSchema], context: RenderContext) -> None:
|
|
163
|
+
"""Generate model code using visitor"""
|
|
164
|
+
model_codes = {}
|
|
165
|
+
|
|
166
|
+
for schema_name, schema in schemas.items():
|
|
167
|
+
try:
|
|
168
|
+
# Generate code for single schema
|
|
169
|
+
code = self.visitors["model"].visit_schema(schema, context)
|
|
170
|
+
model_codes[schema_name] = code
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
# Add schema context to error
|
|
174
|
+
context_info = {"current_schema": schema_name, "current_stage": "model_generation"}
|
|
175
|
+
self.handle_generation_error(e, context_info)
|
|
176
|
+
|
|
177
|
+
# Store for emitters
|
|
178
|
+
self.generated_models = model_codes
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Emitter Coordination
|
|
182
|
+
|
|
183
|
+
### 1. Emitter Initialization
|
|
184
|
+
```python
|
|
185
|
+
def create_emitters(self, output_path: Path) -> Dict[str, Emitter]:
|
|
186
|
+
"""Create all emitters with proper configuration"""
|
|
187
|
+
file_manager = FileManager(output_path)
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
"models": ModelsEmitter(output_path, file_manager),
|
|
191
|
+
"endpoints": EndpointsEmitter(output_path, file_manager),
|
|
192
|
+
"client": ClientEmitter(output_path, file_manager),
|
|
193
|
+
"core": CoreEmitter(output_path, file_manager),
|
|
194
|
+
"exceptions": ExceptionsEmitter(output_path, file_manager)
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 2. Emitter Execution
|
|
199
|
+
```python
|
|
200
|
+
def emit_all_files(self, ir_spec: IRSpec, context: RenderContext, core_package: str) -> None:
|
|
201
|
+
"""Emit all generated code to files"""
|
|
202
|
+
|
|
203
|
+
# Emit in dependency order
|
|
204
|
+
self.emitters["core"].emit_core(context.output_package, core_package)
|
|
205
|
+
self.emitters["models"].emit_models(ir_spec.schemas, context)
|
|
206
|
+
self.emitters["endpoints"].emit_endpoints(ir_spec.operations, context)
|
|
207
|
+
self.emitters["exceptions"].emit_exceptions(ir_spec.responses, context)
|
|
208
|
+
self.emitters["client"].emit_client(ir_spec, context)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Post-Processing
|
|
212
|
+
|
|
213
|
+
### 1. Format and Typecheck
|
|
214
|
+
```python
|
|
215
|
+
def post_process(self, project_root: Path, output_package: str) -> None:
|
|
216
|
+
"""Run Black formatting and mypy type checking"""
|
|
217
|
+
|
|
218
|
+
package_path = self.resolve_package_path(project_root, output_package)
|
|
219
|
+
|
|
220
|
+
# Format with Black
|
|
221
|
+
self.run_black_formatting(package_path)
|
|
222
|
+
|
|
223
|
+
# Type check with mypy
|
|
224
|
+
self.run_mypy_checking(package_path)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 2. Validation
|
|
228
|
+
```python
|
|
229
|
+
def validate_generated_code(self, package_path: Path) -> None:
|
|
230
|
+
"""Validate generated code can be imported"""
|
|
231
|
+
|
|
232
|
+
# Try to import generated client
|
|
233
|
+
try:
|
|
234
|
+
spec = importlib.util.spec_from_file_location("client", package_path / "client.py")
|
|
235
|
+
module = importlib.util.module_from_spec(spec)
|
|
236
|
+
spec.loader.exec_module(module)
|
|
237
|
+
except Exception as e:
|
|
238
|
+
raise GenerationError(f"Generated code validation failed: {e}")
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Dependencies on Other Systems
|
|
242
|
+
|
|
243
|
+
### From core/
|
|
244
|
+
- Uses `SpecLoader` for OpenAPI loading
|
|
245
|
+
- Uses `SpecParser` for IR creation
|
|
246
|
+
- Uses `PostProcessManager` for formatting
|
|
247
|
+
|
|
248
|
+
### From types/
|
|
249
|
+
- Creates `UnifiedTypeService` for visitors
|
|
250
|
+
- Coordinates type resolution across generation
|
|
251
|
+
|
|
252
|
+
### From visit/
|
|
253
|
+
- Orchestrates all visitors
|
|
254
|
+
- Manages visitor dependencies
|
|
255
|
+
|
|
256
|
+
### From emitters/
|
|
257
|
+
- Coordinates all emitters
|
|
258
|
+
- Manages file output
|
|
259
|
+
|
|
260
|
+
## Testing Requirements
|
|
261
|
+
|
|
262
|
+
### Integration Tests
|
|
263
|
+
```python
|
|
264
|
+
def test_client_generator__complete_generation__creates_working_client():
|
|
265
|
+
# Test full generation pipeline
|
|
266
|
+
generator = ClientGenerator()
|
|
267
|
+
|
|
268
|
+
# Generate client
|
|
269
|
+
generator.generate(
|
|
270
|
+
spec_path="test_spec.yaml",
|
|
271
|
+
project_root=temp_dir,
|
|
272
|
+
output_package="test_client"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Verify client works
|
|
276
|
+
assert can_import_client(temp_dir / "test_client")
|
|
277
|
+
assert client_methods_work(temp_dir / "test_client")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Error Handling Tests
|
|
281
|
+
```python
|
|
282
|
+
def test_client_generator__invalid_spec__raises_generation_error():
|
|
283
|
+
generator = ClientGenerator()
|
|
284
|
+
|
|
285
|
+
with pytest.raises(GenerationError) as exc_info:
|
|
286
|
+
generator.generate(
|
|
287
|
+
spec_path="invalid_spec.yaml",
|
|
288
|
+
project_root=temp_dir,
|
|
289
|
+
output_package="test_client"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
assert "parsing failed" in str(exc_info.value)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Extension Points
|
|
296
|
+
|
|
297
|
+
### Custom Generation Steps
|
|
298
|
+
```python
|
|
299
|
+
class CustomClientGenerator(ClientGenerator):
|
|
300
|
+
def run_generation_pipeline(self, *args, **kwargs):
|
|
301
|
+
# Add custom pre-processing
|
|
302
|
+
self.custom_pre_process()
|
|
303
|
+
|
|
304
|
+
# Run standard pipeline
|
|
305
|
+
super().run_generation_pipeline(*args, **kwargs)
|
|
306
|
+
|
|
307
|
+
# Add custom post-processing
|
|
308
|
+
self.custom_post_process()
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Plugin System
|
|
312
|
+
```python
|
|
313
|
+
def register_plugin(self, plugin: GenerationPlugin) -> None:
|
|
314
|
+
"""Register custom generation plugin"""
|
|
315
|
+
self.plugins.append(plugin)
|
|
316
|
+
|
|
317
|
+
def run_plugins(self, stage: str, context: Dict[str, Any]) -> None:
|
|
318
|
+
"""Run plugins at specific generation stage"""
|
|
319
|
+
for plugin in self.plugins:
|
|
320
|
+
if plugin.handles_stage(stage):
|
|
321
|
+
plugin.execute(context)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Critical Implementation Details
|
|
325
|
+
|
|
326
|
+
### Resource Management
|
|
327
|
+
```python
|
|
328
|
+
def generate_safely(self, *args, **kwargs) -> None:
|
|
329
|
+
"""Generate with proper resource cleanup"""
|
|
330
|
+
temp_files = []
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
# Generation logic
|
|
334
|
+
pass
|
|
335
|
+
except Exception:
|
|
336
|
+
# Clean up temporary files
|
|
337
|
+
for temp_file in temp_files:
|
|
338
|
+
if temp_file.exists():
|
|
339
|
+
temp_file.unlink()
|
|
340
|
+
raise
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Progress Reporting
|
|
344
|
+
```python
|
|
345
|
+
def generate_with_progress(self, *args, **kwargs) -> None:
|
|
346
|
+
"""Generate with progress reporting"""
|
|
347
|
+
stages = ["loading", "parsing", "code_generation", "file_emission", "post_processing"]
|
|
348
|
+
|
|
349
|
+
for i, stage in enumerate(stages):
|
|
350
|
+
print(f"[{i+1}/{len(stages)}] {stage.replace('_', ' ').title()}...")
|
|
351
|
+
self.run_stage(stage)
|
|
352
|
+
```
|