pyopenapi-gen 0.8.7__py3-none-any.whl → 0.10.1__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.
Files changed (29) hide show
  1. pyopenapi_gen/__init__.py +2 -2
  2. pyopenapi_gen/context/CLAUDE.md +284 -0
  3. pyopenapi_gen/context/import_collector.py +8 -8
  4. pyopenapi_gen/core/CLAUDE.md +224 -0
  5. pyopenapi_gen/core/loader/operations/parser.py +1 -1
  6. pyopenapi_gen/core/parsing/cycle_helpers.py +1 -1
  7. pyopenapi_gen/core/parsing/keywords/properties_parser.py +4 -4
  8. pyopenapi_gen/core/parsing/schema_parser.py +4 -4
  9. pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +1 -1
  10. pyopenapi_gen/core/writers/python_construct_renderer.py +2 -2
  11. pyopenapi_gen/emitters/CLAUDE.md +286 -0
  12. pyopenapi_gen/emitters/endpoints_emitter.py +1 -1
  13. pyopenapi_gen/generator/CLAUDE.md +352 -0
  14. pyopenapi_gen/helpers/CLAUDE.md +325 -0
  15. pyopenapi_gen/helpers/endpoint_utils.py +2 -2
  16. pyopenapi_gen/helpers/type_cleaner.py +1 -1
  17. pyopenapi_gen/helpers/type_resolution/composition_resolver.py +1 -1
  18. pyopenapi_gen/helpers/type_resolution/finalizer.py +1 -1
  19. pyopenapi_gen/types/CLAUDE.md +140 -0
  20. pyopenapi_gen/types/resolvers/schema_resolver.py +2 -2
  21. pyopenapi_gen/visit/CLAUDE.md +272 -0
  22. pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +1 -1
  23. pyopenapi_gen/visit/endpoint/generators/signature_generator.py +1 -1
  24. pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +1 -1
  25. {pyopenapi_gen-0.8.7.dist-info → pyopenapi_gen-0.10.1.dist-info}/METADATA +18 -4
  26. {pyopenapi_gen-0.8.7.dist-info → pyopenapi_gen-0.10.1.dist-info}/RECORD +29 -22
  27. {pyopenapi_gen-0.8.7.dist-info → pyopenapi_gen-0.10.1.dist-info}/WHEEL +0 -0
  28. {pyopenapi_gen-0.8.7.dist-info → pyopenapi_gen-0.10.1.dist-info}/entry_points.txt +0 -0
  29. {pyopenapi_gen-0.8.7.dist-info → pyopenapi_gen-0.10.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,286 @@
1
+ # emitters/ - File Organization and Output
2
+
3
+ ## Why This Folder?
4
+ Transform visitor-generated code strings into properly structured Python packages. Handles file creation, import resolution, and package organization.
5
+
6
+ ## Key Dependencies
7
+ - **Input**: Code strings from `../visit/` visitors
8
+ - **Output**: Python files in target package structure
9
+ - **Services**: `FileManager` from `../context/file_manager.py`
10
+ - **Context**: `RenderContext` for import management
11
+
12
+ ## Essential Architecture
13
+
14
+ ### 1. Emitter Responsibilities
15
+ ```python
16
+ # Each emitter handles one aspect of the generated client
17
+ models_emitter.py → models/ directory with dataclasses/enums
18
+ endpoints_emitter.py → endpoints/ directory with operation methods
19
+ client_emitter.py → client.py main interface
20
+ core_emitter.py → core/ directory with runtime dependencies
21
+ exceptions_emitter.py → exceptions.py error hierarchy
22
+ ```
23
+
24
+ ### 2. Package Structure Creation
25
+ ```python
26
+ # Target structure for generated client
27
+ output_package/
28
+ ├── __init__.py # Package initialization
29
+ ├── client.py # Main client class
30
+ ├── models/ # Data models
31
+ │ ├── __init__.py
32
+ │ ├── user.py
33
+ │ └── order.py
34
+ ├── endpoints/ # Operation methods
35
+ │ ├── __init__.py
36
+ │ ├── users.py
37
+ │ └── orders.py
38
+ ├── core/ # Runtime dependencies
39
+ │ ├── __init__.py
40
+ │ ├── auth/
41
+ │ ├── exceptions.py
42
+ │ └── http_transport.py
43
+ └── exceptions.py # Exception hierarchy
44
+ ```
45
+
46
+ ## Critical Components
47
+
48
+ ### models_emitter.py
49
+ **Purpose**: Create models/ directory with dataclass and enum files
50
+ ```python
51
+ def emit_models(self, schemas: Dict[str, IRSchema], context: RenderContext) -> None:
52
+ # 1. Group schemas by module (one file per schema or logical grouping)
53
+ # 2. Generate code for each schema using ModelVisitor
54
+ # 3. Create __init__.py with imports
55
+ # 4. Write files to models/ directory
56
+
57
+ for schema_name, schema in schemas.items():
58
+ module_name = self.get_module_name(schema_name)
59
+ file_path = self.output_path / "models" / f"{module_name}.py"
60
+
61
+ # Generate model code
62
+ model_code = self.model_visitor.visit_schema(schema, context)
63
+
64
+ # Write file
65
+ self.file_manager.write_file(file_path, model_code)
66
+ ```
67
+
68
+ ### endpoints_emitter.py
69
+ **Purpose**: Create endpoints/ directory with operation methods grouped by tag
70
+ ```python
71
+ def emit_endpoints(self, operations: List[IROperation], context: RenderContext) -> None:
72
+ # 1. Group operations by OpenAPI tag
73
+ operations_by_tag = self.group_by_tag(operations)
74
+
75
+ # 2. Generate endpoint class for each tag
76
+ for tag, tag_operations in operations_by_tag.items():
77
+ class_name = f"{tag.capitalize()}Endpoints"
78
+ file_path = self.output_path / "endpoints" / f"{tag}.py"
79
+
80
+ # Generate endpoint class code
81
+ endpoint_code = self.endpoint_visitor.visit_tag_operations(tag_operations, context)
82
+
83
+ # Write file
84
+ self.file_manager.write_file(file_path, endpoint_code)
85
+ ```
86
+
87
+ ### client_emitter.py
88
+ **Purpose**: Create main client.py with tag-grouped properties
89
+ ```python
90
+ def emit_client(self, spec: IRSpec, context: RenderContext) -> None:
91
+ # 1. Generate main client class
92
+ # 2. Create properties for each tag endpoint
93
+ # 3. Generate context manager methods
94
+ # 4. Handle authentication setup
95
+
96
+ client_code = self.client_visitor.visit_spec(spec, context)
97
+ self.file_manager.write_file(self.output_path / "client.py", client_code)
98
+ ```
99
+
100
+ ### core_emitter.py
101
+ **Purpose**: Copy runtime dependencies to core/ directory
102
+ ```python
103
+ def emit_core(self, output_package: str, core_package: str) -> None:
104
+ # 1. Copy auth/ directory
105
+ # 2. Copy exceptions.py, http_transport.py, etc.
106
+ # 3. Update import paths for target package
107
+ # 4. Handle shared core vs embedded core
108
+
109
+ if self.use_shared_core:
110
+ # Create symlinks or references to shared core
111
+ pass
112
+ else:
113
+ # Copy all core files to client package
114
+ self.copy_core_files()
115
+ ```
116
+
117
+ ## File Management Patterns
118
+
119
+ ### 1. Import Resolution
120
+ ```python
121
+ # Always resolve imports after code generation
122
+ def write_file_with_imports(self, file_path: Path, code: str, context: RenderContext) -> None:
123
+ # 1. Collect imports from context
124
+ imports = context.get_imports()
125
+
126
+ # 2. Sort and deduplicate imports
127
+ sorted_imports = self.sort_imports(imports)
128
+
129
+ # 3. Combine imports with code
130
+ final_code = self.combine_imports_and_code(sorted_imports, code)
131
+
132
+ # 4. Write file
133
+ self.file_manager.write_file(file_path, final_code)
134
+ ```
135
+
136
+ ### 2. Package Initialization
137
+ ```python
138
+ # Always create __init__.py files
139
+ def create_package_init(self, package_path: Path, exports: List[str]) -> None:
140
+ init_content = []
141
+
142
+ # Add imports for all public exports
143
+ for export in exports:
144
+ init_content.append(f"from .{export} import {export}")
145
+
146
+ # Add __all__ for explicit exports
147
+ init_content.append(f"__all__ = {exports}")
148
+
149
+ self.file_manager.write_file(package_path / "__init__.py", "\n".join(init_content))
150
+ ```
151
+
152
+ ### 3. Relative Import Handling
153
+ ```python
154
+ # Convert absolute imports to relative for generated packages
155
+ def convert_to_relative_imports(self, code: str, current_package: str) -> str:
156
+ # Replace absolute imports with relative imports
157
+ # Example: "from my_client.models.user import User" → "from ..models.user import User"
158
+
159
+ import_pattern = re.compile(rf"from {re.escape(current_package)}\.(.+?) import")
160
+
161
+ def replace_import(match):
162
+ import_path = match.group(1)
163
+ depth = len(import_path.split("."))
164
+ relative_prefix = "." * depth
165
+ return f"from {relative_prefix}{import_path} import"
166
+
167
+ return import_pattern.sub(replace_import, code)
168
+ ```
169
+
170
+ ## Dependencies on Other Systems
171
+
172
+ ### From visit/
173
+ - Consumes generated code strings
174
+ - Coordinates with visitors for code generation
175
+
176
+ ### From context/
177
+ - `FileManager` for file operations
178
+ - `RenderContext` for import management
179
+ - Path resolution utilities
180
+
181
+ ### From core/
182
+ - Runtime components copied to generated clients
183
+ - Template files for package structure
184
+
185
+ ## Testing Requirements
186
+
187
+ ### File Creation Tests
188
+ ```python
189
+ def test_models_emitter__simple_schema__creates_correct_file():
190
+ # Arrange
191
+ schema = IRSchema(name="User", type="object", properties={"name": {"type": "string"}})
192
+ emitter = ModelsEmitter(output_path="/tmp/test")
193
+
194
+ # Act
195
+ emitter.emit_models({"User": schema}, context)
196
+
197
+ # Assert
198
+ assert Path("/tmp/test/models/user.py").exists()
199
+ content = Path("/tmp/test/models/user.py").read_text()
200
+ assert "@dataclass" in content
201
+ assert "name: str" in content
202
+ ```
203
+
204
+ ### Import Resolution Tests
205
+ ```python
206
+ def test_emitter__complex_types__resolves_imports_correctly():
207
+ # Test that imports are correctly collected and written
208
+ # Verify no duplicate imports
209
+ # Verify correct import sorting
210
+ ```
211
+
212
+ ## Extension Points
213
+
214
+ ### Adding New Emitters
215
+ ```python
216
+ # Create new emitter for new output aspects
217
+ class CustomEmitter:
218
+ def __init__(self, output_path: Path, file_manager: FileManager):
219
+ self.output_path = output_path
220
+ self.file_manager = file_manager
221
+
222
+ def emit_custom(self, data: Any, context: RenderContext) -> None:
223
+ # Custom file creation logic
224
+ pass
225
+ ```
226
+
227
+ ### Custom Package Structures
228
+ ```python
229
+ # Modify emitters to create different package layouts
230
+ class AlternativeModelsEmitter(ModelsEmitter):
231
+ def get_file_path(self, schema_name: str) -> Path:
232
+ # Custom file organization logic
233
+ # Example: Group models by domain
234
+ domain = self.get_domain(schema_name)
235
+ return self.output_path / "models" / domain / f"{schema_name.lower()}.py"
236
+ ```
237
+
238
+ ## Critical Implementation Details
239
+
240
+ ### File Path Resolution
241
+ ```python
242
+ # Always use pathlib.Path for cross-platform compatibility
243
+ def get_output_path(self, package_name: str, module_name: str) -> Path:
244
+ # Convert package.module to file path
245
+ parts = package_name.split(".")
246
+ path = Path(self.project_root)
247
+ for part in parts:
248
+ path = path / part
249
+ return path / f"{module_name}.py"
250
+ ```
251
+
252
+ ### Error Handling
253
+ ```python
254
+ def emit_safely(self, generator_func: Callable, context: RenderContext) -> None:
255
+ try:
256
+ generator_func(context)
257
+ except Exception as e:
258
+ # Add context to file emission errors
259
+ raise FileEmissionError(f"Failed to emit {self.__class__.__name__}: {e}")
260
+ ```
261
+
262
+ ### Atomic File Operations
263
+ ```python
264
+ def write_file_atomically(self, file_path: Path, content: str) -> None:
265
+ # Write to temporary file first, then move
266
+ temp_path = file_path.with_suffix(f"{file_path.suffix}.tmp")
267
+
268
+ try:
269
+ temp_path.write_text(content)
270
+ temp_path.replace(file_path) # Atomic move
271
+ except Exception:
272
+ if temp_path.exists():
273
+ temp_path.unlink()
274
+ raise
275
+ ```
276
+
277
+ ### Diff Checking
278
+ ```python
279
+ def should_write_file(self, file_path: Path, new_content: str) -> bool:
280
+ # Only write if content changed
281
+ if not file_path.exists():
282
+ return True
283
+
284
+ existing_content = file_path.read_text()
285
+ return existing_content != new_content
286
+ ```
@@ -211,7 +211,7 @@ class EndpointsEmitter:
211
211
  init_lines = []
212
212
  if unique_clients:
213
213
  all_list_items = sorted([f'"{cls}"' for cls, _ in unique_clients])
214
- init_lines.append(f"__all__ = [{", ".join(all_list_items)}]")
214
+ init_lines.append(f"__all__ = [{', '.join(all_list_items)}]")
215
215
  for cls, mod in sorted(unique_clients):
216
216
  init_lines.append(f"from .{mod} import {cls}")
217
217
 
@@ -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
+ ```