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.
Files changed (80) hide show
  1. pyopenapi_gen/cli.py +3 -3
  2. pyopenapi_gen/context/import_collector.py +10 -10
  3. pyopenapi_gen/context/render_context.py +13 -13
  4. pyopenapi_gen/core/auth/plugins.py +7 -7
  5. pyopenapi_gen/core/http_status_codes.py +2 -4
  6. pyopenapi_gen/core/http_transport.py +19 -19
  7. pyopenapi_gen/core/loader/operations/parser.py +2 -2
  8. pyopenapi_gen/core/loader/operations/request_body.py +3 -3
  9. pyopenapi_gen/core/loader/parameters/parser.py +3 -3
  10. pyopenapi_gen/core/loader/responses/parser.py +2 -2
  11. pyopenapi_gen/core/loader/schemas/extractor.py +4 -4
  12. pyopenapi_gen/core/pagination.py +3 -3
  13. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/list_response.py +3 -3
  14. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/missing_ref.py +2 -2
  15. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/new_schema.py +3 -3
  16. pyopenapi_gen/core/parsing/common/ref_resolution/helpers/stripped_suffix.py +3 -3
  17. pyopenapi_gen/core/parsing/common/ref_resolution/resolve_schema_ref.py +2 -2
  18. pyopenapi_gen/core/parsing/common/type_parser.py +2 -3
  19. pyopenapi_gen/core/parsing/context.py +10 -10
  20. pyopenapi_gen/core/parsing/cycle_helpers.py +5 -2
  21. pyopenapi_gen/core/parsing/keywords/all_of_parser.py +5 -5
  22. pyopenapi_gen/core/parsing/keywords/any_of_parser.py +4 -4
  23. pyopenapi_gen/core/parsing/keywords/array_items_parser.py +4 -4
  24. pyopenapi_gen/core/parsing/keywords/one_of_parser.py +4 -4
  25. pyopenapi_gen/core/parsing/keywords/properties_parser.py +5 -5
  26. pyopenapi_gen/core/parsing/schema_finalizer.py +15 -15
  27. pyopenapi_gen/core/parsing/schema_parser.py +44 -25
  28. pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +4 -4
  29. pyopenapi_gen/core/parsing/transformers/inline_object_promoter.py +7 -4
  30. pyopenapi_gen/core/parsing/unified_cycle_detection.py +10 -10
  31. pyopenapi_gen/core/schemas.py +10 -10
  32. pyopenapi_gen/core/streaming_helpers.py +5 -7
  33. pyopenapi_gen/core/telemetry.py +4 -4
  34. pyopenapi_gen/core/utils.py +7 -7
  35. pyopenapi_gen/core/writers/code_writer.py +2 -2
  36. pyopenapi_gen/core/writers/documentation_writer.py +18 -18
  37. pyopenapi_gen/core/writers/line_writer.py +3 -3
  38. pyopenapi_gen/core/writers/python_construct_renderer.py +10 -10
  39. pyopenapi_gen/emit/models_emitter.py +2 -2
  40. pyopenapi_gen/emitters/core_emitter.py +3 -5
  41. pyopenapi_gen/emitters/endpoints_emitter.py +12 -12
  42. pyopenapi_gen/emitters/exceptions_emitter.py +4 -3
  43. pyopenapi_gen/emitters/models_emitter.py +6 -6
  44. pyopenapi_gen/generator/client_generator.py +6 -6
  45. pyopenapi_gen/helpers/endpoint_utils.py +16 -18
  46. pyopenapi_gen/helpers/type_cleaner.py +66 -53
  47. pyopenapi_gen/helpers/type_helper.py +7 -7
  48. pyopenapi_gen/helpers/type_resolution/array_resolver.py +4 -4
  49. pyopenapi_gen/helpers/type_resolution/composition_resolver.py +5 -5
  50. pyopenapi_gen/helpers/type_resolution/finalizer.py +38 -22
  51. pyopenapi_gen/helpers/type_resolution/named_resolver.py +4 -5
  52. pyopenapi_gen/helpers/type_resolution/object_resolver.py +11 -11
  53. pyopenapi_gen/helpers/type_resolution/primitive_resolver.py +1 -2
  54. pyopenapi_gen/helpers/type_resolution/resolver.py +2 -3
  55. pyopenapi_gen/ir.py +32 -34
  56. pyopenapi_gen/types/contracts/protocols.py +5 -5
  57. pyopenapi_gen/types/contracts/types.py +2 -3
  58. pyopenapi_gen/types/resolvers/reference_resolver.py +4 -4
  59. pyopenapi_gen/types/resolvers/response_resolver.py +6 -4
  60. pyopenapi_gen/types/resolvers/schema_resolver.py +32 -16
  61. pyopenapi_gen/types/services/type_service.py +55 -9
  62. pyopenapi_gen/types/strategies/response_strategy.py +6 -7
  63. pyopenapi_gen/visit/client_visitor.py +5 -7
  64. pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +7 -7
  65. pyopenapi_gen/visit/endpoint/generators/request_generator.py +5 -5
  66. pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +38 -17
  67. pyopenapi_gen/visit/endpoint/generators/signature_generator.py +4 -4
  68. pyopenapi_gen/visit/endpoint/generators/url_args_generator.py +17 -17
  69. pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +8 -8
  70. pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +13 -13
  71. pyopenapi_gen/visit/model/alias_generator.py +1 -4
  72. pyopenapi_gen/visit/model/dataclass_generator.py +139 -10
  73. pyopenapi_gen/visit/model/model_visitor.py +2 -3
  74. pyopenapi_gen/visit/visitor.py +3 -3
  75. {pyopenapi_gen-0.14.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/METADATA +1 -1
  76. pyopenapi_gen-0.14.1.dist-info/RECORD +132 -0
  77. pyopenapi_gen-0.14.0.dist-info/RECORD +0 -132
  78. {pyopenapi_gen-0.14.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/WHEEL +0 -0
  79. {pyopenapi_gen-0.14.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/entry_points.txt +0 -0
  80. {pyopenapi_gen-0.14.0.dist-info → pyopenapi_gen-0.14.1.dist-info}/licenses/LICENSE +0 -0
@@ -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, Dict, Optional
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: Optional[bool] = None) -> None:
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: Optional[Dict[str, Any]] = None) -> None:
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: Dict[str, Any] = {
55
+ data: dict[str, Any] = {
56
56
  "event": event,
57
57
  "properties": properties or {},
58
58
  "timestamp": time.time(),
@@ -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, Dict, Set, Type, TypeVar, cast
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: Dict[str, Any]) -> str:
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: Dict[str, Any] = {}
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) -> Dict[str, Any]:
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, Optional
266
+ from typing import Any, Callable
267
267
 
268
- self._file_mode: Optional[Any] = None
269
- self._format_str: Optional[Callable[..., str]] = None
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, Optional
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: Optional[str] = None, async_: bool = False
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, Optional, Tuple, Union
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 (Optional[str]): The summary line for the docstring.
20
- description (Optional[str]): The detailed 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 (Optional[Tuple[str, str]]): The return type and description.
24
- raises (Optional[List[Tuple[str, str]]]): List of (exception type, description) tuples.
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: Optional[str] = None,
30
- description: Optional[str] = None,
31
- args: Optional[List[Union[Tuple[str, str, str], Tuple[str, str]]]] = None,
32
- returns: Optional[Tuple[str, str]] = None,
33
- raises: Optional[List[Tuple[str, str]]] = None,
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 (Optional[str]): The summary line.
40
- description (Optional[str]): The detailed 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 (Optional[Tuple[str, str]]): Return type and description.
43
- raises (Optional[List[Tuple[str, str]]]): Exceptions.
42
+ returns (Tuple[str, str] | None): Return type and description.
43
+ raises (List[Tuple[str, str]] | None): Exceptions.
44
44
  """
45
- self.summary: Optional[str] = summary
46
- self.description: Optional[str] = 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: Optional[Tuple[str, str]] = 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: Optional[str] = None) -> List[str]:
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, Optional
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: Optional[int] = None) -> None:
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 (Optional[int]) : The column at which to start wrapped lines. If None, uses current
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 Dict, List, Optional, Tuple
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: Optional[str],
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: Optional[str],
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, Optional[str], Optional[str]]], # name, type_hint, default_expr, description
147
- description: Optional[str],
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: Optional[Dict[str, str]] = None,
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: Optional[str] = None
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: Optional[List[str]],
278
- docstring: Optional[str],
279
- body_lines: Optional[List[str]],
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, Optional
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) -> Optional[str]:
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, Optional
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: Optional[float] = 30.0
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: Optional[List[str]] = None
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 Dict, List, Optional, Tuple
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": "Dict[str, Any]",
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[Optional[int]])
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. Optional[Any]
74
- py_type = f"Optional[{py_type}]"
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 "Dict[str, Any]"
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: Optional[EndpointVisitor] = None
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: Dict[str, int] = {}
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 Dict[str, IRSchema]
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: Dict[str, List[IROperation]] = {}
160
- tag_key_to_candidates: Dict[str, List[str]] = {}
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: Dict[str, str] = {}
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: Optional[str] = None) -> None:
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: Optional[str] = None
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 Dict, List, Optional, Set
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: Dict[str, IRSchema]):
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: Dict[str, IRSchema] = 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) -> Optional[str]:
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) -> Dict[str, List[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: Optional[IRSchema] = all_schemas_for_generation.get(schema_key)
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, Dict, List, Optional
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: Dict[str, float] = {}
50
+ self.timings: dict[str, float] = {}
51
51
 
52
- def _log_progress(self, message: str, stage: Optional[str] = None) -> None:
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: Optional[str] = None,
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 (Optional[str]): Custom client package name (not used).
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 (Optional[str]): Kept for interface compatibility.
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, Dict, List, Optional
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: Dict[str, IRSchema]) -> List[Dict[str, Any]]:
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: Dict[str, IRSchema]) -> str:
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: Dict[str, IRSchema]) -> str:
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 Dict[str, Any]
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 "Dict[str, Any]"
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: Dict[str, IRSchema],
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) -> Optional[IRResponse]:
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[Optional[IRSchema], Optional[str]]:
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: Dict[str, IRSchema]) -> Optional[IRSchema]:
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: Dict[str, IRSchema]) -> Optional[IRSchema]:
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: Dict[str, IRSchema],
354
- ) -> List[Dict[str, Any]]:
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[Dict[str, Any]]"
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]) -> Optional[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