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,224 @@
1
+ # core/ - OpenAPI Parsing and Runtime Components
2
+
3
+ ## Why This Folder?
4
+ Raw OpenAPI spec → Intermediate Representation (IR) transformation. Contains parsing logic, cycle detection, and runtime components that get copied to generated clients.
5
+
6
+ ## Key Dependencies
7
+ - **Input**: Raw OpenAPI spec dicts from YAML/JSON
8
+ - **Output**: `IRSpec`, `IRSchema`, `IRResponse`, `IROperation` objects
9
+ - **Runtime**: Components copied to generated clients (`auth/`, `exceptions.py`, `http_transport.py`)
10
+
11
+ ## Essential Architecture
12
+
13
+ ### 1. Parsing Pipeline
14
+ ```mermaid
15
+ graph LR
16
+ A[Raw OpenAPI] --> B[loader/] --> C[parsing/] --> D[IRSpec]
17
+ B --> E[schemas/extractor.py]
18
+ C --> F[unified_cycle_detection.py]
19
+ C --> G[transformers/]
20
+ ```
21
+
22
+ ### 2. Cycle Detection State Machine
23
+ ```python
24
+ # parsing/unified_cycle_detection.py
25
+ @dataclass
26
+ class CycleInfo:
27
+ type: CycleType # STRUCTURAL, SELF_REF, DEPTH_LIMIT
28
+ action: CycleAction # PLACEHOLDER, FORWARD_REF, DEPTH_CUTOFF
29
+ depth: int
30
+
31
+ # Usage in parsing
32
+ if context.detect_cycle(schema_name, current_depth):
33
+ return create_cycle_placeholder(schema_name, cycle_info)
34
+ ```
35
+
36
+ ## Critical Components
37
+
38
+ ### parsing/schema_parser.py
39
+ **Purpose**: Main schema parsing with cycle detection
40
+ ```python
41
+ def parse_schema(schema_data: Dict[str, Any], context: ParsingContext) -> IRSchema:
42
+ # 1. Cycle detection
43
+ # 2. Keyword parsing (allOf, oneOf, anyOf)
44
+ # 3. Transformer application
45
+ # 4. IR object creation
46
+ ```
47
+
48
+ ### parsing/transformers/
49
+ **Purpose**: Modify parsed schemas before IR creation
50
+ - **inline_enum_extractor.py**: Extract inline enums to global schemas
51
+ - **inline_object_promoter.py**: Promote inline objects to named schemas
52
+
53
+ ### loader/
54
+ **Purpose**: Load and validate OpenAPI specs
55
+ ```python
56
+ # loader/loader.py
57
+ def load_spec(spec_path: str) -> IRSpec:
58
+ # 1. Load YAML/JSON
59
+ # 2. Validate OpenAPI format
60
+ # 3. Parse operations, schemas, responses
61
+ # 4. Build IRSpec
62
+ ```
63
+
64
+ ### Runtime Components (Copied to Clients)
65
+
66
+ #### auth/
67
+ ```python
68
+ # auth/base.py - Base authentication classes
69
+ class AuthBase(ABC):
70
+ async def apply_auth(self, request: httpx.Request) -> httpx.Request:
71
+ # Modify request with auth info
72
+
73
+ # auth/plugins.py - Concrete implementations
74
+ class BearerAuth(AuthBase):
75
+ def __init__(self, token: str): ...
76
+ ```
77
+
78
+ #### exceptions.py
79
+ ```python
80
+ # Exception hierarchy for generated clients
81
+ class ClientError(Exception): ...
82
+ class ServerError(ClientError): ...
83
+ class ValidationError(ClientError): ...
84
+ ```
85
+
86
+ #### http_transport.py
87
+ ```python
88
+ # HTTP client abstraction
89
+ class HTTPTransport:
90
+ def __init__(self, base_url: str, auth: Optional[AuthBase] = None):
91
+ self.client = httpx.AsyncClient(base_url=base_url)
92
+ ```
93
+
94
+ ## Environment Variables
95
+ ```python
96
+ # parsing/unified_cycle_detection.py
97
+ PYOPENAPI_MAX_DEPTH = int(os.getenv("PYOPENAPI_MAX_DEPTH", "150"))
98
+ PYOPENAPI_MAX_CYCLES = int(os.getenv("PYOPENAPI_MAX_CYCLES", "0"))
99
+ ```
100
+
101
+ ## Dependencies on Other Systems
102
+
103
+ ### From types/
104
+ - Schema → Python type conversion after parsing
105
+ - Type resolution for complex compositions
106
+
107
+ ### From context/
108
+ - `ParsingContext` for cycle detection state
109
+ - Import management during parsing
110
+
111
+ ### To visit/
112
+ - Provides `IRSpec` for visitor pattern traversal
113
+ - IR objects consumed by code generators
114
+
115
+ ## Common Parsing Patterns
116
+
117
+ ### 1. Keyword Composition
118
+ ```python
119
+ # parsing/keywords/all_of_parser.py
120
+ def parse_all_of(all_of_items: List[Dict], context: ParsingContext) -> IRSchema:
121
+ # Merge all schemas into single IR object
122
+ merged_schema = IRSchema(type="object")
123
+ for item in all_of_items:
124
+ parsed_item = parse_schema(item, context)
125
+ merged_schema = merge_schemas(merged_schema, parsed_item)
126
+ return merged_schema
127
+ ```
128
+
129
+ ### 2. Reference Resolution
130
+ ```python
131
+ # parsing/common/ref_resolution/resolve_schema_ref.py
132
+ def resolve_schema_ref(ref: str, context: ParsingContext) -> IRSchema:
133
+ if ref.startswith("#/components/schemas/"):
134
+ schema_name = ref.split("/")[-1]
135
+ return context.get_schema(schema_name)
136
+ raise ValueError(f"Unsupported ref: {ref}")
137
+ ```
138
+
139
+ ### 3. Cycle Detection
140
+ ```python
141
+ # parsing/unified_cycle_detection.py
142
+ def detect_cycle(schema_name: str, current_depth: int, context: ParsingContext) -> Optional[CycleInfo]:
143
+ if current_depth > PYOPENAPI_MAX_DEPTH:
144
+ return CycleInfo(CycleType.DEPTH_LIMIT, CycleAction.DEPTH_CUTOFF, current_depth)
145
+
146
+ if schema_name in context.parsing_stack:
147
+ return CycleInfo(CycleType.STRUCTURAL, CycleAction.PLACEHOLDER, current_depth)
148
+
149
+ return None
150
+ ```
151
+
152
+ ## Testing Requirements
153
+
154
+ ### Parser Tests
155
+ ```python
156
+ def test_parse_schema__all_of_composition__merges_correctly():
157
+ # Test keyword parsers with real OpenAPI data
158
+ schema_data = {
159
+ "allOf": [
160
+ {"type": "object", "properties": {"name": {"type": "string"}}},
161
+ {"type": "object", "properties": {"age": {"type": "integer"}}}
162
+ ]
163
+ }
164
+ # Test parsing result
165
+ ```
166
+
167
+ ### Cycle Detection Tests
168
+ ```python
169
+ def test_unified_cycle_detection__structural_cycle__creates_placeholder():
170
+ # Test cycle detection with circular references
171
+ context = ParsingContext()
172
+ # Create circular reference scenario
173
+ # Verify placeholder creation
174
+ ```
175
+
176
+ ## Extension Points
177
+
178
+ ### Adding New Keywords
179
+ ```python
180
+ # parsing/keywords/new_keyword_parser.py
181
+ def parse_new_keyword(keyword_data: Any, context: ParsingContext) -> IRSchema:
182
+ # Custom keyword parsing logic
183
+ pass
184
+ ```
185
+
186
+ ### Adding New Transformers
187
+ ```python
188
+ # parsing/transformers/new_transformer.py
189
+ def transform_schema(schema: IRSchema, context: ParsingContext) -> IRSchema:
190
+ # Custom transformation logic
191
+ return modified_schema
192
+ ```
193
+
194
+ ## Critical Implementation Details
195
+
196
+ ### IR Object Creation
197
+ ```python
198
+ # Always use IRSchema constructor with all required fields
199
+ schema = IRSchema(
200
+ name=schema_name,
201
+ type=schema_type,
202
+ properties=properties,
203
+ required=required_fields,
204
+ description=description,
205
+ enum=enum_values,
206
+ is_nullable=is_nullable
207
+ )
208
+ ```
209
+
210
+ ### Context State Management
211
+ ```python
212
+ # parsing/context.py
213
+ class ParsingContext:
214
+ def __init__(self):
215
+ self.parsed_schemas: Dict[str, IRSchema] = {}
216
+ self.parsing_stack: List[str] = [] # For cycle detection
217
+ self.forward_refs: Set[str] = set()
218
+
219
+ def enter_schema(self, schema_name: str):
220
+ self.parsing_stack.append(schema_name)
221
+
222
+ def exit_schema(self, schema_name: str):
223
+ self.parsing_stack.remove(schema_name)
224
+ ```
File without changes
@@ -0,0 +1,22 @@
1
+ from typing import Any, Protocol # noqa: F401
2
+
3
+
4
+ class BaseAuth(Protocol):
5
+ """Protocol for authentication plugins."""
6
+
7
+ async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
8
+ """Modify or augment the request arguments for authentication."""
9
+ # Default stub returns the input unchanged
10
+ return request_args
11
+
12
+
13
+ class CompositeAuth(BaseAuth):
14
+ """Compose multiple BaseAuth plugins, applying each in sequence to the request."""
15
+
16
+ def __init__(self, *plugins: BaseAuth):
17
+ self.plugins = plugins
18
+
19
+ async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
20
+ for plugin in self.plugins:
21
+ request_args = await plugin.authenticate_request(request_args)
22
+ return request_args
@@ -0,0 +1,89 @@
1
+ from typing import Any, Awaitable, Callable
2
+
3
+ from .base import BaseAuth
4
+
5
+
6
+ class BearerAuth(BaseAuth):
7
+ """Authentication plugin for Bearer tokens."""
8
+
9
+ def __init__(self, token: str) -> None:
10
+ self.token = token
11
+
12
+ async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
13
+ # Ensure headers dict exists
14
+ headers = dict(request_args.get("headers", {}))
15
+ headers["Authorization"] = f"Bearer {self.token}"
16
+ request_args["headers"] = headers
17
+ return request_args
18
+
19
+
20
+ class HeadersAuth(BaseAuth):
21
+ """Authentication plugin for arbitrary headers."""
22
+
23
+ def __init__(self, headers: dict[str, str]) -> None:
24
+ self.headers = headers
25
+
26
+ async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
27
+ # Merge custom headers
28
+ hdrs = dict(request_args.get("headers", {}))
29
+ hdrs.update(self.headers)
30
+ request_args["headers"] = hdrs
31
+ return request_args
32
+
33
+
34
+ class ApiKeyAuth(BaseAuth):
35
+ """Authentication plugin for API keys (header, query, or cookie)."""
36
+
37
+ def __init__(self, key: str, location: str = "header", name: str = "X-API-Key") -> None:
38
+ """
39
+ Args:
40
+ key: The API key value.
41
+ location: Where to add the key ("header", "query", or "cookie").
42
+ name: The name of the header/query/cookie parameter.
43
+ """
44
+ self.key = key
45
+ self.location = location
46
+ self.name = name
47
+
48
+ async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
49
+ if self.location == "header":
50
+ headers = dict(request_args.get("headers", {}))
51
+ headers[self.name] = self.key
52
+ request_args["headers"] = headers
53
+ elif self.location == "query":
54
+ params = dict(request_args.get("params", {}))
55
+ params[self.name] = self.key
56
+ request_args["params"] = params
57
+ elif self.location == "cookie":
58
+ cookies = dict(request_args.get("cookies", {}))
59
+ cookies[self.name] = self.key
60
+ request_args["cookies"] = cookies
61
+ else:
62
+ raise ValueError(f"Invalid API key location: {self.location}")
63
+ return request_args
64
+
65
+
66
+ class OAuth2Auth(BaseAuth):
67
+ """Authentication plugin for OAuth2 Bearer tokens, with optional auto-refresh."""
68
+
69
+ def __init__(self, access_token: str, refresh_callback: Callable[[str], Awaitable[str]] | None = None) -> None:
70
+ """
71
+ Args:
72
+ access_token: The OAuth2 access token.
73
+ refresh_callback: Optional async function to refresh the token. If provided, will be called
74
+ if token is expired.
75
+ """
76
+ self.access_token = access_token
77
+ self.refresh_callback = refresh_callback
78
+
79
+ async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
80
+ # In a real implementation, check expiry and refresh if needed
81
+ if self.refresh_callback is not None:
82
+ # Optionally refresh token (user must implement expiry logic)
83
+ new_token = await self.refresh_callback(self.access_token)
84
+ if new_token and new_token != self.access_token:
85
+ self.access_token = new_token
86
+ headers = dict(request_args.get("headers", {}))
87
+ headers["Authorization"] = f"Bearer {self.access_token}"
88
+ request_args["headers"] = headers
89
+ return request_args