pyopenapi-gen 0.14.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 +2 -4
- 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/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 +10 -10
- 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 +4 -3
- pyopenapi_gen/emitters/models_emitter.py +6 -6
- pyopenapi_gen/generator/client_generator.py +6 -6
- 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 +38 -17
- 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/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.14.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.14.0.dist-info/RECORD +0 -132
- {pyopenapi_gen-0.14.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/WHEEL +0 -0
- {pyopenapi_gen-0.14.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/entry_points.txt +0 -0
- {pyopenapi_gen-0.14.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/licenses/LICENSE +0 -0
pyopenapi_gen/core/telemetry.py
CHANGED
@@ -8,7 +8,7 @@ usage telemetry for PyOpenAPI Generator. Telemetry is opt-in only.
|
|
8
8
|
import json
|
9
9
|
import os
|
10
10
|
import time
|
11
|
-
from typing import Any
|
11
|
+
from typing import Any
|
12
12
|
|
13
13
|
|
14
14
|
class TelemetryClient:
|
@@ -24,7 +24,7 @@ class TelemetryClient:
|
|
24
24
|
enabled: Whether telemetry is currently enabled
|
25
25
|
"""
|
26
26
|
|
27
|
-
def __init__(self, enabled:
|
27
|
+
def __init__(self, enabled: bool | None = None) -> None:
|
28
28
|
"""
|
29
29
|
Initialize a new TelemetryClient.
|
30
30
|
|
@@ -38,7 +38,7 @@ class TelemetryClient:
|
|
38
38
|
else:
|
39
39
|
self.enabled = enabled
|
40
40
|
|
41
|
-
def track_event(self, event: str, properties:
|
41
|
+
def track_event(self, event: str, properties: dict[str, Any] | None = None) -> None:
|
42
42
|
"""
|
43
43
|
Track a telemetry event if telemetry is enabled.
|
44
44
|
|
@@ -52,7 +52,7 @@ class TelemetryClient:
|
|
52
52
|
if not self.enabled:
|
53
53
|
return
|
54
54
|
|
55
|
-
data:
|
55
|
+
data: dict[str, Any] = {
|
56
56
|
"event": event,
|
57
57
|
"properties": properties or {},
|
58
58
|
"timestamp": time.time(),
|
pyopenapi_gen/core/utils.py
CHANGED
@@ -8,7 +8,7 @@ import keyword
|
|
8
8
|
import logging
|
9
9
|
import re
|
10
10
|
from datetime import datetime
|
11
|
-
from typing import Any,
|
11
|
+
from typing import Any, Set, Type, TypeVar, cast
|
12
12
|
|
13
13
|
logger = logging.getLogger(__name__)
|
14
14
|
|
@@ -228,7 +228,7 @@ class ParamSubstitutor:
|
|
228
228
|
"""Helper for rendering path templates with path parameters."""
|
229
229
|
|
230
230
|
@staticmethod
|
231
|
-
def render_path(template: str, values:
|
231
|
+
def render_path(template: str, values: dict[str, Any]) -> str:
|
232
232
|
"""Replace placeholders in a URL path template using provided values."""
|
233
233
|
rendered = template
|
234
234
|
for key, val in values.items():
|
@@ -240,7 +240,7 @@ class KwargsBuilder:
|
|
240
240
|
"""Builder for assembling HTTP request keyword arguments."""
|
241
241
|
|
242
242
|
def __init__(self) -> None:
|
243
|
-
self._kwargs:
|
243
|
+
self._kwargs: dict[str, Any] = {}
|
244
244
|
|
245
245
|
def with_params(self, **params: Any) -> "KwargsBuilder":
|
246
246
|
"""Add query parameters, skipping None values."""
|
@@ -254,7 +254,7 @@ class KwargsBuilder:
|
|
254
254
|
self._kwargs["json"] = body
|
255
255
|
return self
|
256
256
|
|
257
|
-
def build(self) ->
|
257
|
+
def build(self) -> dict[str, Any]:
|
258
258
|
"""Return the assembled kwargs dictionary."""
|
259
259
|
return self._kwargs
|
260
260
|
|
@@ -263,10 +263,10 @@ class Formatter:
|
|
263
263
|
"""Helper to format code using Black, falling back to unformatted content if Black is unavailable or errors."""
|
264
264
|
|
265
265
|
def __init__(self) -> None:
|
266
|
-
from typing import Any, Callable
|
266
|
+
from typing import Any, Callable
|
267
267
|
|
268
|
-
self._file_mode:
|
269
|
-
self._format_str:
|
268
|
+
self._file_mode: Any | None = None
|
269
|
+
self._format_str: Callable[..., str] | None = None
|
270
270
|
try:
|
271
271
|
from black import FileMode, format_str
|
272
272
|
|
@@ -6,7 +6,7 @@ writing lines and blocks, and supporting wrapped output for code and docstrings.
|
|
6
6
|
to be used by code generation visitors and emitters to ensure consistent, readable output.
|
7
7
|
"""
|
8
8
|
|
9
|
-
from typing import List
|
9
|
+
from typing import List
|
10
10
|
|
11
11
|
from .line_writer import LineWriter
|
12
12
|
|
@@ -88,7 +88,7 @@ class CodeWriter:
|
|
88
88
|
self.writer.max_width = old_width
|
89
89
|
|
90
90
|
def write_function_signature(
|
91
|
-
self, name: str, args: List[str], return_type:
|
91
|
+
self, name: str, args: List[str], return_type: str | None = None, async_: bool = False
|
92
92
|
) -> None:
|
93
93
|
"""
|
94
94
|
Write a function or method signature, with each argument on its own line and correct indentation.
|
@@ -6,7 +6,7 @@ for building comprehensive, type-rich docstrings for generated Python code. It s
|
|
6
6
|
alignment, line wrapping, and section formatting for Args, Returns, and Raises.
|
7
7
|
"""
|
8
8
|
|
9
|
-
from typing import List,
|
9
|
+
from typing import List, Tuple, Union
|
10
10
|
|
11
11
|
from .line_writer import LineWriter
|
12
12
|
|
@@ -16,36 +16,36 @@ class DocumentationBlock:
|
|
16
16
|
Data container for docstring content.
|
17
17
|
|
18
18
|
Attributes:
|
19
|
-
summary (
|
20
|
-
description (
|
19
|
+
summary (str | None): The summary line for the docstring.
|
20
|
+
description (str | None): The detailed description.
|
21
21
|
args (Optional[List[Union[Tuple[str, str, str], Tuple[str, str]]]]):
|
22
22
|
List of arguments as (name, type, desc) or (type, desc) tuples.
|
23
|
-
returns (
|
24
|
-
raises (
|
23
|
+
returns (Tuple[str, str] | None): The return type and description.
|
24
|
+
raises (List[Tuple[str, str]] | None): List of (exception type, description) tuples.
|
25
25
|
"""
|
26
26
|
|
27
27
|
def __init__(
|
28
28
|
self,
|
29
|
-
summary:
|
30
|
-
description:
|
31
|
-
args:
|
32
|
-
returns:
|
33
|
-
raises:
|
29
|
+
summary: str | None = None,
|
30
|
+
description: str | None = None,
|
31
|
+
args: List[Union[Tuple[str, str, str], Tuple[str, str]]] | None = None,
|
32
|
+
returns: Tuple[str, str] | None = None,
|
33
|
+
raises: List[Tuple[str, str]] | None = None,
|
34
34
|
) -> None:
|
35
35
|
"""
|
36
36
|
Initialize a DocumentationBlock.
|
37
37
|
|
38
38
|
Args:
|
39
|
-
summary (
|
40
|
-
description (
|
39
|
+
summary (str | None): The summary line.
|
40
|
+
description (str | None): The detailed description.
|
41
41
|
args (Optional[List[Union[Tuple[str, str, str], Tuple[str, str]]]]): Arguments.
|
42
|
-
returns (
|
43
|
-
raises (
|
42
|
+
returns (Tuple[str, str] | None): Return type and description.
|
43
|
+
raises (List[Tuple[str, str]] | None): Exceptions.
|
44
44
|
"""
|
45
|
-
self.summary:
|
46
|
-
self.description:
|
45
|
+
self.summary: str | None = summary
|
46
|
+
self.description: str | None = description
|
47
47
|
self.args: List[Union[Tuple[str, str, str], Tuple[str, str]]] = args or []
|
48
|
-
self.returns:
|
48
|
+
self.returns: Tuple[str, str] | None = returns
|
49
49
|
self.raises: List[Tuple[str, str]] = raises or []
|
50
50
|
|
51
51
|
|
@@ -58,7 +58,7 @@ class DocumentationFormatter:
|
|
58
58
|
self.width: int = width
|
59
59
|
self.min_desc_col: int = min_desc_col
|
60
60
|
|
61
|
-
def wrap(self, text: str, indent: int, prefix:
|
61
|
+
def wrap(self, text: str, indent: int, prefix: str | None = None) -> List[str]:
|
62
62
|
if not text:
|
63
63
|
return []
|
64
64
|
writer = LineWriter(max_width=self.width)
|
@@ -5,7 +5,7 @@ This class is designed for use in both code and documentation generation, provid
|
|
5
5
|
new lines, and query the current line's width.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from typing import List
|
8
|
+
from typing import List
|
9
9
|
|
10
10
|
|
11
11
|
class LineWriter:
|
@@ -169,7 +169,7 @@ class LineWriter:
|
|
169
169
|
self.lines[-1] += " " * (col - current - 1)
|
170
170
|
# If already at or past col, do nothing
|
171
171
|
|
172
|
-
def append_wrapped_at_column(self, text: str, width: int, col:
|
172
|
+
def append_wrapped_at_column(self, text: str, width: int, col: int | None = None) -> None:
|
173
173
|
"""
|
174
174
|
Append text, wrapping as needed, so that the first line continues from the current position,
|
175
175
|
and all subsequent lines start at column `col`.
|
@@ -180,7 +180,7 @@ class LineWriter:
|
|
180
180
|
Args:
|
181
181
|
text (str) : The text to append and wrap.
|
182
182
|
width (int) : The maximum line width.
|
183
|
-
col (
|
183
|
+
col (int | None) : The column at which to start wrapped lines. If None, uses current
|
184
184
|
line width.
|
185
185
|
"""
|
186
186
|
import textwrap
|
@@ -7,7 +7,7 @@ It handles all the details of formatting, import registration, and docstring gen
|
|
7
7
|
for these constructs.
|
8
8
|
"""
|
9
9
|
|
10
|
-
from typing import
|
10
|
+
from typing import List, Tuple
|
11
11
|
|
12
12
|
from pyopenapi_gen.context.render_context import RenderContext
|
13
13
|
|
@@ -35,7 +35,7 @@ class PythonConstructRenderer:
|
|
35
35
|
self,
|
36
36
|
alias_name: str,
|
37
37
|
target_type: str,
|
38
|
-
description:
|
38
|
+
description: str | None,
|
39
39
|
context: RenderContext,
|
40
40
|
) -> str:
|
41
41
|
"""
|
@@ -80,7 +80,7 @@ class PythonConstructRenderer:
|
|
80
80
|
enum_name: str,
|
81
81
|
base_type: str, # 'str' or 'int'
|
82
82
|
values: List[Tuple[str, str | int]], # List of (MEMBER_NAME, value)
|
83
|
-
description:
|
83
|
+
description: str | None,
|
84
84
|
context: RenderContext,
|
85
85
|
) -> str:
|
86
86
|
"""
|
@@ -143,10 +143,10 @@ class PythonConstructRenderer:
|
|
143
143
|
def render_dataclass(
|
144
144
|
self,
|
145
145
|
class_name: str,
|
146
|
-
fields: List[Tuple[str, str,
|
147
|
-
description:
|
146
|
+
fields: List[Tuple[str, str, str | None, str | None]], # name, type_hint, default_expr, description
|
147
|
+
description: str | None,
|
148
148
|
context: RenderContext,
|
149
|
-
field_mappings:
|
149
|
+
field_mappings: dict[str, str] | None = None,
|
150
150
|
) -> str:
|
151
151
|
"""
|
152
152
|
Render a dataclass as Python code with BaseSchema support.
|
@@ -168,7 +168,7 @@ class PythonConstructRenderer:
|
|
168
168
|
\"\"\"User information with automatic JSON field mapping.\"\"\"
|
169
169
|
id_: str
|
170
170
|
first_name: str
|
171
|
-
email:
|
171
|
+
email: str | None = None
|
172
172
|
is_active: bool = True
|
173
173
|
|
174
174
|
class Meta:
|
@@ -274,9 +274,9 @@ class PythonConstructRenderer:
|
|
274
274
|
def render_class(
|
275
275
|
self,
|
276
276
|
class_name: str,
|
277
|
-
base_classes:
|
278
|
-
docstring:
|
279
|
-
body_lines:
|
277
|
+
base_classes: List[str] | None,
|
278
|
+
docstring: str | None,
|
279
|
+
body_lines: List[str] | None,
|
280
280
|
context: RenderContext,
|
281
281
|
) -> str:
|
282
282
|
"""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
from pathlib import Path
|
3
|
-
from typing import List
|
3
|
+
from typing import List
|
4
4
|
|
5
5
|
from pyopenapi_gen.context.render_context import RenderContext
|
6
6
|
from pyopenapi_gen.core.utils import NameSanitizer
|
@@ -20,7 +20,7 @@ class ModelsEmitter:
|
|
20
20
|
# self.writer an instance CodeWriter() here seems unused globally for this emitter.
|
21
21
|
# Each file generation part either writes directly or uses a local CodeWriter.
|
22
22
|
|
23
|
-
def _generate_model_file(self, schema_ir: IRSchema, models_dir: Path) ->
|
23
|
+
def _generate_model_file(self, schema_ir: IRSchema, models_dir: Path) -> str | None:
|
24
24
|
"""Generates a single Python file for a given IRSchema. Returns file path if generated."""
|
25
25
|
if not schema_ir.name:
|
26
26
|
logger.warning(f"Skipping model generation for schema without a name: {schema_ir}")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import importlib.resources
|
2
2
|
import os
|
3
|
-
from typing import List
|
3
|
+
from typing import List
|
4
4
|
|
5
5
|
from pyopenapi_gen.context.file_manager import FileManager
|
6
6
|
|
@@ -22,12 +22,10 @@ CORE_README_TEMPLATE_FILENAME = "README.md"
|
|
22
22
|
|
23
23
|
CONFIG_TEMPLATE = """
|
24
24
|
from dataclasses import dataclass
|
25
|
-
from typing import Optional
|
26
|
-
|
27
25
|
@dataclass
|
28
26
|
class ClientConfig:
|
29
27
|
base_url: str
|
30
|
-
timeout:
|
28
|
+
timeout: float | None = 30.0
|
31
29
|
"""
|
32
30
|
|
33
31
|
|
@@ -35,7 +33,7 @@ class CoreEmitter:
|
|
35
33
|
"""Copies all required runtime files into the generated core module."""
|
36
34
|
|
37
35
|
def __init__(
|
38
|
-
self, core_dir: str = "core", core_package: str = "core", exception_alias_names:
|
36
|
+
self, core_dir: str = "core", core_package: str = "core", exception_alias_names: List[str] | None = None
|
39
37
|
):
|
40
38
|
# core_dir is the relative path WITHIN the output package, e.g., "core" or "shared/core"
|
41
39
|
# core_package is the Python import name, e.g., "core" or "shared.core"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
from pathlib import Path
|
3
|
-
from typing import
|
3
|
+
from typing import List, Tuple
|
4
4
|
|
5
5
|
from pyopenapi_gen import IROperation, IRParameter, IRRequestBody
|
6
6
|
from pyopenapi_gen.context.render_context import RenderContext
|
@@ -17,7 +17,7 @@ PARAM_TYPE_MAPPING = {
|
|
17
17
|
"boolean": "bool",
|
18
18
|
"string": "str",
|
19
19
|
"array": "List",
|
20
|
-
"object": "
|
20
|
+
"object": "dict[str, Any]",
|
21
21
|
}
|
22
22
|
# Format-specific overrides
|
23
23
|
PARAM_FORMAT_MAPPING = {
|
@@ -47,7 +47,7 @@ def schema_to_type(schema: IRParameter) -> str:
|
|
47
47
|
# Array handling
|
48
48
|
elif s.type == "array" and s.items:
|
49
49
|
# For array items, we recursively call schema_to_type.
|
50
|
-
# The nullability of the item_type itself (e.g. List[
|
50
|
+
# The nullability of the item_type itself (e.g. List[int | None])
|
51
51
|
# will be handled by the recursive call based on s.items.is_nullable.
|
52
52
|
item_schema_as_param = IRParameter(name="_item", param_in="_internal", required=False, schema=s.items)
|
53
53
|
item_type_str = schema_to_type(item_schema_as_param)
|
@@ -70,8 +70,8 @@ def schema_to_type(schema: IRParameter) -> str:
|
|
70
70
|
# 2. Apply nullability based on IRSchema's is_nullable field
|
71
71
|
# This s.is_nullable should be the source of truth from the IR after parsing.
|
72
72
|
if s.is_nullable:
|
73
|
-
# Ensure "Any" also gets wrapped, e.g.
|
74
|
-
py_type = f"
|
73
|
+
# Ensure "Any" also gets wrapped, e.g. Any | None
|
74
|
+
py_type = f"{py_type} | None"
|
75
75
|
|
76
76
|
return py_type
|
77
77
|
|
@@ -82,7 +82,7 @@ def _get_request_body_type(body: IRRequestBody) -> str:
|
|
82
82
|
if "json" in mt.lower():
|
83
83
|
return schema_to_type(IRParameter(name="body", param_in="body", required=body.required, schema=sch))
|
84
84
|
# Fallback to generic dict
|
85
|
-
return "
|
85
|
+
return "dict[str, Any]"
|
86
86
|
|
87
87
|
|
88
88
|
def _deduplicate_tag_clients(client_classes: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
|
@@ -106,7 +106,7 @@ class EndpointsEmitter:
|
|
106
106
|
def __init__(self, context: RenderContext) -> None:
|
107
107
|
self.context = context
|
108
108
|
self.formatter = Formatter()
|
109
|
-
self.visitor:
|
109
|
+
self.visitor: EndpointVisitor | None = None
|
110
110
|
|
111
111
|
def _deduplicate_operation_ids(self, operations: List[IROperation]) -> None:
|
112
112
|
"""
|
@@ -115,7 +115,7 @@ class EndpointsEmitter:
|
|
115
115
|
Args:
|
116
116
|
operations: List of operations for a single tag.
|
117
117
|
"""
|
118
|
-
seen_methods:
|
118
|
+
seen_methods: dict[str, int] = {}
|
119
119
|
for op in operations:
|
120
120
|
method_name = NameSanitizer.sanitize_method_name(op.operation_id)
|
121
121
|
if method_name in seen_methods:
|
@@ -144,7 +144,7 @@ class EndpointsEmitter:
|
|
144
144
|
self.context.file_manager.write_file(str(file_path), content)
|
145
145
|
|
146
146
|
# Ensure parsed_schemas is at least an empty dict if None,
|
147
|
-
# as EndpointVisitor expects
|
147
|
+
# as EndpointVisitor expects dict[str, IRSchema]
|
148
148
|
current_parsed_schemas = self.context.parsed_schemas
|
149
149
|
if current_parsed_schemas is None:
|
150
150
|
logger.warning(
|
@@ -156,8 +156,8 @@ class EndpointsEmitter:
|
|
156
156
|
if self.visitor is None:
|
157
157
|
self.visitor = EndpointVisitor(current_parsed_schemas) # Pass the (potentially defaulted) dict
|
158
158
|
|
159
|
-
tag_key_to_ops:
|
160
|
-
tag_key_to_candidates:
|
159
|
+
tag_key_to_ops: dict[str, List[IROperation]] = {}
|
160
|
+
tag_key_to_candidates: dict[str, List[str]] = {}
|
161
161
|
for op in operations:
|
162
162
|
tags = op.tags or [DEFAULT_TAG]
|
163
163
|
for tag in tags:
|
@@ -175,7 +175,7 @@ class EndpointsEmitter:
|
|
175
175
|
upper = sum(1 for c in t if c.isupper())
|
176
176
|
return (is_pascal, word_count, upper, t)
|
177
177
|
|
178
|
-
tag_map:
|
178
|
+
tag_map: dict[str, str] = {}
|
179
179
|
for key, candidates in tag_key_to_candidates.items():
|
180
180
|
best_tag_for_key = DEFAULT_TAG # Default if no candidates somehow
|
181
181
|
if candidates:
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
import os
|
3
3
|
from pathlib import Path
|
4
|
-
from typing import Optional
|
5
4
|
|
6
5
|
from pyopenapi_gen import IRSpec
|
7
6
|
from pyopenapi_gen.context.render_context import RenderContext
|
@@ -22,13 +21,13 @@ class ExceptionsEmitter:
|
|
22
21
|
exceptions are available.
|
23
22
|
"""
|
24
23
|
|
25
|
-
def __init__(self, core_package_name: str = "core", overall_project_root:
|
24
|
+
def __init__(self, core_package_name: str = "core", overall_project_root: str | None = None) -> None:
|
26
25
|
self.visitor = ExceptionVisitor()
|
27
26
|
self.core_package_name = core_package_name
|
28
27
|
self.overall_project_root = overall_project_root
|
29
28
|
|
30
29
|
def emit(
|
31
|
-
self, spec: IRSpec, output_dir: str, client_package_name:
|
30
|
+
self, spec: IRSpec, output_dir: str, client_package_name: str | None = None
|
32
31
|
) -> tuple[list[str], list[str]]:
|
33
32
|
"""Generate exception aliases for the given spec.
|
34
33
|
|
@@ -61,6 +60,8 @@ class ExceptionsEmitter:
|
|
61
60
|
|
62
61
|
generated_imports = context.render_imports()
|
63
62
|
|
63
|
+
alias_names.sort()
|
64
|
+
|
64
65
|
# Add __all__ list with proper spacing (2 blank lines after last class - Ruff E305)
|
65
66
|
if alias_names:
|
66
67
|
all_list_str = ", ".join([f'"{name}"' for name in alias_names])
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
from pathlib import Path
|
3
|
-
from typing import
|
3
|
+
from typing import List, Set
|
4
4
|
|
5
5
|
from pyopenapi_gen import IRSchema, IRSpec
|
6
6
|
from pyopenapi_gen.context.render_context import RenderContext
|
@@ -22,15 +22,15 @@ class ModelsEmitter:
|
|
22
22
|
Handles creation of __init__.py and py.typed files.
|
23
23
|
"""
|
24
24
|
|
25
|
-
def __init__(self, context: RenderContext, parsed_schemas:
|
25
|
+
def __init__(self, context: RenderContext, parsed_schemas: dict[str, IRSchema]):
|
26
26
|
self.context: RenderContext = context
|
27
27
|
# Store a reference to the schemas that were passed in.
|
28
28
|
# These schemas will have their .generation_name and .final_module_stem updated.
|
29
|
-
self.parsed_schemas:
|
29
|
+
self.parsed_schemas: dict[str, IRSchema] = parsed_schemas
|
30
30
|
self.import_collector = self.context.import_collector
|
31
31
|
self.writer = CodeWriter()
|
32
32
|
|
33
|
-
def _generate_model_file(self, schema_ir: IRSchema, models_dir: Path) ->
|
33
|
+
def _generate_model_file(self, schema_ir: IRSchema, models_dir: Path) -> str | None:
|
34
34
|
"""Generates a single Python file for a given IRSchema."""
|
35
35
|
if not schema_ir.name: # Original name, used for logging/initial identification
|
36
36
|
logger.warning(f"Skipping model generation for schema without an original name: {schema_ir}")
|
@@ -168,7 +168,7 @@ class ModelsEmitter:
|
|
168
168
|
generated_content = init_writer.get_code()
|
169
169
|
return generated_content
|
170
170
|
|
171
|
-
def emit(self, spec: IRSpec, output_root: str) ->
|
171
|
+
def emit(self, spec: IRSpec, output_root: str) -> dict[str, List[str]]:
|
172
172
|
"""Emits all model files derived from IR schemas.
|
173
173
|
|
174
174
|
Contracts:
|
@@ -352,7 +352,7 @@ class ModelsEmitter:
|
|
352
352
|
|
353
353
|
# Fetch the schema_ir object using the key from all_schemas_for_generation
|
354
354
|
# This ensures we are working with the potentially newly created & named schemas.
|
355
|
-
current_schema_ir_obj:
|
355
|
+
current_schema_ir_obj: IRSchema | None = all_schemas_for_generation.get(schema_key)
|
356
356
|
|
357
357
|
if not current_schema_ir_obj:
|
358
358
|
logger.warning(f"Schema key '{schema_key}' from all_schemas_for_generation not found. Skipping.")
|
@@ -9,7 +9,7 @@ import tempfile
|
|
9
9
|
import time
|
10
10
|
from datetime import datetime
|
11
11
|
from pathlib import Path
|
12
|
-
from typing import Any,
|
12
|
+
from typing import Any, List
|
13
13
|
|
14
14
|
from pyopenapi_gen.context.render_context import RenderContext
|
15
15
|
from pyopenapi_gen.core.loader.loader import load_ir_from_spec
|
@@ -47,9 +47,9 @@ class ClientGenerator:
|
|
47
47
|
"""
|
48
48
|
self.verbose = verbose
|
49
49
|
self.start_time = time.time()
|
50
|
-
self.timings:
|
50
|
+
self.timings: dict[str, float] = {}
|
51
51
|
|
52
|
-
def _log_progress(self, message: str, stage:
|
52
|
+
def _log_progress(self, message: str, stage: str | None = None) -> None:
|
53
53
|
"""
|
54
54
|
Log a progress message with timestamp.
|
55
55
|
|
@@ -89,7 +89,7 @@ class ClientGenerator:
|
|
89
89
|
output_package: str,
|
90
90
|
force: bool = False,
|
91
91
|
no_postprocess: bool = False,
|
92
|
-
core_package:
|
92
|
+
core_package: str | None = None,
|
93
93
|
) -> List[Path]:
|
94
94
|
"""
|
95
95
|
Generate the client code from the OpenAPI spec.
|
@@ -99,10 +99,10 @@ class ClientGenerator:
|
|
99
99
|
project_root (Path): Path to the root of the Python project (absolute or relative).
|
100
100
|
output_package (str): Python package path for the generated client (e.g., 'pyapis.my_api_client').
|
101
101
|
force (bool): Overwrite output without diff check.
|
102
|
-
name (
|
102
|
+
name (str | None): Custom client package name (not used).
|
103
103
|
docs (bool): Kept for interface compatibility.
|
104
104
|
telemetry (bool): Kept for interface compatibility.
|
105
|
-
auth (
|
105
|
+
auth (str | None): Kept for interface compatibility.
|
106
106
|
no_postprocess (bool): Skip post-processing (type checking, etc.).
|
107
107
|
core_package (str): Python package path for the core package.
|
108
108
|
|
@@ -5,7 +5,7 @@ Used by EndpointVisitor and related emitters.
|
|
5
5
|
|
6
6
|
import logging
|
7
7
|
import re
|
8
|
-
from typing import Any,
|
8
|
+
from typing import Any, List
|
9
9
|
|
10
10
|
from pyopenapi_gen import IROperation, IRParameter, IRRequestBody, IRResponse, IRSchema
|
11
11
|
from pyopenapi_gen.context.render_context import RenderContext
|
@@ -17,7 +17,7 @@ from ..types.services.type_service import UnifiedTypeService
|
|
17
17
|
logger = logging.getLogger(__name__)
|
18
18
|
|
19
19
|
|
20
|
-
def get_params(op: IROperation, context: RenderContext, schemas:
|
20
|
+
def get_params(op: IROperation, context: RenderContext, schemas: dict[str, IRSchema]) -> List[dict[str, Any]]:
|
21
21
|
"""
|
22
22
|
Returns a list of dicts with name, type, default, and required for template rendering.
|
23
23
|
Requires the full schema dictionary for type resolution.
|
@@ -37,7 +37,7 @@ def get_params(op: IROperation, context: RenderContext, schemas: Dict[str, IRSch
|
|
37
37
|
return params
|
38
38
|
|
39
39
|
|
40
|
-
def get_param_type(param: IRParameter, context: RenderContext, schemas:
|
40
|
+
def get_param_type(param: IRParameter, context: RenderContext, schemas: dict[str, IRSchema]) -> str:
|
41
41
|
"""Returns the Python type hint for a parameter, resolving references using the schemas dict."""
|
42
42
|
# Use unified service for type resolution
|
43
43
|
type_service = UnifiedTypeService(schemas)
|
@@ -59,7 +59,7 @@ def get_param_type(param: IRParameter, context: RenderContext, schemas: Dict[str
|
|
59
59
|
return py_type
|
60
60
|
|
61
61
|
|
62
|
-
def get_request_body_type(body: IRRequestBody, context: RenderContext, schemas:
|
62
|
+
def get_request_body_type(body: IRRequestBody, context: RenderContext, schemas: dict[str, IRSchema]) -> str:
|
63
63
|
"""Returns the Python type hint for a request body, resolving references using the schemas dict."""
|
64
64
|
# Prefer application/json schema if available
|
65
65
|
json_schema = body.content.get("application/json")
|
@@ -70,11 +70,11 @@ def get_request_body_type(body: IRRequestBody, context: RenderContext, schemas:
|
|
70
70
|
if py_type.startswith(".") and not py_type.startswith(".."):
|
71
71
|
py_type = "models" + py_type
|
72
72
|
|
73
|
-
# If the resolved type is 'Any' for a JSON body, default to
|
73
|
+
# If the resolved type is 'Any' for a JSON body, default to dict[str, Any]
|
74
74
|
if py_type == "Any":
|
75
75
|
context.add_import("typing", "Dict")
|
76
76
|
# context.add_import("typing", "Any") # Already added by the fallback or TypeHelper
|
77
|
-
return "
|
77
|
+
return "dict[str, Any]"
|
78
78
|
return py_type
|
79
79
|
# Fallback for other content types (e.g., octet-stream)
|
80
80
|
# TODO: Handle other types more specifically if needed
|
@@ -89,7 +89,7 @@ def get_request_body_type(body: IRRequestBody, context: RenderContext, schemas:
|
|
89
89
|
def get_return_type(
|
90
90
|
op: IROperation,
|
91
91
|
context: RenderContext,
|
92
|
-
schemas:
|
92
|
+
schemas: dict[str, IRSchema],
|
93
93
|
) -> tuple[str | None, bool]:
|
94
94
|
"""
|
95
95
|
DEPRECATED: Use get_return_type_unified instead.
|
@@ -136,7 +136,7 @@ def get_return_type(
|
|
136
136
|
return (py_type, False)
|
137
137
|
|
138
138
|
|
139
|
-
def _get_primary_response(op: IROperation) ->
|
139
|
+
def _get_primary_response(op: IROperation) -> IRResponse | None:
|
140
140
|
"""Helper to find the best primary success response."""
|
141
141
|
resp = None
|
142
142
|
# Prioritize 200, 201, 202, 204
|
@@ -158,7 +158,7 @@ def _get_primary_response(op: IROperation) -> Optional[IRResponse]:
|
|
158
158
|
return None
|
159
159
|
|
160
160
|
|
161
|
-
def _get_response_schema_and_content_type(resp: IRResponse) -> tuple[
|
161
|
+
def _get_response_schema_and_content_type(resp: IRResponse) -> tuple[IRSchema | None, str | None]:
|
162
162
|
"""Helper to get the schema and content type from a response."""
|
163
163
|
if not resp.content:
|
164
164
|
return None, None
|
@@ -179,7 +179,7 @@ def _get_response_schema_and_content_type(resp: IRResponse) -> tuple[Optional[IR
|
|
179
179
|
return resp.content.get(mt), mt
|
180
180
|
|
181
181
|
|
182
|
-
def _find_resource_schema(update_schema_name: str, schemas:
|
182
|
+
def _find_resource_schema(update_schema_name: str, schemas: dict[str, IRSchema]) -> IRSchema | None:
|
183
183
|
"""
|
184
184
|
Given an update schema name (e.g. 'TenantUpdate'), try to find the corresponding
|
185
185
|
resource schema (e.g. 'Tenant') in the schemas dictionary.
|
@@ -205,7 +205,7 @@ def _find_resource_schema(update_schema_name: str, schemas: Dict[str, IRSchema])
|
|
205
205
|
return None
|
206
206
|
|
207
207
|
|
208
|
-
def _infer_type_from_path(path: str, schemas:
|
208
|
+
def _infer_type_from_path(path: str, schemas: dict[str, IRSchema]) -> IRSchema | None:
|
209
209
|
"""
|
210
210
|
Infers a response type from a path. This is used when a response schema is not specified.
|
211
211
|
|
@@ -350,8 +350,8 @@ def merge_params_with_model_fields(
|
|
350
350
|
op: IROperation,
|
351
351
|
model_schema: IRSchema,
|
352
352
|
context: RenderContext,
|
353
|
-
schemas:
|
354
|
-
) -> List[
|
353
|
+
schemas: dict[str, IRSchema],
|
354
|
+
) -> List[dict[str, Any]]:
|
355
355
|
"""
|
356
356
|
Merge endpoint parameters with required model fields for function signatures.
|
357
357
|
- Ensures all required model fields are present as parameters (without duplication).
|
@@ -484,7 +484,7 @@ def get_type_for_specific_response(
|
|
484
484
|
ctx.add_import("typing", "AsyncIterator")
|
485
485
|
ctx.add_import("typing", "Dict")
|
486
486
|
ctx.add_import("typing", "Any")
|
487
|
-
return "AsyncIterator[
|
487
|
+
return "AsyncIterator[dict[str, Any]]"
|
488
488
|
|
489
489
|
return final_py_type
|
490
490
|
|
@@ -504,9 +504,7 @@ def _is_binary_stream_content(resp_ir: IRResponse) -> bool:
|
|
504
504
|
)
|
505
505
|
|
506
506
|
|
507
|
-
def _get_item_type_from_schema(
|
508
|
-
resp_ir: IRResponse, all_schemas: dict[str, IRSchema], ctx: RenderContext
|
509
|
-
) -> Optional[str]:
|
507
|
+
def _get_item_type_from_schema(resp_ir: IRResponse, all_schemas: dict[str, IRSchema], ctx: RenderContext) -> str | None:
|
510
508
|
"""Extract item type from schema for streaming responses."""
|
511
509
|
schema, _ = _get_response_schema_and_content_type(resp_ir)
|
512
510
|
if not schema:
|
@@ -528,7 +526,7 @@ def get_python_type_for_response_body(resp_ir: IRResponse, all_schemas: dict[str
|
|
528
526
|
return type_service.resolve_schema_type(schema, ctx, required=True)
|
529
527
|
|
530
528
|
|
531
|
-
def get_schema_from_response(resp_ir: IRResponse, all_schemas: dict[str, IRSchema]) ->
|
529
|
+
def get_schema_from_response(resp_ir: IRResponse, all_schemas: dict[str, IRSchema]) -> IRSchema | None:
|
532
530
|
"""Get the schema from a response object."""
|
533
531
|
schema, _ = _get_response_schema_and_content_type(resp_ir)
|
534
532
|
return schema
|