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,272 @@
|
|
|
1
|
+
# visit/ - Code Generation Visitor Pattern
|
|
2
|
+
|
|
3
|
+
## Why This Folder?
|
|
4
|
+
Transform IR objects into Python code strings using visitor pattern. Each visitor specializes in generating one aspect of the client (models, endpoints, exceptions, etc.).
|
|
5
|
+
|
|
6
|
+
## Key Dependencies
|
|
7
|
+
- **Input**: `IRSpec`, `IRSchema`, `IROperation` from `../ir.py`
|
|
8
|
+
- **Output**: Python code strings for emitters
|
|
9
|
+
- **Services**: `UnifiedTypeService` from `../types/services/`
|
|
10
|
+
- **Context**: `RenderContext` from `../context/render_context.py`
|
|
11
|
+
|
|
12
|
+
## Essential Architecture
|
|
13
|
+
|
|
14
|
+
### 1. Visitor Pattern Hierarchy
|
|
15
|
+
```python
|
|
16
|
+
# visitor.py - Base visitor
|
|
17
|
+
class Visitor(Generic[tNode, tRet]):
|
|
18
|
+
def visit(self, node: tNode, context: RenderContext) -> tRet:
|
|
19
|
+
# Dispatch to specific visit methods
|
|
20
|
+
|
|
21
|
+
# Concrete visitors
|
|
22
|
+
class ModelVisitor(Visitor[IRSchema, str]):
|
|
23
|
+
def visit_schema(self, schema: IRSchema, context: RenderContext) -> str:
|
|
24
|
+
# Generate dataclass/enum code
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Generators vs Visitors
|
|
28
|
+
- **Visitors**: High-level orchestration, traverse IR structure
|
|
29
|
+
- **Generators**: Low-level code generation, create specific code blocks
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
# endpoint/endpoint_visitor.py
|
|
33
|
+
class EndpointVisitor:
|
|
34
|
+
def __init__(self):
|
|
35
|
+
self.signature_generator = SignatureGenerator()
|
|
36
|
+
self.request_generator = RequestGenerator()
|
|
37
|
+
self.response_generator = ResponseHandlerGenerator()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Critical Components
|
|
41
|
+
|
|
42
|
+
### model/model_visitor.py
|
|
43
|
+
**Purpose**: Generate dataclass and enum code from schemas
|
|
44
|
+
```python
|
|
45
|
+
def visit_schema(self, schema: IRSchema, context: RenderContext) -> str:
|
|
46
|
+
if schema.enum:
|
|
47
|
+
return self.enum_generator.generate_enum(schema, context)
|
|
48
|
+
elif schema.type == "object":
|
|
49
|
+
return self.dataclass_generator.generate_dataclass(schema, context)
|
|
50
|
+
else:
|
|
51
|
+
return self.alias_generator.generate_alias(schema, context)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### endpoint/endpoint_visitor.py
|
|
55
|
+
**Purpose**: Generate async method code from operations
|
|
56
|
+
```python
|
|
57
|
+
def visit_operation(self, operation: IROperation, context: RenderContext) -> str:
|
|
58
|
+
# 1. Generate method signature
|
|
59
|
+
signature = self.signature_generator.generate(operation, context)
|
|
60
|
+
|
|
61
|
+
# 2. Generate request construction
|
|
62
|
+
request_code = self.request_generator.generate(operation, context)
|
|
63
|
+
|
|
64
|
+
# 3. Generate response handling
|
|
65
|
+
response_code = self.response_generator.generate(operation, context)
|
|
66
|
+
|
|
67
|
+
# 4. Combine into complete method
|
|
68
|
+
return self.combine_method_parts(signature, request_code, response_code)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### client_visitor.py
|
|
72
|
+
**Purpose**: Generate main client class with tag-grouped methods
|
|
73
|
+
```python
|
|
74
|
+
def visit_spec(self, spec: IRSpec, context: RenderContext) -> str:
|
|
75
|
+
# 1. Group operations by tag
|
|
76
|
+
operations_by_tag = self.group_operations_by_tag(spec.operations)
|
|
77
|
+
|
|
78
|
+
# 2. Generate client class
|
|
79
|
+
# 3. Generate tag-based property methods
|
|
80
|
+
# 4. Generate context manager methods
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Code Generation Patterns
|
|
84
|
+
|
|
85
|
+
### 1. Template-Based Generation
|
|
86
|
+
```python
|
|
87
|
+
# Use string templates for consistent formatting
|
|
88
|
+
METHOD_TEMPLATE = '''
|
|
89
|
+
async def {method_name}(self, {parameters}) -> {return_type}:
|
|
90
|
+
"""
|
|
91
|
+
{docstring}
|
|
92
|
+
"""
|
|
93
|
+
{body}
|
|
94
|
+
'''
|
|
95
|
+
|
|
96
|
+
# Fill template with generated content
|
|
97
|
+
method_code = METHOD_TEMPLATE.format(
|
|
98
|
+
method_name=operation.operation_id,
|
|
99
|
+
parameters=self.signature_generator.generate_parameters(operation),
|
|
100
|
+
return_type=self.get_return_type(operation),
|
|
101
|
+
docstring=self.docstring_generator.generate(operation),
|
|
102
|
+
body=self.generate_method_body(operation)
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 2. Type Resolution Integration
|
|
107
|
+
```python
|
|
108
|
+
# endpoint/generators/signature_generator.py
|
|
109
|
+
def generate_parameters(self, operation: IROperation, context: RenderContext) -> str:
|
|
110
|
+
params = []
|
|
111
|
+
for param in operation.parameters:
|
|
112
|
+
# Use unified type service for parameter types
|
|
113
|
+
param_type = self.type_service.resolve_schema_type(
|
|
114
|
+
param.schema, context, required=param.required
|
|
115
|
+
)
|
|
116
|
+
params.append(f"{param.name}: {param_type}")
|
|
117
|
+
return ", ".join(params)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 3. Import Management
|
|
121
|
+
```python
|
|
122
|
+
# Always register imports when using complex types
|
|
123
|
+
def generate_dataclass(self, schema: IRSchema, context: RenderContext) -> str:
|
|
124
|
+
imports = []
|
|
125
|
+
|
|
126
|
+
for prop_name, prop_schema in schema.properties.items():
|
|
127
|
+
prop_type = self.type_service.resolve_schema_type(prop_schema, context)
|
|
128
|
+
|
|
129
|
+
# Type service handles import registration
|
|
130
|
+
# context.add_import() called internally
|
|
131
|
+
|
|
132
|
+
return dataclass_code
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Specialized Generators
|
|
136
|
+
|
|
137
|
+
### endpoint/generators/
|
|
138
|
+
**Purpose**: Generate specific parts of endpoint methods
|
|
139
|
+
|
|
140
|
+
#### docstring_generator.py
|
|
141
|
+
```python
|
|
142
|
+
def generate_docstring(self, operation: IROperation, context: RenderContext) -> str:
|
|
143
|
+
# Generate Google-style docstrings
|
|
144
|
+
# Include parameter descriptions
|
|
145
|
+
# Include return type information
|
|
146
|
+
# Include raises information
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### request_generator.py
|
|
150
|
+
```python
|
|
151
|
+
def generate_request_construction(self, operation: IROperation, context: RenderContext) -> str:
|
|
152
|
+
# Generate httpx.Request construction
|
|
153
|
+
# Handle query parameters, headers, body
|
|
154
|
+
# Apply authentication
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### response_handler_generator.py
|
|
158
|
+
```python
|
|
159
|
+
def generate_response_handling(self, operation: IROperation, context: RenderContext) -> str:
|
|
160
|
+
# Generate match/case for status codes
|
|
161
|
+
# Handle response deserialization
|
|
162
|
+
# Generate exception raising
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Dependencies on Other Systems
|
|
166
|
+
|
|
167
|
+
### From types/
|
|
168
|
+
- `UnifiedTypeService` for all type resolution
|
|
169
|
+
- Response unwrapping detection
|
|
170
|
+
- Forward reference handling
|
|
171
|
+
|
|
172
|
+
### From context/
|
|
173
|
+
- `RenderContext` for import management
|
|
174
|
+
- Template rendering utilities
|
|
175
|
+
- Path resolution
|
|
176
|
+
|
|
177
|
+
### To emitters/
|
|
178
|
+
- Visitors produce code strings
|
|
179
|
+
- Emitters organize code into files
|
|
180
|
+
|
|
181
|
+
## Testing Requirements
|
|
182
|
+
|
|
183
|
+
### Visitor Tests
|
|
184
|
+
```python
|
|
185
|
+
def test_model_visitor__dataclass_schema__generates_correct_code():
|
|
186
|
+
# Arrange
|
|
187
|
+
schema = IRSchema(type="object", properties={"name": {"type": "string"}})
|
|
188
|
+
context = RenderContext()
|
|
189
|
+
visitor = ModelVisitor()
|
|
190
|
+
|
|
191
|
+
# Act
|
|
192
|
+
code = visitor.visit_schema(schema, context)
|
|
193
|
+
|
|
194
|
+
# Assert
|
|
195
|
+
assert "@dataclass" in code
|
|
196
|
+
assert "name: str" in code
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Generator Tests
|
|
200
|
+
```python
|
|
201
|
+
def test_signature_generator__operation_with_params__generates_correct_signature():
|
|
202
|
+
# Test specific code generation components
|
|
203
|
+
operation = IROperation(parameters=[...])
|
|
204
|
+
generator = SignatureGenerator()
|
|
205
|
+
|
|
206
|
+
signature = generator.generate(operation, context)
|
|
207
|
+
|
|
208
|
+
# Verify parameter types, defaults, etc.
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Extension Points
|
|
212
|
+
|
|
213
|
+
### Adding New Visitors
|
|
214
|
+
```python
|
|
215
|
+
# Create new visitor for new code aspects
|
|
216
|
+
class CustomVisitor(Visitor[IRCustomNode, str]):
|
|
217
|
+
def visit_custom_node(self, node: IRCustomNode, context: RenderContext) -> str:
|
|
218
|
+
# Custom code generation logic
|
|
219
|
+
pass
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Adding New Generators
|
|
223
|
+
```python
|
|
224
|
+
# endpoint/generators/custom_generator.py
|
|
225
|
+
class CustomGenerator:
|
|
226
|
+
def __init__(self, type_service: UnifiedTypeService):
|
|
227
|
+
self.type_service = type_service
|
|
228
|
+
|
|
229
|
+
def generate(self, operation: IROperation, context: RenderContext) -> str:
|
|
230
|
+
# Custom code generation logic
|
|
231
|
+
pass
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Critical Implementation Details
|
|
235
|
+
|
|
236
|
+
### Error Handling in Visitors
|
|
237
|
+
```python
|
|
238
|
+
def visit_schema(self, schema: IRSchema, context: RenderContext) -> str:
|
|
239
|
+
try:
|
|
240
|
+
return self.generate_code(schema, context)
|
|
241
|
+
except Exception as e:
|
|
242
|
+
# Add context to errors
|
|
243
|
+
raise CodeGenerationError(f"Failed to generate code for schema {schema.name}: {e}")
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Context Management
|
|
247
|
+
```python
|
|
248
|
+
# Always use context for imports and state
|
|
249
|
+
def generate_method(self, operation: IROperation, context: RenderContext) -> str:
|
|
250
|
+
# Register imports
|
|
251
|
+
context.add_import("from typing import Optional")
|
|
252
|
+
|
|
253
|
+
# Use context for type resolution
|
|
254
|
+
return_type = self.type_service.resolve_operation_response_type(operation, context)
|
|
255
|
+
|
|
256
|
+
# Return code string
|
|
257
|
+
return method_code
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Code Formatting
|
|
261
|
+
```python
|
|
262
|
+
# Use consistent indentation and formatting
|
|
263
|
+
def format_method_body(self, lines: List[str]) -> str:
|
|
264
|
+
# Ensure proper indentation
|
|
265
|
+
formatted_lines = []
|
|
266
|
+
for line in lines:
|
|
267
|
+
if line.strip():
|
|
268
|
+
formatted_lines.append(f" {line}") # 4-space indent
|
|
269
|
+
else:
|
|
270
|
+
formatted_lines.append("")
|
|
271
|
+
return "\n".join(formatted_lines)
|
|
272
|
+
```
|