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.
Files changed (137) hide show
  1. pyopenapi_gen/__init__.py +224 -0
  2. pyopenapi_gen/__main__.py +6 -0
  3. pyopenapi_gen/cli.py +62 -0
  4. pyopenapi_gen/context/CLAUDE.md +284 -0
  5. pyopenapi_gen/context/file_manager.py +52 -0
  6. pyopenapi_gen/context/import_collector.py +382 -0
  7. pyopenapi_gen/context/render_context.py +726 -0
  8. pyopenapi_gen/core/CLAUDE.md +224 -0
  9. pyopenapi_gen/core/__init__.py +0 -0
  10. pyopenapi_gen/core/auth/base.py +22 -0
  11. pyopenapi_gen/core/auth/plugins.py +89 -0
  12. pyopenapi_gen/core/cattrs_converter.py +810 -0
  13. pyopenapi_gen/core/exceptions.py +20 -0
  14. pyopenapi_gen/core/http_status_codes.py +218 -0
  15. pyopenapi_gen/core/http_transport.py +222 -0
  16. pyopenapi_gen/core/loader/__init__.py +12 -0
  17. pyopenapi_gen/core/loader/loader.py +174 -0
  18. pyopenapi_gen/core/loader/operations/__init__.py +12 -0
  19. pyopenapi_gen/core/loader/operations/parser.py +161 -0
  20. pyopenapi_gen/core/loader/operations/post_processor.py +62 -0
  21. pyopenapi_gen/core/loader/operations/request_body.py +90 -0
  22. pyopenapi_gen/core/loader/parameters/__init__.py +10 -0
  23. pyopenapi_gen/core/loader/parameters/parser.py +186 -0
  24. pyopenapi_gen/core/loader/responses/__init__.py +10 -0
  25. pyopenapi_gen/core/loader/responses/parser.py +111 -0
  26. pyopenapi_gen/core/loader/schemas/__init__.py +11 -0
  27. pyopenapi_gen/core/loader/schemas/extractor.py +275 -0
  28. pyopenapi_gen/core/pagination.py +64 -0
  29. pyopenapi_gen/core/parsing/__init__.py +13 -0
  30. pyopenapi_gen/core/parsing/common/__init__.py +1 -0
  31. pyopenapi_gen/core/parsing/common/ref_resolution/__init__.py +9 -0
  32. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/__init__.py +0 -0
  33. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/cyclic_properties.py +66 -0
  34. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/direct_cycle.py +33 -0
  35. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/existing_schema.py +22 -0
  36. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/list_response.py +54 -0
  37. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/missing_ref.py +52 -0
  38. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/new_schema.py +50 -0
  39. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/stripped_suffix.py +51 -0
  40. pyopenapi_gen/core/parsing/common/ref_resolution/resolve_schema_ref.py +86 -0
  41. pyopenapi_gen/core/parsing/common/type_parser.py +73 -0
  42. pyopenapi_gen/core/parsing/context.py +187 -0
  43. pyopenapi_gen/core/parsing/cycle_helpers.py +126 -0
  44. pyopenapi_gen/core/parsing/keywords/__init__.py +1 -0
  45. pyopenapi_gen/core/parsing/keywords/all_of_parser.py +81 -0
  46. pyopenapi_gen/core/parsing/keywords/any_of_parser.py +84 -0
  47. pyopenapi_gen/core/parsing/keywords/array_items_parser.py +72 -0
  48. pyopenapi_gen/core/parsing/keywords/one_of_parser.py +77 -0
  49. pyopenapi_gen/core/parsing/keywords/properties_parser.py +98 -0
  50. pyopenapi_gen/core/parsing/schema_finalizer.py +169 -0
  51. pyopenapi_gen/core/parsing/schema_parser.py +804 -0
  52. pyopenapi_gen/core/parsing/transformers/__init__.py +0 -0
  53. pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +285 -0
  54. pyopenapi_gen/core/parsing/transformers/inline_object_promoter.py +120 -0
  55. pyopenapi_gen/core/parsing/unified_cycle_detection.py +293 -0
  56. pyopenapi_gen/core/postprocess_manager.py +260 -0
  57. pyopenapi_gen/core/spec_fetcher.py +148 -0
  58. pyopenapi_gen/core/streaming_helpers.py +84 -0
  59. pyopenapi_gen/core/telemetry.py +69 -0
  60. pyopenapi_gen/core/utils.py +456 -0
  61. pyopenapi_gen/core/warning_collector.py +83 -0
  62. pyopenapi_gen/core/writers/code_writer.py +135 -0
  63. pyopenapi_gen/core/writers/documentation_writer.py +222 -0
  64. pyopenapi_gen/core/writers/line_writer.py +217 -0
  65. pyopenapi_gen/core/writers/python_construct_renderer.py +321 -0
  66. pyopenapi_gen/core_package_template/README.md +21 -0
  67. pyopenapi_gen/emit/models_emitter.py +143 -0
  68. pyopenapi_gen/emitters/CLAUDE.md +286 -0
  69. pyopenapi_gen/emitters/client_emitter.py +51 -0
  70. pyopenapi_gen/emitters/core_emitter.py +181 -0
  71. pyopenapi_gen/emitters/docs_emitter.py +44 -0
  72. pyopenapi_gen/emitters/endpoints_emitter.py +247 -0
  73. pyopenapi_gen/emitters/exceptions_emitter.py +187 -0
  74. pyopenapi_gen/emitters/mocks_emitter.py +185 -0
  75. pyopenapi_gen/emitters/models_emitter.py +426 -0
  76. pyopenapi_gen/generator/CLAUDE.md +352 -0
  77. pyopenapi_gen/generator/client_generator.py +567 -0
  78. pyopenapi_gen/generator/exceptions.py +7 -0
  79. pyopenapi_gen/helpers/CLAUDE.md +325 -0
  80. pyopenapi_gen/helpers/__init__.py +1 -0
  81. pyopenapi_gen/helpers/endpoint_utils.py +532 -0
  82. pyopenapi_gen/helpers/type_cleaner.py +334 -0
  83. pyopenapi_gen/helpers/type_helper.py +112 -0
  84. pyopenapi_gen/helpers/type_resolution/__init__.py +1 -0
  85. pyopenapi_gen/helpers/type_resolution/array_resolver.py +57 -0
  86. pyopenapi_gen/helpers/type_resolution/composition_resolver.py +79 -0
  87. pyopenapi_gen/helpers/type_resolution/finalizer.py +105 -0
  88. pyopenapi_gen/helpers/type_resolution/named_resolver.py +172 -0
  89. pyopenapi_gen/helpers/type_resolution/object_resolver.py +216 -0
  90. pyopenapi_gen/helpers/type_resolution/primitive_resolver.py +109 -0
  91. pyopenapi_gen/helpers/type_resolution/resolver.py +47 -0
  92. pyopenapi_gen/helpers/url_utils.py +14 -0
  93. pyopenapi_gen/http_types.py +20 -0
  94. pyopenapi_gen/ir.py +165 -0
  95. pyopenapi_gen/py.typed +1 -0
  96. pyopenapi_gen/types/CLAUDE.md +140 -0
  97. pyopenapi_gen/types/__init__.py +11 -0
  98. pyopenapi_gen/types/contracts/__init__.py +13 -0
  99. pyopenapi_gen/types/contracts/protocols.py +106 -0
  100. pyopenapi_gen/types/contracts/types.py +28 -0
  101. pyopenapi_gen/types/resolvers/__init__.py +7 -0
  102. pyopenapi_gen/types/resolvers/reference_resolver.py +71 -0
  103. pyopenapi_gen/types/resolvers/response_resolver.py +177 -0
  104. pyopenapi_gen/types/resolvers/schema_resolver.py +498 -0
  105. pyopenapi_gen/types/services/__init__.py +5 -0
  106. pyopenapi_gen/types/services/type_service.py +165 -0
  107. pyopenapi_gen/types/strategies/__init__.py +5 -0
  108. pyopenapi_gen/types/strategies/response_strategy.py +310 -0
  109. pyopenapi_gen/visit/CLAUDE.md +272 -0
  110. pyopenapi_gen/visit/client_visitor.py +477 -0
  111. pyopenapi_gen/visit/docs_visitor.py +38 -0
  112. pyopenapi_gen/visit/endpoint/__init__.py +1 -0
  113. pyopenapi_gen/visit/endpoint/endpoint_visitor.py +292 -0
  114. pyopenapi_gen/visit/endpoint/generators/__init__.py +1 -0
  115. pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +123 -0
  116. pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py +222 -0
  117. pyopenapi_gen/visit/endpoint/generators/mock_generator.py +140 -0
  118. pyopenapi_gen/visit/endpoint/generators/overload_generator.py +252 -0
  119. pyopenapi_gen/visit/endpoint/generators/request_generator.py +103 -0
  120. pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +705 -0
  121. pyopenapi_gen/visit/endpoint/generators/signature_generator.py +83 -0
  122. pyopenapi_gen/visit/endpoint/generators/url_args_generator.py +207 -0
  123. pyopenapi_gen/visit/endpoint/processors/__init__.py +1 -0
  124. pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +78 -0
  125. pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +171 -0
  126. pyopenapi_gen/visit/exception_visitor.py +90 -0
  127. pyopenapi_gen/visit/model/__init__.py +0 -0
  128. pyopenapi_gen/visit/model/alias_generator.py +93 -0
  129. pyopenapi_gen/visit/model/dataclass_generator.py +553 -0
  130. pyopenapi_gen/visit/model/enum_generator.py +212 -0
  131. pyopenapi_gen/visit/model/model_visitor.py +198 -0
  132. pyopenapi_gen/visit/visitor.py +97 -0
  133. pyopenapi_gen-2.7.2.dist-info/METADATA +1169 -0
  134. pyopenapi_gen-2.7.2.dist-info/RECORD +137 -0
  135. pyopenapi_gen-2.7.2.dist-info/WHEEL +4 -0
  136. pyopenapi_gen-2.7.2.dist-info/entry_points.txt +2 -0
  137. 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
+ ```