pyopenapi-gen 0.13.0__py3-none-any.whl → 0.14.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.
- pyopenapi_gen/cli.py +3 -3
- pyopenapi_gen/context/import_collector.py +10 -10
- pyopenapi_gen/context/render_context.py +13 -13
- pyopenapi_gen/core/auth/plugins.py +7 -7
- pyopenapi_gen/core/http_status_codes.py +218 -0
- pyopenapi_gen/core/http_transport.py +19 -19
- pyopenapi_gen/core/loader/operations/parser.py +2 -2
- pyopenapi_gen/core/loader/operations/request_body.py +3 -3
- pyopenapi_gen/core/loader/parameters/parser.py +3 -3
- pyopenapi_gen/core/loader/responses/parser.py +2 -2
- pyopenapi_gen/core/loader/schemas/extractor.py +4 -4
- pyopenapi_gen/core/pagination.py +3 -3
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/list_response.py +3 -3
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/missing_ref.py +2 -2
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/new_schema.py +3 -3
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/stripped_suffix.py +3 -3
- pyopenapi_gen/core/parsing/common/ref_resolution/resolve_schema_ref.py +2 -2
- pyopenapi_gen/core/parsing/common/type_parser.py +2 -3
- pyopenapi_gen/core/parsing/context.py +10 -10
- pyopenapi_gen/core/parsing/cycle_helpers.py +5 -2
- pyopenapi_gen/core/parsing/keywords/all_of_parser.py +5 -5
- pyopenapi_gen/core/parsing/keywords/any_of_parser.py +4 -4
- pyopenapi_gen/core/parsing/keywords/array_items_parser.py +4 -4
- pyopenapi_gen/core/parsing/keywords/one_of_parser.py +4 -4
- pyopenapi_gen/core/parsing/keywords/properties_parser.py +5 -5
- pyopenapi_gen/core/parsing/schema_finalizer.py +15 -15
- pyopenapi_gen/core/parsing/schema_parser.py +44 -25
- pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +4 -4
- pyopenapi_gen/core/parsing/transformers/inline_object_promoter.py +7 -4
- pyopenapi_gen/core/parsing/unified_cycle_detection.py +10 -10
- pyopenapi_gen/core/postprocess_manager.py +85 -12
- pyopenapi_gen/core/schemas.py +10 -10
- pyopenapi_gen/core/streaming_helpers.py +5 -7
- pyopenapi_gen/core/telemetry.py +4 -4
- pyopenapi_gen/core/utils.py +7 -7
- pyopenapi_gen/core/writers/code_writer.py +2 -2
- pyopenapi_gen/core/writers/documentation_writer.py +18 -18
- pyopenapi_gen/core/writers/line_writer.py +3 -3
- pyopenapi_gen/core/writers/python_construct_renderer.py +15 -11
- pyopenapi_gen/emit/models_emitter.py +2 -2
- pyopenapi_gen/emitters/core_emitter.py +3 -5
- pyopenapi_gen/emitters/endpoints_emitter.py +12 -12
- pyopenapi_gen/emitters/exceptions_emitter.py +153 -18
- pyopenapi_gen/emitters/models_emitter.py +6 -6
- pyopenapi_gen/generator/client_generator.py +10 -8
- pyopenapi_gen/helpers/endpoint_utils.py +16 -18
- pyopenapi_gen/helpers/type_cleaner.py +66 -53
- pyopenapi_gen/helpers/type_helper.py +7 -7
- pyopenapi_gen/helpers/type_resolution/array_resolver.py +4 -4
- pyopenapi_gen/helpers/type_resolution/composition_resolver.py +5 -5
- pyopenapi_gen/helpers/type_resolution/finalizer.py +38 -22
- pyopenapi_gen/helpers/type_resolution/named_resolver.py +4 -5
- pyopenapi_gen/helpers/type_resolution/object_resolver.py +11 -11
- pyopenapi_gen/helpers/type_resolution/primitive_resolver.py +1 -2
- pyopenapi_gen/helpers/type_resolution/resolver.py +2 -3
- pyopenapi_gen/ir.py +32 -34
- pyopenapi_gen/types/contracts/protocols.py +5 -5
- pyopenapi_gen/types/contracts/types.py +2 -3
- pyopenapi_gen/types/resolvers/reference_resolver.py +4 -4
- pyopenapi_gen/types/resolvers/response_resolver.py +6 -4
- pyopenapi_gen/types/resolvers/schema_resolver.py +32 -16
- pyopenapi_gen/types/services/type_service.py +55 -9
- pyopenapi_gen/types/strategies/response_strategy.py +6 -7
- pyopenapi_gen/visit/client_visitor.py +5 -7
- pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +7 -7
- pyopenapi_gen/visit/endpoint/generators/request_generator.py +5 -5
- pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +41 -19
- pyopenapi_gen/visit/endpoint/generators/signature_generator.py +4 -4
- pyopenapi_gen/visit/endpoint/generators/url_args_generator.py +17 -17
- pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +8 -8
- pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +13 -13
- pyopenapi_gen/visit/exception_visitor.py +54 -16
- pyopenapi_gen/visit/model/alias_generator.py +1 -4
- pyopenapi_gen/visit/model/dataclass_generator.py +139 -10
- pyopenapi_gen/visit/model/model_visitor.py +2 -3
- pyopenapi_gen/visit/visitor.py +3 -3
- {pyopenapi_gen-0.13.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/METADATA +1 -1
- pyopenapi_gen-0.14.1.dist-info/RECORD +132 -0
- pyopenapi_gen-0.13.0.dist-info/RECORD +0 -131
- {pyopenapi_gen-0.13.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/WHEEL +0 -0
- {pyopenapi_gen-0.13.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/entry_points.txt +0 -0
- {pyopenapi_gen-0.13.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/licenses/LICENSE +0 -0
pyopenapi_gen/cli.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from pathlib import Path
|
2
|
-
from typing import Any,
|
2
|
+
from typing import Any, Union
|
3
3
|
|
4
4
|
import typer
|
5
5
|
import yaml
|
@@ -7,7 +7,7 @@ import yaml
|
|
7
7
|
from .generator.client_generator import ClientGenerator, GenerationError
|
8
8
|
|
9
9
|
|
10
|
-
def _load_spec(path_or_url: str) -> Union[
|
10
|
+
def _load_spec(path_or_url: str) -> Union[dict[str, Any], Any]:
|
11
11
|
"""Load a spec from a file path or URL."""
|
12
12
|
if Path(path_or_url).exists():
|
13
13
|
return yaml.safe_load(Path(path_or_url).read_text())
|
@@ -30,7 +30,7 @@ def main(
|
|
30
30
|
),
|
31
31
|
force: bool = typer.Option(False, "-f", "--force", help="Overwrite without diff check"),
|
32
32
|
no_postprocess: bool = typer.Option(False, "--no-postprocess", help="Skip post-processing (type checking, etc.)"),
|
33
|
-
core_package:
|
33
|
+
core_package: str | None = typer.Option(
|
34
34
|
None,
|
35
35
|
"--core-package",
|
36
36
|
help=(
|
@@ -9,7 +9,7 @@ direct, relative, and plain imports, with methods to add and query import statem
|
|
9
9
|
import logging
|
10
10
|
import sys
|
11
11
|
from collections import defaultdict
|
12
|
-
from typing import
|
12
|
+
from typing import List, Set
|
13
13
|
|
14
14
|
# Initialize module logger
|
15
15
|
logger = logging.getLogger(__name__)
|
@@ -143,18 +143,18 @@ class ImportCollector:
|
|
143
143
|
def __init__(self) -> None:
|
144
144
|
"""Initialize a new ImportCollector with empty collections for all import types."""
|
145
145
|
# Standard imports (from x import y)
|
146
|
-
self.imports:
|
146
|
+
self.imports: dict[str, Set[str]] = {}
|
147
147
|
# Direct imports like 'from datetime import date'
|
148
|
-
# self.direct_imports:
|
148
|
+
# self.direct_imports: dict[str, Set[str]] = {} # Removed
|
149
149
|
# Relative imports like 'from .models import Pet'
|
150
150
|
self.relative_imports: defaultdict[str, set[str]] = defaultdict(set)
|
151
151
|
# Plain imports like 'import json'
|
152
152
|
self.plain_imports: set[str] = set()
|
153
153
|
|
154
154
|
# Path information for the current file, used by get_formatted_imports
|
155
|
-
self._current_file_module_dot_path:
|
156
|
-
self._current_file_package_root:
|
157
|
-
self._current_file_core_pkg_name_for_abs:
|
155
|
+
self._current_file_module_dot_path: str | None = None
|
156
|
+
self._current_file_package_root: str | None = None
|
157
|
+
self._current_file_core_pkg_name_for_abs: str | None = None
|
158
158
|
|
159
159
|
def reset(self) -> None:
|
160
160
|
"""Reset the collector to its initial empty state."""
|
@@ -167,9 +167,9 @@ class ImportCollector:
|
|
167
167
|
|
168
168
|
def set_current_file_context_for_rendering(
|
169
169
|
self,
|
170
|
-
current_module_dot_path:
|
171
|
-
package_root:
|
172
|
-
core_package_name_for_absolute_treatment:
|
170
|
+
current_module_dot_path: str | None,
|
171
|
+
package_root: str | None,
|
172
|
+
core_package_name_for_absolute_treatment: str | None,
|
173
173
|
) -> None:
|
174
174
|
"""Set the context for the current file, used by get_formatted_imports."""
|
175
175
|
self._current_file_module_dot_path = current_module_dot_path
|
@@ -234,7 +234,7 @@ class ImportCollector:
|
|
234
234
|
"""
|
235
235
|
self.plain_imports.add(module)
|
236
236
|
|
237
|
-
def has_import(self, module: str, name:
|
237
|
+
def has_import(self, module: str, name: str | None = None) -> bool:
|
238
238
|
"""Check if a specific module or name within a module is already imported."""
|
239
239
|
if name:
|
240
240
|
# Check absolute/standard imports
|
@@ -12,7 +12,7 @@ import os
|
|
12
12
|
import re
|
13
13
|
import sys
|
14
14
|
from pathlib import Path
|
15
|
-
from typing import
|
15
|
+
from typing import Set
|
16
16
|
|
17
17
|
from pyopenapi_gen import IRSchema
|
18
18
|
from pyopenapi_gen.core.utils import NameSanitizer
|
@@ -50,13 +50,13 @@ class RenderContext:
|
|
50
50
|
|
51
51
|
def __init__(
|
52
52
|
self,
|
53
|
-
file_manager:
|
53
|
+
file_manager: FileManager | None = None,
|
54
54
|
core_package_name: str = "core",
|
55
|
-
package_root_for_generated_code:
|
56
|
-
overall_project_root:
|
57
|
-
parsed_schemas:
|
55
|
+
package_root_for_generated_code: str | None = None,
|
56
|
+
overall_project_root: str | None = None,
|
57
|
+
parsed_schemas: dict[str, IRSchema] | None = None,
|
58
58
|
use_absolute_imports: bool = True,
|
59
|
-
output_package_name:
|
59
|
+
output_package_name: str | None = None,
|
60
60
|
) -> None:
|
61
61
|
"""
|
62
62
|
Initialize a new RenderContext.
|
@@ -77,15 +77,15 @@ class RenderContext:
|
|
77
77
|
self.file_manager = file_manager or FileManager()
|
78
78
|
self.import_collector = ImportCollector()
|
79
79
|
self.generated_modules: Set[str] = set()
|
80
|
-
self.current_file:
|
80
|
+
self.current_file: str | None = None
|
81
81
|
self.core_package_name: str = core_package_name
|
82
|
-
self.package_root_for_generated_code:
|
83
|
-
self.overall_project_root:
|
84
|
-
self.parsed_schemas:
|
82
|
+
self.package_root_for_generated_code: str | None = package_root_for_generated_code
|
83
|
+
self.overall_project_root: str | None = overall_project_root or os.getcwd()
|
84
|
+
self.parsed_schemas: dict[str, IRSchema] | None = parsed_schemas
|
85
85
|
self.use_absolute_imports: bool = use_absolute_imports
|
86
|
-
self.output_package_name:
|
86
|
+
self.output_package_name: str | None = output_package_name
|
87
87
|
# Dictionary to store conditional imports, keyed by condition
|
88
|
-
self.conditional_imports:
|
88
|
+
self.conditional_imports: dict[str, dict[str, Set[str]]] = {}
|
89
89
|
|
90
90
|
def set_current_file(self, abs_path: str) -> None:
|
91
91
|
"""
|
@@ -111,7 +111,7 @@ class RenderContext:
|
|
111
111
|
core_package_name_for_absolute_treatment=self.core_package_name,
|
112
112
|
)
|
113
113
|
|
114
|
-
def add_import(self, logical_module: str, name:
|
114
|
+
def add_import(self, logical_module: str, name: str | None = None, is_typing_import: bool = False) -> None:
|
115
115
|
"""
|
116
116
|
Add an import to the collector.
|
117
117
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Any, Awaitable, Callable
|
1
|
+
from typing import Any, Awaitable, Callable
|
2
2
|
|
3
3
|
from .base import BaseAuth
|
4
4
|
|
@@ -9,7 +9,7 @@ class BearerAuth(BaseAuth):
|
|
9
9
|
def __init__(self, token: str) -> None:
|
10
10
|
self.token = token
|
11
11
|
|
12
|
-
async def authenticate_request(self, request_args:
|
12
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
13
13
|
# Ensure headers dict exists
|
14
14
|
headers = dict(request_args.get("headers", {}))
|
15
15
|
headers["Authorization"] = f"Bearer {self.token}"
|
@@ -20,10 +20,10 @@ class BearerAuth(BaseAuth):
|
|
20
20
|
class HeadersAuth(BaseAuth):
|
21
21
|
"""Authentication plugin for arbitrary headers."""
|
22
22
|
|
23
|
-
def __init__(self, headers:
|
23
|
+
def __init__(self, headers: dict[str, str]) -> None:
|
24
24
|
self.headers = headers
|
25
25
|
|
26
|
-
async def authenticate_request(self, request_args:
|
26
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
27
27
|
# Merge custom headers
|
28
28
|
hdrs = dict(request_args.get("headers", {}))
|
29
29
|
hdrs.update(self.headers)
|
@@ -45,7 +45,7 @@ class ApiKeyAuth(BaseAuth):
|
|
45
45
|
self.location = location
|
46
46
|
self.name = name
|
47
47
|
|
48
|
-
async def authenticate_request(self, request_args:
|
48
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
49
49
|
if self.location == "header":
|
50
50
|
headers = dict(request_args.get("headers", {}))
|
51
51
|
headers[self.name] = self.key
|
@@ -66,7 +66,7 @@ class ApiKeyAuth(BaseAuth):
|
|
66
66
|
class OAuth2Auth(BaseAuth):
|
67
67
|
"""Authentication plugin for OAuth2 Bearer tokens, with optional auto-refresh."""
|
68
68
|
|
69
|
-
def __init__(self, access_token: str, refresh_callback:
|
69
|
+
def __init__(self, access_token: str, refresh_callback: Callable[[str], Awaitable[str]] | None = None) -> None:
|
70
70
|
"""
|
71
71
|
Args:
|
72
72
|
access_token: The OAuth2 access token.
|
@@ -76,7 +76,7 @@ class OAuth2Auth(BaseAuth):
|
|
76
76
|
self.access_token = access_token
|
77
77
|
self.refresh_callback = refresh_callback
|
78
78
|
|
79
|
-
async def authenticate_request(self, request_args:
|
79
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
80
80
|
# In a real implementation, check expiry and refresh if needed
|
81
81
|
if self.refresh_callback is not None:
|
82
82
|
# Optionally refresh token (user must implement expiry logic)
|
@@ -0,0 +1,218 @@
|
|
1
|
+
"""HTTP status code definitions and human-readable names.
|
2
|
+
|
3
|
+
This module provides a registry of standard HTTP status codes with their
|
4
|
+
canonical names according to RFC specifications.
|
5
|
+
|
6
|
+
References:
|
7
|
+
- RFC 9110 (HTTP Semantics): https://www.rfc-editor.org/rfc/rfc9110.html
|
8
|
+
- IANA HTTP Status Code Registry: https://www.iana.org/assignments/http-status-codes/
|
9
|
+
"""
|
10
|
+
|
11
|
+
# Standard HTTP status codes with human-readable names
|
12
|
+
HTTP_STATUS_CODES: dict[int, str] = {
|
13
|
+
# 1xx Informational
|
14
|
+
100: "Continue",
|
15
|
+
101: "Switching Protocols",
|
16
|
+
102: "Processing",
|
17
|
+
103: "Early Hints",
|
18
|
+
# 2xx Success
|
19
|
+
200: "OK",
|
20
|
+
201: "Created",
|
21
|
+
202: "Accepted",
|
22
|
+
203: "Non-Authoritative Information",
|
23
|
+
204: "No Content",
|
24
|
+
205: "Reset Content",
|
25
|
+
206: "Partial Content",
|
26
|
+
207: "Multi-Status",
|
27
|
+
208: "Already Reported",
|
28
|
+
226: "IM Used",
|
29
|
+
# 3xx Redirection
|
30
|
+
300: "Multiple Choices",
|
31
|
+
301: "Moved Permanently",
|
32
|
+
302: "Found",
|
33
|
+
303: "See Other",
|
34
|
+
304: "Not Modified",
|
35
|
+
305: "Use Proxy",
|
36
|
+
307: "Temporary Redirect",
|
37
|
+
308: "Permanent Redirect",
|
38
|
+
# 4xx Client Error
|
39
|
+
400: "Bad Request",
|
40
|
+
401: "Unauthorised",
|
41
|
+
402: "Payment Required",
|
42
|
+
403: "Forbidden",
|
43
|
+
404: "Not Found",
|
44
|
+
405: "Method Not Allowed",
|
45
|
+
406: "Not Acceptable",
|
46
|
+
407: "Proxy Authentication Required",
|
47
|
+
408: "Request Timeout",
|
48
|
+
409: "Conflict",
|
49
|
+
410: "Gone",
|
50
|
+
411: "Length Required",
|
51
|
+
412: "Precondition Failed",
|
52
|
+
413: "Payload Too Large",
|
53
|
+
414: "URI Too Long",
|
54
|
+
415: "Unsupported Media Type",
|
55
|
+
416: "Range Not Satisfiable",
|
56
|
+
417: "Expectation Failed",
|
57
|
+
418: "I'm a teapot",
|
58
|
+
421: "Misdirected Request",
|
59
|
+
422: "Unprocessable Entity",
|
60
|
+
423: "Locked",
|
61
|
+
424: "Failed Dependency",
|
62
|
+
425: "Too Early",
|
63
|
+
426: "Upgrade Required",
|
64
|
+
428: "Precondition Required",
|
65
|
+
429: "Too Many Requests",
|
66
|
+
431: "Request Header Fields Too Large",
|
67
|
+
451: "Unavailable For Legal Reasons",
|
68
|
+
# 5xx Server Error
|
69
|
+
500: "Internal Server Error",
|
70
|
+
501: "Not Implemented",
|
71
|
+
502: "Bad Gateway",
|
72
|
+
503: "Service Unavailable",
|
73
|
+
504: "Gateway Timeout",
|
74
|
+
505: "HTTP Version Not Supported",
|
75
|
+
506: "Variant Also Negotiates",
|
76
|
+
507: "Insufficient Storage",
|
77
|
+
508: "Loop Detected",
|
78
|
+
510: "Not Extended",
|
79
|
+
511: "Network Authentication Required",
|
80
|
+
}
|
81
|
+
|
82
|
+
|
83
|
+
def get_status_name(code: int) -> str:
|
84
|
+
"""Get the human-readable name for an HTTP status code.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
code: HTTP status code (e.g., 404)
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
Human-readable status name (e.g., "Not Found"), or "Unknown" if not found
|
91
|
+
"""
|
92
|
+
return HTTP_STATUS_CODES.get(code, "Unknown")
|
93
|
+
|
94
|
+
|
95
|
+
def is_error_code(code: int) -> bool:
|
96
|
+
"""Check if a status code represents an error (4xx or 5xx).
|
97
|
+
|
98
|
+
Args:
|
99
|
+
code: HTTP status code
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
True if the code is a client or server error, False otherwise
|
103
|
+
"""
|
104
|
+
return 400 <= code < 600
|
105
|
+
|
106
|
+
|
107
|
+
def is_client_error(code: int) -> bool:
|
108
|
+
"""Check if a status code represents a client error (4xx).
|
109
|
+
|
110
|
+
Args:
|
111
|
+
code: HTTP status code
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
True if the code is a client error, False otherwise
|
115
|
+
"""
|
116
|
+
return 400 <= code < 500
|
117
|
+
|
118
|
+
|
119
|
+
def is_server_error(code: int) -> bool:
|
120
|
+
"""Check if a status code represents a server error (5xx).
|
121
|
+
|
122
|
+
Args:
|
123
|
+
code: HTTP status code
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
True if the code is a server error, False otherwise
|
127
|
+
"""
|
128
|
+
return 500 <= code < 600
|
129
|
+
|
130
|
+
|
131
|
+
def is_success_code(code: int) -> bool:
|
132
|
+
"""Check if a status code represents success (2xx).
|
133
|
+
|
134
|
+
Args:
|
135
|
+
code: HTTP status code
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
True if the code is a success code, False otherwise
|
139
|
+
"""
|
140
|
+
return 200 <= code < 300
|
141
|
+
|
142
|
+
|
143
|
+
# Mapping of HTTP status codes to Python exception class names
|
144
|
+
# These are semantically meaningful names that Python developers expect
|
145
|
+
HTTP_EXCEPTION_NAMES: dict[int, str] = {
|
146
|
+
# 4xx Client Errors
|
147
|
+
400: "BadRequestError",
|
148
|
+
401: "UnauthorisedError",
|
149
|
+
402: "PaymentRequiredError",
|
150
|
+
403: "ForbiddenError",
|
151
|
+
404: "NotFoundError",
|
152
|
+
405: "MethodNotAllowedError",
|
153
|
+
406: "NotAcceptableError",
|
154
|
+
407: "ProxyAuthenticationRequiredError",
|
155
|
+
408: "RequestTimeoutError",
|
156
|
+
409: "ConflictError",
|
157
|
+
410: "GoneError",
|
158
|
+
411: "LengthRequiredError",
|
159
|
+
412: "PreconditionFailedError",
|
160
|
+
413: "PayloadTooLargeError",
|
161
|
+
414: "UriTooLongError",
|
162
|
+
415: "UnsupportedMediaTypeError",
|
163
|
+
416: "RangeNotSatisfiableError",
|
164
|
+
417: "ExpectationFailedError",
|
165
|
+
418: "ImATeapotError",
|
166
|
+
421: "MisdirectedRequestError",
|
167
|
+
422: "UnprocessableEntityError",
|
168
|
+
423: "LockedError",
|
169
|
+
424: "FailedDependencyError",
|
170
|
+
425: "TooEarlyError",
|
171
|
+
426: "UpgradeRequiredError",
|
172
|
+
428: "PreconditionRequiredError",
|
173
|
+
429: "TooManyRequestsError",
|
174
|
+
431: "RequestHeaderFieldsTooLargeError",
|
175
|
+
451: "UnavailableForLegalReasonsError",
|
176
|
+
# 5xx Server Errors
|
177
|
+
500: "InternalServerError",
|
178
|
+
501: "NotImplementedError", # Note: Conflicts with Python built-in, will be handled
|
179
|
+
502: "BadGatewayError",
|
180
|
+
503: "ServiceUnavailableError",
|
181
|
+
504: "GatewayTimeoutError",
|
182
|
+
505: "HttpVersionNotSupportedError",
|
183
|
+
506: "VariantAlsoNegotiatesError",
|
184
|
+
507: "InsufficientStorageError",
|
185
|
+
508: "LoopDetectedError",
|
186
|
+
510: "NotExtendedError",
|
187
|
+
511: "NetworkAuthenticationRequiredError",
|
188
|
+
}
|
189
|
+
|
190
|
+
|
191
|
+
def get_exception_class_name(code: int) -> str:
|
192
|
+
"""Get the Python exception class name for an HTTP status code.
|
193
|
+
|
194
|
+
Args:
|
195
|
+
code: HTTP status code (e.g., 404)
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
Python exception class name (e.g., "NotFoundError"), or "Error{code}" as fallback
|
199
|
+
|
200
|
+
Examples:
|
201
|
+
>>> get_exception_class_name(404)
|
202
|
+
'NotFoundError'
|
203
|
+
>>> get_exception_class_name(429)
|
204
|
+
'TooManyRequestsError'
|
205
|
+
>>> get_exception_class_name(999) # Unknown code
|
206
|
+
'Error999'
|
207
|
+
"""
|
208
|
+
# Check if we have a semantic name for this code
|
209
|
+
if code in HTTP_EXCEPTION_NAMES:
|
210
|
+
name = HTTP_EXCEPTION_NAMES[code]
|
211
|
+
# Handle Python keyword conflicts
|
212
|
+
if name == "NotImplementedError":
|
213
|
+
# Avoid conflict with Python's built-in NotImplementedError
|
214
|
+
return "HttpNotImplementedError"
|
215
|
+
return name
|
216
|
+
|
217
|
+
# Fallback to Error{code} for codes we don't have semantic names for
|
218
|
+
return f"Error{code}"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Any,
|
1
|
+
from typing import Any, Protocol
|
2
2
|
|
3
3
|
import httpx
|
4
4
|
|
@@ -83,47 +83,47 @@ class HttpxTransport:
|
|
83
83
|
|
84
84
|
Attributes:
|
85
85
|
_client (httpx.AsyncClient): Configured HTTPX async client for all requests.
|
86
|
-
_auth (
|
87
|
-
_bearer_token (
|
88
|
-
_default_headers (
|
86
|
+
_auth (BaseAuth | None): Optional authentication plugin for request signing (can be CompositeAuth).
|
87
|
+
_bearer_token (str | None): Optional bearer token for Authorization header.
|
88
|
+
_default_headers (dict[str, str] | None): Default headers to apply to all requests.
|
89
89
|
"""
|
90
90
|
|
91
91
|
def __init__(
|
92
92
|
self,
|
93
93
|
base_url: str,
|
94
|
-
timeout:
|
95
|
-
auth:
|
96
|
-
bearer_token:
|
97
|
-
default_headers:
|
94
|
+
timeout: float | None = None,
|
95
|
+
auth: BaseAuth | None = None,
|
96
|
+
bearer_token: str | None = None,
|
97
|
+
default_headers: dict[str, str] | None = None,
|
98
98
|
) -> None:
|
99
99
|
"""
|
100
100
|
Initializes the HttpxTransport.
|
101
101
|
|
102
102
|
Args:
|
103
103
|
base_url (str): The base URL for all API requests made through this transport.
|
104
|
-
timeout (
|
105
|
-
auth (
|
106
|
-
bearer_token (
|
107
|
-
default_headers (
|
104
|
+
timeout (float | None): The default timeout in seconds for requests. If None, httpx's default is used.
|
105
|
+
auth (BaseAuth | None): Optional authentication plugin for request signing (can be CompositeAuth).
|
106
|
+
bearer_token (str | None): Optional raw bearer token string for Authorization header.
|
107
|
+
default_headers (dict[str, str] | None): Default headers to apply to all requests.
|
108
108
|
|
109
109
|
Note:
|
110
110
|
If both auth and bearer_token are provided, auth takes precedence.
|
111
111
|
"""
|
112
112
|
self._client: httpx.AsyncClient = httpx.AsyncClient(base_url=base_url, timeout=timeout)
|
113
|
-
self._auth:
|
114
|
-
self._bearer_token:
|
115
|
-
self._default_headers:
|
113
|
+
self._auth: BaseAuth | None = auth
|
114
|
+
self._bearer_token: str | None = bearer_token
|
115
|
+
self._default_headers: dict[str, str] | None = default_headers
|
116
116
|
|
117
117
|
async def _prepare_headers(
|
118
118
|
self,
|
119
|
-
current_request_kwargs:
|
120
|
-
) ->
|
119
|
+
current_request_kwargs: dict[str, Any],
|
120
|
+
) -> dict[str, str]:
|
121
121
|
"""
|
122
122
|
Prepares headers for an HTTP request, incorporating default headers,
|
123
123
|
request-specific headers, and authentication.
|
124
124
|
"""
|
125
125
|
# Initialize headers for the current request
|
126
|
-
prepared_headers:
|
126
|
+
prepared_headers: dict[str, str] = {}
|
127
127
|
|
128
128
|
# 1. Apply transport-level default headers
|
129
129
|
if self._default_headers:
|
@@ -180,7 +180,7 @@ class HttpxTransport:
|
|
180
180
|
HTTPError: For non-2xx HTTP responses.
|
181
181
|
"""
|
182
182
|
# Prepare request arguments, excluding headers initially
|
183
|
-
request_args:
|
183
|
+
request_args: dict[str, Any] = {k: v for k, v in kwargs.items() if k != "headers"}
|
184
184
|
|
185
185
|
# This method handles default headers, request-specific headers, and authentication
|
186
186
|
prepared_headers = await self._prepare_headers(kwargs)
|
@@ -7,7 +7,7 @@ from __future__ import annotations
|
|
7
7
|
|
8
8
|
import logging
|
9
9
|
import warnings
|
10
|
-
from typing import Any, List, Mapping,
|
10
|
+
from typing import Any, List, Mapping, cast
|
11
11
|
|
12
12
|
from pyopenapi_gen import HTTPMethod, IROperation, IRParameter, IRRequestBody, IRResponse
|
13
13
|
from pyopenapi_gen.core.loader.operations.post_processor import post_process_operation
|
@@ -96,7 +96,7 @@ def parse_operations(
|
|
96
96
|
params.append(parse_parameter(resolved_p_param_node, context, operation_id_for_promo=operation_id))
|
97
97
|
|
98
98
|
# Parse request body
|
99
|
-
rb:
|
99
|
+
rb: IRRequestBody | None = None
|
100
100
|
if "requestBody" in node_op:
|
101
101
|
rb = parse_request_body(
|
102
102
|
cast(Mapping[str, Any], node_op["requestBody"]),
|
@@ -6,7 +6,7 @@ Provides functions to parse and transform OpenAPI request bodies into IR format.
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import logging
|
9
|
-
from typing import Any,
|
9
|
+
from typing import Any, Mapping
|
10
10
|
|
11
11
|
from pyopenapi_gen import IRRequestBody, IRSchema
|
12
12
|
from pyopenapi_gen.core.parsing.context import ParsingContext
|
@@ -20,7 +20,7 @@ def parse_request_body(
|
|
20
20
|
raw_request_bodies: Mapping[str, Any],
|
21
21
|
context: ParsingContext,
|
22
22
|
operation_id: str,
|
23
|
-
) ->
|
23
|
+
) -> IRRequestBody | None:
|
24
24
|
"""Parse a request body node into an IRRequestBody.
|
25
25
|
|
26
26
|
Contracts:
|
@@ -55,7 +55,7 @@ def parse_request_body(
|
|
55
55
|
|
56
56
|
required_flag = bool(resolved_rb_node.get("required", False))
|
57
57
|
desc = resolved_rb_node.get("description")
|
58
|
-
content_map:
|
58
|
+
content_map: dict[str, IRSchema] = {}
|
59
59
|
|
60
60
|
parent_promo_name_for_req_body = f"{operation_id}RequestBody"
|
61
61
|
|
@@ -6,7 +6,7 @@ Provides functions to parse and transform OpenAPI parameters into IR format.
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import logging
|
9
|
-
from typing import Any, Mapping,
|
9
|
+
from typing import Any, Mapping, cast
|
10
10
|
|
11
11
|
from pyopenapi_gen import IRParameter, IRSchema
|
12
12
|
from pyopenapi_gen.core.parsing.context import ParsingContext
|
@@ -51,7 +51,7 @@ def resolve_parameter_node_if_ref(param_node_data: Mapping[str, Any], context: P
|
|
51
51
|
def parse_parameter(
|
52
52
|
node: Mapping[str, Any],
|
53
53
|
context: ParsingContext,
|
54
|
-
operation_id_for_promo:
|
54
|
+
operation_id_for_promo: str | None = None,
|
55
55
|
) -> IRParameter:
|
56
56
|
"""Convert an OpenAPI parameter node into IRParameter.
|
57
57
|
|
@@ -74,7 +74,7 @@ def parse_parameter(
|
|
74
74
|
sch = node.get("schema")
|
75
75
|
param_name = node["name"]
|
76
76
|
|
77
|
-
name_for_inline_param_schema:
|
77
|
+
name_for_inline_param_schema: str | None = None
|
78
78
|
if (
|
79
79
|
sch
|
80
80
|
and isinstance(sch, Mapping)
|
@@ -6,7 +6,7 @@ Provides functions to parse and transform OpenAPI responses into IR format.
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import logging
|
9
|
-
from typing import Any,
|
9
|
+
from typing import Any, Mapping
|
10
10
|
|
11
11
|
from pyopenapi_gen import IRResponse, IRSchema
|
12
12
|
from pyopenapi_gen.core.parsing.context import ParsingContext
|
@@ -43,7 +43,7 @@ def parse_response(
|
|
43
43
|
if not operation_id_for_promo:
|
44
44
|
raise ValueError("operation_id_for_promo must be provided")
|
45
45
|
|
46
|
-
content:
|
46
|
+
content: dict[str, IRSchema] = {}
|
47
47
|
STREAM_FORMATS = {
|
48
48
|
"application/octet-stream": "octet-stream",
|
49
49
|
"text/event-stream": "event-stream",
|
@@ -7,7 +7,7 @@ from __future__ import annotations
|
|
7
7
|
|
8
8
|
import copy
|
9
9
|
import logging
|
10
|
-
from typing import Any,
|
10
|
+
from typing import Any, Mapping
|
11
11
|
|
12
12
|
from pyopenapi_gen import IRSchema
|
13
13
|
from pyopenapi_gen.core.parsing.context import ParsingContext
|
@@ -17,7 +17,7 @@ from pyopenapi_gen.core.utils import NameSanitizer
|
|
17
17
|
logger = logging.getLogger(__name__)
|
18
18
|
|
19
19
|
|
20
|
-
def build_schemas(raw_schemas:
|
20
|
+
def build_schemas(raw_schemas: dict[str, Mapping[str, Any]], raw_components: Mapping[str, Any]) -> ParsingContext:
|
21
21
|
"""Build all named schemas up front, populating a ParsingContext.
|
22
22
|
|
23
23
|
Contracts:
|
@@ -47,7 +47,7 @@ def build_schemas(raw_schemas: Dict[str, Mapping[str, Any]], raw_components: Map
|
|
47
47
|
return context
|
48
48
|
|
49
49
|
|
50
|
-
def extract_inline_array_items(schemas:
|
50
|
+
def extract_inline_array_items(schemas: dict[str, IRSchema]) -> dict[str, IRSchema]:
|
51
51
|
"""Extract inline array item schemas as unique named schemas and update references.
|
52
52
|
|
53
53
|
Contracts:
|
@@ -132,7 +132,7 @@ def extract_inline_array_items(schemas: Dict[str, IRSchema]) -> Dict[str, IRSche
|
|
132
132
|
return schemas
|
133
133
|
|
134
134
|
|
135
|
-
def extract_inline_enums(schemas:
|
135
|
+
def extract_inline_enums(schemas: dict[str, IRSchema]) -> dict[str, IRSchema]:
|
136
136
|
"""Extract inline property enums as unique schemas and update property references.
|
137
137
|
|
138
138
|
Also ensures top-level enum schemas are properly marked for generation.
|
pyopenapi_gen/core/pagination.py
CHANGED
@@ -6,11 +6,11 @@ turning them into convenient async iterators that automatically handle
|
|
6
6
|
fetching subsequent pages.
|
7
7
|
"""
|
8
8
|
|
9
|
-
from typing import Any, AsyncIterator, Awaitable, Callable
|
9
|
+
from typing import Any, AsyncIterator, Awaitable, Callable
|
10
10
|
|
11
11
|
|
12
12
|
def paginate_by_next(
|
13
|
-
fetch_page: Callable[..., Awaitable[
|
13
|
+
fetch_page: Callable[..., Awaitable[dict[str, Any]]],
|
14
14
|
items_key: str = "items",
|
15
15
|
next_key: str = "next",
|
16
16
|
**params: Any,
|
@@ -52,7 +52,7 @@ def paginate_by_next(
|
|
52
52
|
while True:
|
53
53
|
result = await fetch_page(**params)
|
54
54
|
# result is expected to be a dict
|
55
|
-
# (assumed since fetch_page is typed to return
|
55
|
+
# (assumed since fetch_page is typed to return dict[str, Any])
|
56
56
|
items = result.get(items_key, [])
|
57
57
|
for item in items:
|
58
58
|
yield item
|
@@ -3,7 +3,7 @@ Module for handling ListResponse fallback strategy.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
-
from typing import Any, Callable, Mapping
|
6
|
+
from typing import Any, Callable, Mapping
|
7
7
|
|
8
8
|
from pyopenapi_gen.ir import IRSchema
|
9
9
|
|
@@ -17,8 +17,8 @@ def try_list_response_fallback(
|
|
17
17
|
ref_value: str,
|
18
18
|
context: ParsingContext,
|
19
19
|
max_depth: int,
|
20
|
-
parse_fn: Callable[[
|
21
|
-
) ->
|
20
|
+
parse_fn: Callable[[str | None, Mapping[str, Any] | None, ParsingContext, int], IRSchema],
|
21
|
+
) -> IRSchema | None:
|
22
22
|
"""
|
23
23
|
Attempts to resolve a reference by treating it as a list of a base type.
|
24
24
|
|