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
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
10
10
|
import logging
|
11
11
|
from dataclasses import dataclass, field
|
12
12
|
from enum import Enum
|
13
|
-
from typing import
|
13
|
+
from typing import List, Set
|
14
14
|
|
15
15
|
from pyopenapi_gen import IRSchema
|
16
16
|
from pyopenapi_gen.core.utils import NameSanitizer
|
@@ -62,10 +62,10 @@ class CycleDetectionResult:
|
|
62
62
|
"""Result of cycle detection check."""
|
63
63
|
|
64
64
|
is_cycle: bool
|
65
|
-
cycle_type:
|
65
|
+
cycle_type: CycleType | None
|
66
66
|
action: CycleAction
|
67
|
-
cycle_info:
|
68
|
-
placeholder_schema:
|
67
|
+
cycle_info: CycleInfo | None = None
|
68
|
+
placeholder_schema: IRSchema | None = None
|
69
69
|
|
70
70
|
|
71
71
|
@dataclass
|
@@ -74,8 +74,8 @@ class UnifiedCycleContext:
|
|
74
74
|
|
75
75
|
# Core tracking
|
76
76
|
schema_stack: List[str] = field(default_factory=list)
|
77
|
-
schema_states:
|
78
|
-
parsed_schemas:
|
77
|
+
schema_states: dict[str, SchemaState] = field(default_factory=dict)
|
78
|
+
parsed_schemas: dict[str, IRSchema] = field(default_factory=dict)
|
79
79
|
recursion_depth: int = 0
|
80
80
|
|
81
81
|
# Detection results
|
@@ -155,7 +155,7 @@ def create_depth_placeholder(schema_name: str, depth: int) -> IRSchema:
|
|
155
155
|
)
|
156
156
|
|
157
157
|
|
158
|
-
def unified_cycle_check(schema_name:
|
158
|
+
def unified_cycle_check(schema_name: str | None, context: UnifiedCycleContext) -> CycleDetectionResult:
|
159
159
|
"""Unified cycle detection that handles all cases."""
|
160
160
|
|
161
161
|
if schema_name is None:
|
@@ -262,7 +262,7 @@ def unified_cycle_check(schema_name: Optional[str], context: UnifiedCycleContext
|
|
262
262
|
return CycleDetectionResult(False, None, CycleAction.CONTINUE_PARSING)
|
263
263
|
|
264
264
|
|
265
|
-
def unified_enter_schema(schema_name:
|
265
|
+
def unified_enter_schema(schema_name: str | None, context: UnifiedCycleContext) -> CycleDetectionResult:
|
266
266
|
"""Unified entry point that always maintains consistent state."""
|
267
267
|
context.recursion_depth += 1
|
268
268
|
|
@@ -275,7 +275,7 @@ def unified_enter_schema(schema_name: Optional[str], context: UnifiedCycleContex
|
|
275
275
|
return result
|
276
276
|
|
277
277
|
|
278
|
-
def unified_exit_schema(schema_name:
|
278
|
+
def unified_exit_schema(schema_name: str | None, context: UnifiedCycleContext) -> None:
|
279
279
|
"""Unified exit that always maintains consistent state."""
|
280
280
|
if context.recursion_depth > 0:
|
281
281
|
context.recursion_depth -= 1
|
@@ -288,6 +288,6 @@ def unified_exit_schema(schema_name: Optional[str], context: UnifiedCycleContext
|
|
288
288
|
context.schema_states[schema_name] = SchemaState.COMPLETED
|
289
289
|
|
290
290
|
|
291
|
-
def get_schema_or_placeholder(schema_name: str, context: UnifiedCycleContext) ->
|
291
|
+
def get_schema_or_placeholder(schema_name: str, context: UnifiedCycleContext) -> IRSchema | None:
|
292
292
|
"""Get an existing schema or placeholder from the context."""
|
293
293
|
return context.parsed_schemas.get(schema_name)
|
@@ -32,13 +32,15 @@ class PostprocessManager:
|
|
32
32
|
# Ensure all targets are Path objects
|
33
33
|
target_paths = [Path(t) for t in targets]
|
34
34
|
|
35
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
# OPTIMISED: Run Ruff once on all files instead of per-file
|
36
|
+
# Collect all Python files
|
37
|
+
python_files = [p for p in target_paths if p.is_file() and p.suffix == ".py"]
|
38
|
+
|
39
|
+
if python_files:
|
40
|
+
# Run Ruff checks once on all files (much faster than per-file)
|
41
|
+
self.remove_unused_imports_bulk(python_files)
|
42
|
+
self.sort_imports_bulk(python_files)
|
43
|
+
self.format_code_bulk(python_files)
|
42
44
|
|
43
45
|
# Determine the package root directory(s) for Mypy
|
44
46
|
package_roots = set()
|
@@ -58,11 +60,82 @@ class PostprocessManager:
|
|
58
60
|
package_roots.add(target_path)
|
59
61
|
|
60
62
|
# Run Mypy on each identified package root
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
# TEMPORARILY DISABLED: Mypy is slow on large specs, disabled for faster iteration
|
64
|
+
# if package_roots:
|
65
|
+
# print(f"Running Mypy on package root(s): {package_roots}")
|
66
|
+
# for root_dir in package_roots:
|
67
|
+
# print(f"Running mypy on {root_dir}...")
|
68
|
+
# self.type_check(root_dir)
|
69
|
+
|
70
|
+
def remove_unused_imports_bulk(self, targets: List[Path]) -> None:
|
71
|
+
"""Remove unused imports from multiple targets using Ruff (bulk operation)."""
|
72
|
+
if not targets:
|
73
|
+
return
|
74
|
+
result = subprocess.run(
|
75
|
+
[
|
76
|
+
sys.executable,
|
77
|
+
"-m",
|
78
|
+
"ruff",
|
79
|
+
"check",
|
80
|
+
"--select=F401",
|
81
|
+
"--fix",
|
82
|
+
]
|
83
|
+
+ [str(t) for t in targets],
|
84
|
+
stdout=subprocess.PIPE,
|
85
|
+
stderr=subprocess.PIPE,
|
86
|
+
text=True,
|
87
|
+
)
|
88
|
+
if result.returncode != 0 or result.stderr:
|
89
|
+
if result.stdout:
|
90
|
+
_print_filtered_stdout(result.stdout)
|
91
|
+
if result.stderr:
|
92
|
+
print(result.stderr, file=sys.stderr)
|
93
|
+
|
94
|
+
def sort_imports_bulk(self, targets: List[Path]) -> None:
|
95
|
+
"""Sort imports in multiple targets using Ruff (bulk operation)."""
|
96
|
+
if not targets:
|
97
|
+
return
|
98
|
+
result = subprocess.run(
|
99
|
+
[
|
100
|
+
sys.executable,
|
101
|
+
"-m",
|
102
|
+
"ruff",
|
103
|
+
"check",
|
104
|
+
"--select=I",
|
105
|
+
"--fix",
|
106
|
+
]
|
107
|
+
+ [str(t) for t in targets],
|
108
|
+
stdout=subprocess.PIPE,
|
109
|
+
stderr=subprocess.PIPE,
|
110
|
+
text=True,
|
111
|
+
)
|
112
|
+
if result.returncode != 0 or result.stderr:
|
113
|
+
if result.stdout:
|
114
|
+
_print_filtered_stdout(result.stdout)
|
115
|
+
if result.stderr:
|
116
|
+
print(result.stderr, file=sys.stderr)
|
117
|
+
|
118
|
+
def format_code_bulk(self, targets: List[Path]) -> None:
|
119
|
+
"""Format code in multiple targets using Ruff (bulk operation)."""
|
120
|
+
if not targets:
|
121
|
+
return
|
122
|
+
result = subprocess.run(
|
123
|
+
[
|
124
|
+
sys.executable,
|
125
|
+
"-m",
|
126
|
+
"ruff",
|
127
|
+
"format",
|
128
|
+
]
|
129
|
+
+ [str(t) for t in targets],
|
130
|
+
stdout=subprocess.PIPE,
|
131
|
+
stderr=subprocess.PIPE,
|
132
|
+
text=True,
|
133
|
+
)
|
134
|
+
if result.returncode != 0 or result.stderr:
|
135
|
+
if result.stdout:
|
136
|
+
_print_filtered_stdout(result.stdout)
|
137
|
+
if result.stderr:
|
138
|
+
print(result.stderr, file=sys.stderr)
|
66
139
|
|
67
140
|
def remove_unused_imports(self, target: Union[str, Path]) -> None:
|
68
141
|
"""Remove unused imports from the target using Ruff."""
|
pyopenapi_gen/core/schemas.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from dataclasses import MISSING, dataclass, fields
|
4
|
-
from typing import Any,
|
4
|
+
from typing import Any, Type, TypeVar, Union, get_args, get_origin, get_type_hints
|
5
5
|
|
6
6
|
T = TypeVar("T", bound="BaseSchema")
|
7
7
|
|
@@ -10,7 +10,7 @@ def _extract_base_type(field_type: Any) -> Any:
|
|
10
10
|
"""Extract the base type from Optional/Union types."""
|
11
11
|
origin = get_origin(field_type)
|
12
12
|
if origin is Union:
|
13
|
-
# For
|
13
|
+
# For T | None or Union[T, None], get the non-None type
|
14
14
|
args = get_args(field_type)
|
15
15
|
non_none_args = [arg for arg in args if arg is not type(None)]
|
16
16
|
if len(non_none_args) == 1:
|
@@ -23,26 +23,26 @@ class BaseSchema:
|
|
23
23
|
"""Base class for all generated models, providing validation, dict conversion, and field mapping."""
|
24
24
|
|
25
25
|
@classmethod
|
26
|
-
def _get_field_mappings(cls) ->
|
26
|
+
def _get_field_mappings(cls) -> dict[str, str]:
|
27
27
|
"""Get field mappings from Meta class if defined. Returns API field -> Python field mappings."""
|
28
28
|
if hasattr(cls, "Meta") and hasattr(cls.Meta, "key_transform_with_load"):
|
29
29
|
return cls.Meta.key_transform_with_load # type: ignore[no-any-return]
|
30
30
|
return {}
|
31
31
|
|
32
32
|
@classmethod
|
33
|
-
def _get_reverse_field_mappings(cls) ->
|
33
|
+
def _get_reverse_field_mappings(cls) -> dict[str, str]:
|
34
34
|
"""Get reverse field mappings. Returns Python field -> API field mappings."""
|
35
35
|
mappings = cls._get_field_mappings()
|
36
36
|
return {python_field: api_field for api_field, python_field in mappings.items()}
|
37
37
|
|
38
38
|
@classmethod
|
39
|
-
def from_dict(cls: Type[T], data:
|
39
|
+
def from_dict(cls: Type[T], data: dict[str, Any]) -> T:
|
40
40
|
"""Create an instance from a dictionary with automatic field name mapping."""
|
41
41
|
if not isinstance(data, dict):
|
42
42
|
raise TypeError(f"Input must be a dictionary, got {type(data).__name__}")
|
43
43
|
|
44
44
|
field_mappings = cls._get_field_mappings() # API -> Python
|
45
|
-
kwargs:
|
45
|
+
kwargs: dict[str, Any] = {}
|
46
46
|
cls_fields = {f.name: f for f in fields(cls)}
|
47
47
|
|
48
48
|
# Process each field in the data
|
@@ -64,7 +64,7 @@ class BaseSchema:
|
|
64
64
|
# Fall back to raw annotation if get_type_hints fails
|
65
65
|
pass
|
66
66
|
|
67
|
-
# Extract base type (handles
|
67
|
+
# Extract base type (handles Type | None -> Type)
|
68
68
|
base_type = _extract_base_type(field_type)
|
69
69
|
|
70
70
|
if base_type is not None and hasattr(base_type, "from_dict") and isinstance(value, dict):
|
@@ -90,7 +90,7 @@ class BaseSchema:
|
|
90
90
|
|
91
91
|
return cls(**kwargs)
|
92
92
|
|
93
|
-
def to_dict(self, exclude_none: bool = False) ->
|
93
|
+
def to_dict(self, exclude_none: bool = False) -> dict[str, Any]:
|
94
94
|
"""Convert the model instance to a dictionary with reverse field name mapping."""
|
95
95
|
reverse_mappings = self._get_reverse_field_mappings() # Python -> API
|
96
96
|
result = {}
|
@@ -116,10 +116,10 @@ class BaseSchema:
|
|
116
116
|
|
117
117
|
# Legacy aliases for backward compatibility
|
118
118
|
@classmethod
|
119
|
-
def model_validate(cls: Type[T], data:
|
119
|
+
def model_validate(cls: Type[T], data: dict[str, Any]) -> T:
|
120
120
|
"""Legacy alias for from_dict."""
|
121
121
|
return cls.from_dict(data)
|
122
122
|
|
123
|
-
def model_dump(self, exclude_none: bool = False) ->
|
123
|
+
def model_dump(self, exclude_none: bool = False) -> dict[str, Any]:
|
124
124
|
"""Legacy alias for to_dict."""
|
125
125
|
return self.to_dict(exclude_none=exclude_none)
|
@@ -1,17 +1,15 @@
|
|
1
1
|
import json
|
2
|
-
from typing import Any, AsyncIterator, List
|
2
|
+
from typing import Any, AsyncIterator, List
|
3
3
|
|
4
4
|
import httpx
|
5
5
|
|
6
6
|
|
7
7
|
class SSEEvent:
|
8
|
-
def __init__(
|
9
|
-
self, data: str, event: Optional[str] = None, id: Optional[str] = None, retry: Optional[int] = None
|
10
|
-
) -> None:
|
8
|
+
def __init__(self, data: str, event: str | None = None, id: str | None = None, retry: int | None = None) -> None:
|
11
9
|
self.data: str = data
|
12
|
-
self.event:
|
13
|
-
self.id:
|
14
|
-
self.retry:
|
10
|
+
self.event: str | None = event
|
11
|
+
self.id: str | None = id
|
12
|
+
self.retry: int | None = retry
|
15
13
|
|
16
14
|
def __repr__(self) -> str:
|
17
15
|
return f"SSEEvent(data={self.data!r}, event={self.event!r}, id={self.id!r}, retry={self.retry!r})"
|
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
|
"""
|
@@ -312,7 +312,11 @@ class PythonConstructRenderer:
|
|
312
312
|
has_content = True
|
313
313
|
if body_lines:
|
314
314
|
for line in body_lines:
|
315
|
-
|
315
|
+
# Handle empty lines without adding indentation (Ruff W293)
|
316
|
+
if line == "":
|
317
|
+
writer.writer.newline() # Just add a newline, no indent
|
318
|
+
else:
|
319
|
+
writer.write_line(line)
|
316
320
|
has_content = True
|
317
321
|
|
318
322
|
if not has_content:
|
@@ -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"
|