pyopenapi-gen 0.8.6__py3-none-any.whl → 0.9.0__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 (30) 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.6.dist-info → pyopenapi_gen-0.9.0.dist-info}/METADATA +56 -40
  26. {pyopenapi_gen-0.8.6.dist-info → pyopenapi_gen-0.9.0.dist-info}/RECORD +49 -42
  27. {pyopenapi_gen-0.8.6.dist-info → pyopenapi_gen-0.9.0.dist-info}/WHEEL +1 -1
  28. pyopenapi_gen-0.9.0.dist-info/entry_points.txt +3 -0
  29. pyopenapi_gen-0.8.6.dist-info/entry_points.txt +0 -2
  30. {pyopenapi_gen-0.8.6.dist-info/licenses → pyopenapi_gen-0.9.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,325 @@
1
+ # helpers/ - Legacy Compatibility Layer
2
+
3
+ ## Why This Folder?
4
+ Backward compatibility during transition to unified type system. Delegates to new `types/` system while maintaining old API surface for gradual migration.
5
+
6
+ ## Key Dependencies
7
+ - **Delegates to**: `../types/services/UnifiedTypeService`
8
+ - **Used by**: Legacy code that hasn't migrated to unified system
9
+ - **Status**: Transitional - prefer `types/` for new code
10
+
11
+ ## Critical Architecture
12
+
13
+ ### 1. Legacy → Unified Delegation
14
+ ```python
15
+ # type_helper.py
16
+ class TypeHelper:
17
+ @staticmethod
18
+ def get_python_type_for_schema(schema: IRSchema, all_schemas: Dict[str, IRSchema],
19
+ context: RenderContext, required: bool = True,
20
+ resolve_alias_target: bool = False) -> str:
21
+ """Legacy API - delegates to UnifiedTypeService"""
22
+ type_service = UnifiedTypeService(all_schemas)
23
+ return type_service.resolve_schema_type(
24
+ schema, context, required, resolve_underlying=resolve_alias_target
25
+ )
26
+ ```
27
+
28
+ ### 2. Type Resolution Subdirectory
29
+ ```python
30
+ # type_resolution/ - Legacy individual resolvers
31
+ # These now delegate to unified system components
32
+ array_resolver.py → types/resolvers/schema_resolver.py
33
+ composition_resolver.py → types/resolvers/schema_resolver.py
34
+ object_resolver.py → types/resolvers/schema_resolver.py
35
+ primitive_resolver.py → types/resolvers/schema_resolver.py
36
+ ```
37
+
38
+ ## Migration Strategy
39
+
40
+ ### 1. Deprecation Pattern
41
+ ```python
42
+ import warnings
43
+ from ..types.services import UnifiedTypeService
44
+
45
+ def legacy_function(schema: IRSchema, context: RenderContext) -> str:
46
+ """
47
+ DEPRECATED: Use UnifiedTypeService.resolve_schema_type() instead.
48
+ This function will be removed in version 2.0.
49
+ """
50
+ warnings.warn(
51
+ "legacy_function is deprecated. Use UnifiedTypeService.resolve_schema_type()",
52
+ DeprecationWarning,
53
+ stacklevel=2
54
+ )
55
+
56
+ # Delegate to new system
57
+ type_service = UnifiedTypeService({})
58
+ return type_service.resolve_schema_type(schema, context)
59
+ ```
60
+
61
+ ### 2. API Compatibility
62
+ ```python
63
+ # Maintain old signatures while delegating
64
+ def get_return_type(operation: IROperation, context: RenderContext,
65
+ schemas: Dict[str, IRSchema]) -> Tuple[str, bool]:
66
+ """Legacy endpoint_utils function"""
67
+ type_service = UnifiedTypeService(schemas)
68
+ return type_service.resolve_operation_response_with_unwrap_info(operation, context)
69
+ ```
70
+
71
+ ## Critical Components
72
+
73
+ ### type_helper.py
74
+ **Purpose**: Main legacy entry point for type resolution
75
+ ```python
76
+ class TypeHelper:
77
+ @staticmethod
78
+ def get_python_type_for_schema(schema: IRSchema, all_schemas: Dict[str, IRSchema],
79
+ context: RenderContext, required: bool = True,
80
+ resolve_alias_target: bool = False) -> str:
81
+ """
82
+ Legacy type resolution - delegates to UnifiedTypeService
83
+
84
+ Args:
85
+ schema: Schema to resolve
86
+ all_schemas: All schemas in spec (for references)
87
+ context: Render context for imports
88
+ required: Whether field is required (affects Optional[])
89
+ resolve_alias_target: Whether to resolve through aliases
90
+
91
+ Returns:
92
+ Python type string
93
+ """
94
+ type_service = UnifiedTypeService(all_schemas)
95
+ return type_service.resolve_schema_type(
96
+ schema, context, required, resolve_underlying=resolve_alias_target
97
+ )
98
+ ```
99
+
100
+ ### endpoint_utils.py
101
+ **Purpose**: Legacy endpoint-specific utilities
102
+ ```python
103
+ def get_return_type(operation: IROperation, context: RenderContext,
104
+ schemas: Dict[str, IRSchema]) -> Tuple[str, bool]:
105
+ """
106
+ Legacy function for getting operation return type
107
+
108
+ Returns:
109
+ Tuple of (python_type, was_unwrapped)
110
+ """
111
+ type_service = UnifiedTypeService(schemas)
112
+ return type_service.resolve_operation_response_with_unwrap_info(operation, context)
113
+
114
+ def get_endpoint_return_types(operation: IROperation, context: RenderContext,
115
+ schemas: Dict[str, IRSchema]) -> Dict[str, str]:
116
+ """Legacy function for getting all response types"""
117
+ type_service = UnifiedTypeService(schemas)
118
+ return type_service.resolve_all_response_types(operation, context)
119
+ ```
120
+
121
+ ### type_resolution/ Subdirectory
122
+ **Purpose**: Legacy individual type resolvers
123
+
124
+ #### array_resolver.py
125
+ ```python
126
+ def resolve_array_type(schema: IRSchema, context: RenderContext,
127
+ all_schemas: Dict[str, IRSchema]) -> str:
128
+ """Legacy array type resolution"""
129
+ type_service = UnifiedTypeService(all_schemas)
130
+ return type_service.resolve_schema_type(schema, context)
131
+ ```
132
+
133
+ #### composition_resolver.py
134
+ ```python
135
+ def resolve_composition_type(schema: IRSchema, context: RenderContext,
136
+ all_schemas: Dict[str, IRSchema]) -> str:
137
+ """Legacy composition (allOf/oneOf/anyOf) resolution"""
138
+ type_service = UnifiedTypeService(all_schemas)
139
+ return type_service.resolve_schema_type(schema, context)
140
+ ```
141
+
142
+ ## Usage Patterns (Legacy)
143
+
144
+ ### 1. Type Resolution
145
+ ```python
146
+ # OLD WAY (still works, but deprecated)
147
+ from pyopenapi_gen.helpers.type_helper import TypeHelper
148
+
149
+ python_type = TypeHelper.get_python_type_for_schema(
150
+ schema, all_schemas, context, required=True
151
+ )
152
+
153
+ # NEW WAY (preferred)
154
+ from pyopenapi_gen.types.services import UnifiedTypeService
155
+
156
+ type_service = UnifiedTypeService(all_schemas)
157
+ python_type = type_service.resolve_schema_type(schema, context, required=True)
158
+ ```
159
+
160
+ ### 2. Endpoint Type Resolution
161
+ ```python
162
+ # OLD WAY (still works, but deprecated)
163
+ from pyopenapi_gen.helpers.endpoint_utils import get_return_type
164
+
165
+ return_type, was_unwrapped = get_return_type(operation, context, schemas)
166
+
167
+ # NEW WAY (preferred)
168
+ from pyopenapi_gen.types.services import UnifiedTypeService
169
+
170
+ type_service = UnifiedTypeService(schemas)
171
+ return_type, was_unwrapped = type_service.resolve_operation_response_with_unwrap_info(
172
+ operation, context
173
+ )
174
+ ```
175
+
176
+ ## Migration Guide
177
+
178
+ ### 1. Type Helper Migration
179
+ ```python
180
+ # Before
181
+ from pyopenapi_gen.helpers.type_helper import TypeHelper
182
+
183
+ class MyVisitor:
184
+ def resolve_type(self, schema: IRSchema) -> str:
185
+ return TypeHelper.get_python_type_for_schema(
186
+ schema, self.all_schemas, self.context, required=True
187
+ )
188
+
189
+ # After
190
+ from pyopenapi_gen.types.services import UnifiedTypeService
191
+
192
+ class MyVisitor:
193
+ def __init__(self, all_schemas: Dict[str, IRSchema]):
194
+ self.type_service = UnifiedTypeService(all_schemas)
195
+
196
+ def resolve_type(self, schema: IRSchema) -> str:
197
+ return self.type_service.resolve_schema_type(
198
+ schema, self.context, required=True
199
+ )
200
+ ```
201
+
202
+ ### 2. Endpoint Utils Migration
203
+ ```python
204
+ # Before
205
+ from pyopenapi_gen.helpers.endpoint_utils import get_return_type
206
+
207
+ def generate_method(self, operation: IROperation):
208
+ return_type, was_unwrapped = get_return_type(operation, self.context, self.schemas)
209
+
210
+ # After
211
+ from pyopenapi_gen.types.services import UnifiedTypeService
212
+
213
+ def generate_method(self, operation: IROperation):
214
+ return_type, was_unwrapped = self.type_service.resolve_operation_response_with_unwrap_info(
215
+ operation, self.context
216
+ )
217
+ ```
218
+
219
+ ## Testing Strategy
220
+
221
+ ### 1. Compatibility Tests
222
+ ```python
223
+ def test_type_helper__legacy_api__matches_unified_service():
224
+ """Ensure legacy API produces same results as unified service"""
225
+
226
+ # Test with legacy API
227
+ legacy_result = TypeHelper.get_python_type_for_schema(
228
+ schema, all_schemas, context, required=True
229
+ )
230
+
231
+ # Test with unified service
232
+ type_service = UnifiedTypeService(all_schemas)
233
+ unified_result = type_service.resolve_schema_type(
234
+ schema, context, required=True
235
+ )
236
+
237
+ assert legacy_result == unified_result
238
+ ```
239
+
240
+ ### 2. Deprecation Warning Tests
241
+ ```python
242
+ def test_legacy_function__emits_deprecation_warning():
243
+ """Ensure legacy functions emit deprecation warnings"""
244
+
245
+ with warnings.catch_warnings(record=True) as w:
246
+ warnings.simplefilter("always")
247
+
248
+ # Call legacy function
249
+ TypeHelper.get_python_type_for_schema(schema, all_schemas, context)
250
+
251
+ # Check warning was emitted
252
+ assert len(w) == 1
253
+ assert issubclass(w[0].category, DeprecationWarning)
254
+ assert "deprecated" in str(w[0].message)
255
+ ```
256
+
257
+ ## Removal Timeline
258
+
259
+ ### Phase 1: Deprecation (Current)
260
+ - Add deprecation warnings to all legacy functions
261
+ - Update internal code to use unified system
262
+ - Maintain backward compatibility
263
+
264
+ ### Phase 2: Migration (Future)
265
+ - Remove legacy functions
266
+ - Update all external references
267
+ - Remove helpers/ directory
268
+
269
+ ## Extension Points
270
+
271
+ ### Custom Legacy Adapters
272
+ ```python
273
+ class CustomLegacyAdapter:
274
+ """Adapt old custom APIs to unified system"""
275
+
276
+ def __init__(self, type_service: UnifiedTypeService):
277
+ self.type_service = type_service
278
+
279
+ def old_custom_method(self, schema: IRSchema) -> str:
280
+ # Convert old API to new unified service call
281
+ return self.type_service.resolve_schema_type(schema, context)
282
+ ```
283
+
284
+ ## Critical Implementation Details
285
+
286
+ ### Error Handling
287
+ ```python
288
+ def legacy_function(schema: IRSchema) -> str:
289
+ """Legacy function with error handling"""
290
+ try:
291
+ # Delegate to unified system
292
+ return unified_function(schema)
293
+ except Exception as e:
294
+ # Convert unified errors to legacy error format
295
+ raise LegacyError(f"Legacy function failed: {e}")
296
+ ```
297
+
298
+ ### Performance Considerations
299
+ ```python
300
+ # Cache UnifiedTypeService instances to avoid recreation
301
+ _type_service_cache = {}
302
+
303
+ def get_cached_type_service(schemas: Dict[str, IRSchema]) -> UnifiedTypeService:
304
+ """Get cached type service for performance"""
305
+ schema_hash = hash(frozenset(schemas.keys()))
306
+
307
+ if schema_hash not in _type_service_cache:
308
+ _type_service_cache[schema_hash] = UnifiedTypeService(schemas)
309
+
310
+ return _type_service_cache[schema_hash]
311
+ ```
312
+
313
+ ## Common Pitfalls
314
+
315
+ 1. **Direct Usage**: Using legacy functions in new code
316
+ 2. **Missing Warnings**: Not emitting deprecation warnings
317
+ 3. **Inconsistent Results**: Legacy and unified APIs returning different results
318
+ 4. **Performance**: Creating new UnifiedTypeService instances repeatedly
319
+
320
+ ## Best Practices
321
+
322
+ 1. **Prefer Unified System**: Use `types/` for all new code
323
+ 2. **Emit Warnings**: Always emit deprecation warnings in legacy functions
324
+ 3. **Test Compatibility**: Ensure legacy and unified APIs return same results
325
+ 4. **Document Migration**: Provide clear migration paths in docstrings
@@ -308,10 +308,10 @@ def format_method_args(params: list[dict[str, Any]]) -> str:
308
308
  optional = [p for p in params if not p.get("required", True)]
309
309
  arg_strs = []
310
310
  for p in required:
311
- arg_strs.append(f"{p["name"]}: {p["type"]}")
311
+ arg_strs.append(f"{p['name']}: {p['type']}")
312
312
  for p in optional:
313
313
  default = p["default"]
314
- arg_strs.append(f"{p["name"]}: {p["type"]} = {default}")
314
+ arg_strs.append(f"{p['name']}: {p['type']} = {default}")
315
315
  return ", ".join(arg_strs)
316
316
 
317
317
 
@@ -220,7 +220,7 @@ class TypeCleaner:
220
220
  if len(unique_members) == 1:
221
221
  return unique_members[0] # A Union with one member is just that member.
222
222
 
223
- return f"Union[{", ".join(unique_members)}]"
223
+ return f"Union[{', '.join(unique_members)}]"
224
224
 
225
225
  @classmethod
226
226
  def _clean_list_type(cls, type_str: str) -> str:
@@ -73,7 +73,7 @@ class CompositionTypeResolver:
73
73
  return unique_types[0]
74
74
 
75
75
  self.context.add_import("typing", "Union")
76
- union_str = f"Union[{", ".join(unique_types)}]"
76
+ union_str = f"Union[{', '.join(unique_types)}]"
77
77
  return union_str
78
78
 
79
79
  return None
@@ -23,7 +23,7 @@ class TypeFinalizer:
23
23
  if py_type is None:
24
24
  logger.warning(
25
25
  f"[TypeFinalizer] Received None as py_type for schema "
26
- f"'{schema.name or "anonymous"}'. Defaulting to 'Any'."
26
+ f"'{schema.name or 'anonymous'}'. Defaulting to 'Any'."
27
27
  )
28
28
  self.context.add_import("typing", "Any")
29
29
  py_type = "Any"
@@ -0,0 +1,140 @@
1
+ # types/ - Unified Type Resolution System
2
+
3
+ ## Why This Folder?
4
+ Central, testable type resolution replacing scattered type conversion logic. Single source of truth for OpenAPI → Python type mappings with dependency injection architecture.
5
+
6
+ ## Key Dependencies
7
+ - **Input**: `IRSchema`, `IRResponse`, `IROperation` from `../../ir.py`
8
+ - **Output**: Python type strings (`str`, `List[User]`, `Optional[Dict[str, Any]]`)
9
+ - **Context**: `RenderContext` from `../../context/render_context.py`
10
+
11
+ ## Essential Patterns
12
+
13
+ ### 1. Service → Resolvers → Contracts
14
+ ```python
15
+ # Main entry point (services/)
16
+ UnifiedTypeService → SchemaResolver/ResponseResolver → TypeContext protocol
17
+
18
+ # Usage pattern
19
+ type_service = UnifiedTypeService(schemas, responses)
20
+ python_type = type_service.resolve_schema_type(schema, context, required=True)
21
+ ```
22
+
23
+ ### 2. Protocol-Based Dependency Injection
24
+ ```python
25
+ # contracts/protocols.py - Define interfaces
26
+ class TypeContext(Protocol):
27
+ def add_import(self, import_str: str) -> None: ...
28
+
29
+ # resolvers/ - Use protocols, not concrete types
30
+ def resolve_type(schema: IRSchema, context: TypeContext) -> str:
31
+ # Implementation uses context protocol
32
+ ```
33
+
34
+ ### 3. Error Handling
35
+ ```python
36
+ from .contracts.types import TypeResolutionError
37
+
38
+ # Always wrap resolution failures
39
+ try:
40
+ return resolve_complex_type(schema)
41
+ except Exception as e:
42
+ raise TypeResolutionError(f"Failed to resolve {schema.name}: {e}")
43
+ ```
44
+
45
+ ## Critical Implementation Details
46
+
47
+ ### Schema Type Resolution Priority
48
+ 1. **Enum**: `schema.enum` → `UserStatusEnum`
49
+ 2. **Named Reference**: `schema.type` as schema name → `User`
50
+ 3. **Primitive**: `schema.type` → `str`, `int`, `bool`
51
+ 4. **Array**: `schema.type="array"` → `List[ItemType]`
52
+ 5. **Object**: `schema.type="object"` → `Dict[str, Any]` or dataclass
53
+ 6. **Composition**: `allOf`/`oneOf`/`anyOf` → `Union[...]`
54
+
55
+ ### Forward Reference Handling
56
+ ```python
57
+ # For circular dependencies
58
+ if schema.name in context.forward_refs:
59
+ return f'"{schema.name}"' # String annotation
60
+ ```
61
+
62
+ ### Response Unwrapping Logic
63
+ ```python
64
+ # Detect wrapper responses with single 'data' field
65
+ if (response.schema.type == "object" and
66
+ "data" in response.schema.properties and
67
+ len(response.schema.properties) == 1):
68
+ return resolve_schema_type(response.schema.properties["data"])
69
+ ```
70
+
71
+ ## Dependencies on Other Systems
72
+
73
+ ### From core/
74
+ - `IRSchema`, `IRResponse`, `IROperation` definitions
75
+ - Parsing context for cycle detection state
76
+
77
+ ### From context/
78
+ - `RenderContext` for import management and rendering state
79
+ - Import collection and deduplication
80
+
81
+ ### From helpers/ (Legacy)
82
+ - `TypeHelper` delegates to `UnifiedTypeService`
83
+ - Maintains backward compatibility during transition
84
+
85
+ ## Testing Requirements
86
+
87
+ ### Unit Test Pattern
88
+ ```python
89
+ def test_resolve_schema_type__string_schema__returns_str():
90
+ # Arrange
91
+ schema = IRSchema(type="string")
92
+ mock_context = Mock(spec=TypeContext)
93
+ resolver = OpenAPISchemaResolver({})
94
+
95
+ # Act
96
+ result = resolver.resolve_type(schema, mock_context)
97
+
98
+ # Assert
99
+ assert result == "str"
100
+ ```
101
+
102
+ ### Integration Test Pattern
103
+ ```python
104
+ def test_type_service__complex_schema__resolves_correctly():
105
+ # Test with real schemas and context
106
+ schemas = {"User": IRSchema(...)}
107
+ responses = {"UserResponse": IRResponse(...)}
108
+ service = UnifiedTypeService(schemas, responses)
109
+ # Test actual resolution
110
+ ```
111
+
112
+ ## Common Pitfalls
113
+
114
+ 1. **Context Mutation**: Always pass context, never mutate globally
115
+ 2. **Missing Imports**: Resolver must call `context.add_import()` for complex types
116
+ 3. **Circular Dependencies**: Check `context.forward_refs` before resolution
117
+ 4. **Error Swallowing**: Wrap exceptions in `TypeResolutionError`
118
+
119
+ ## Extension Points
120
+
121
+ ### Adding New Resolvers
122
+ ```python
123
+ # Create new resolver implementing protocols
124
+ class CustomResolver:
125
+ def resolve_type(self, schema: IRSchema, context: TypeContext) -> str:
126
+ # Custom logic
127
+ pass
128
+
129
+ # Register in UnifiedTypeService
130
+ service.register_resolver(CustomResolver())
131
+ ```
132
+
133
+ ### New Response Strategies
134
+ ```python
135
+ # strategies/response_strategy.py
136
+ class CustomResponseStrategy:
137
+ def should_unwrap(self, response: IRResponse) -> bool:
138
+ # Custom unwrapping logic
139
+ pass
140
+ ```
@@ -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[{", ".join(resolved_types)}]"
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[{", ".join(resolved_types)}]"
365
+ union_type = f"Union[{', '.join(resolved_types)}]"
366
366
 
367
367
  return ResolvedType(python_type=union_type, is_optional=not required)