pyopenapi-gen 2.7.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyopenapi_gen/__init__.py +224 -0
- pyopenapi_gen/__main__.py +6 -0
- pyopenapi_gen/cli.py +62 -0
- pyopenapi_gen/context/CLAUDE.md +284 -0
- pyopenapi_gen/context/file_manager.py +52 -0
- pyopenapi_gen/context/import_collector.py +382 -0
- pyopenapi_gen/context/render_context.py +726 -0
- pyopenapi_gen/core/CLAUDE.md +224 -0
- pyopenapi_gen/core/__init__.py +0 -0
- pyopenapi_gen/core/auth/base.py +22 -0
- pyopenapi_gen/core/auth/plugins.py +89 -0
- pyopenapi_gen/core/cattrs_converter.py +810 -0
- pyopenapi_gen/core/exceptions.py +20 -0
- pyopenapi_gen/core/http_status_codes.py +218 -0
- pyopenapi_gen/core/http_transport.py +222 -0
- pyopenapi_gen/core/loader/__init__.py +12 -0
- pyopenapi_gen/core/loader/loader.py +174 -0
- pyopenapi_gen/core/loader/operations/__init__.py +12 -0
- pyopenapi_gen/core/loader/operations/parser.py +161 -0
- pyopenapi_gen/core/loader/operations/post_processor.py +62 -0
- pyopenapi_gen/core/loader/operations/request_body.py +90 -0
- pyopenapi_gen/core/loader/parameters/__init__.py +10 -0
- pyopenapi_gen/core/loader/parameters/parser.py +186 -0
- pyopenapi_gen/core/loader/responses/__init__.py +10 -0
- pyopenapi_gen/core/loader/responses/parser.py +111 -0
- pyopenapi_gen/core/loader/schemas/__init__.py +11 -0
- pyopenapi_gen/core/loader/schemas/extractor.py +275 -0
- pyopenapi_gen/core/pagination.py +64 -0
- pyopenapi_gen/core/parsing/__init__.py +13 -0
- pyopenapi_gen/core/parsing/common/__init__.py +1 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/__init__.py +9 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/__init__.py +0 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/cyclic_properties.py +66 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/direct_cycle.py +33 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/existing_schema.py +22 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/list_response.py +54 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/missing_ref.py +52 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/new_schema.py +50 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/stripped_suffix.py +51 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/resolve_schema_ref.py +86 -0
- pyopenapi_gen/core/parsing/common/type_parser.py +73 -0
- pyopenapi_gen/core/parsing/context.py +187 -0
- pyopenapi_gen/core/parsing/cycle_helpers.py +126 -0
- pyopenapi_gen/core/parsing/keywords/__init__.py +1 -0
- pyopenapi_gen/core/parsing/keywords/all_of_parser.py +81 -0
- pyopenapi_gen/core/parsing/keywords/any_of_parser.py +84 -0
- pyopenapi_gen/core/parsing/keywords/array_items_parser.py +72 -0
- pyopenapi_gen/core/parsing/keywords/one_of_parser.py +77 -0
- pyopenapi_gen/core/parsing/keywords/properties_parser.py +98 -0
- pyopenapi_gen/core/parsing/schema_finalizer.py +169 -0
- pyopenapi_gen/core/parsing/schema_parser.py +804 -0
- pyopenapi_gen/core/parsing/transformers/__init__.py +0 -0
- pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +285 -0
- pyopenapi_gen/core/parsing/transformers/inline_object_promoter.py +120 -0
- pyopenapi_gen/core/parsing/unified_cycle_detection.py +293 -0
- pyopenapi_gen/core/postprocess_manager.py +260 -0
- pyopenapi_gen/core/spec_fetcher.py +148 -0
- pyopenapi_gen/core/streaming_helpers.py +84 -0
- pyopenapi_gen/core/telemetry.py +69 -0
- pyopenapi_gen/core/utils.py +456 -0
- pyopenapi_gen/core/warning_collector.py +83 -0
- pyopenapi_gen/core/writers/code_writer.py +135 -0
- pyopenapi_gen/core/writers/documentation_writer.py +222 -0
- pyopenapi_gen/core/writers/line_writer.py +217 -0
- pyopenapi_gen/core/writers/python_construct_renderer.py +321 -0
- pyopenapi_gen/core_package_template/README.md +21 -0
- pyopenapi_gen/emit/models_emitter.py +143 -0
- pyopenapi_gen/emitters/CLAUDE.md +286 -0
- pyopenapi_gen/emitters/client_emitter.py +51 -0
- pyopenapi_gen/emitters/core_emitter.py +181 -0
- pyopenapi_gen/emitters/docs_emitter.py +44 -0
- pyopenapi_gen/emitters/endpoints_emitter.py +247 -0
- pyopenapi_gen/emitters/exceptions_emitter.py +187 -0
- pyopenapi_gen/emitters/mocks_emitter.py +185 -0
- pyopenapi_gen/emitters/models_emitter.py +426 -0
- pyopenapi_gen/generator/CLAUDE.md +352 -0
- pyopenapi_gen/generator/client_generator.py +567 -0
- pyopenapi_gen/generator/exceptions.py +7 -0
- pyopenapi_gen/helpers/CLAUDE.md +325 -0
- pyopenapi_gen/helpers/__init__.py +1 -0
- pyopenapi_gen/helpers/endpoint_utils.py +532 -0
- pyopenapi_gen/helpers/type_cleaner.py +334 -0
- pyopenapi_gen/helpers/type_helper.py +112 -0
- pyopenapi_gen/helpers/type_resolution/__init__.py +1 -0
- pyopenapi_gen/helpers/type_resolution/array_resolver.py +57 -0
- pyopenapi_gen/helpers/type_resolution/composition_resolver.py +79 -0
- pyopenapi_gen/helpers/type_resolution/finalizer.py +105 -0
- pyopenapi_gen/helpers/type_resolution/named_resolver.py +172 -0
- pyopenapi_gen/helpers/type_resolution/object_resolver.py +216 -0
- pyopenapi_gen/helpers/type_resolution/primitive_resolver.py +109 -0
- pyopenapi_gen/helpers/type_resolution/resolver.py +47 -0
- pyopenapi_gen/helpers/url_utils.py +14 -0
- pyopenapi_gen/http_types.py +20 -0
- pyopenapi_gen/ir.py +165 -0
- pyopenapi_gen/py.typed +1 -0
- pyopenapi_gen/types/CLAUDE.md +140 -0
- pyopenapi_gen/types/__init__.py +11 -0
- pyopenapi_gen/types/contracts/__init__.py +13 -0
- pyopenapi_gen/types/contracts/protocols.py +106 -0
- pyopenapi_gen/types/contracts/types.py +28 -0
- pyopenapi_gen/types/resolvers/__init__.py +7 -0
- pyopenapi_gen/types/resolvers/reference_resolver.py +71 -0
- pyopenapi_gen/types/resolvers/response_resolver.py +177 -0
- pyopenapi_gen/types/resolvers/schema_resolver.py +498 -0
- pyopenapi_gen/types/services/__init__.py +5 -0
- pyopenapi_gen/types/services/type_service.py +165 -0
- pyopenapi_gen/types/strategies/__init__.py +5 -0
- pyopenapi_gen/types/strategies/response_strategy.py +310 -0
- pyopenapi_gen/visit/CLAUDE.md +272 -0
- pyopenapi_gen/visit/client_visitor.py +477 -0
- pyopenapi_gen/visit/docs_visitor.py +38 -0
- pyopenapi_gen/visit/endpoint/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/endpoint_visitor.py +292 -0
- pyopenapi_gen/visit/endpoint/generators/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +123 -0
- pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py +222 -0
- pyopenapi_gen/visit/endpoint/generators/mock_generator.py +140 -0
- pyopenapi_gen/visit/endpoint/generators/overload_generator.py +252 -0
- pyopenapi_gen/visit/endpoint/generators/request_generator.py +103 -0
- pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +705 -0
- pyopenapi_gen/visit/endpoint/generators/signature_generator.py +83 -0
- pyopenapi_gen/visit/endpoint/generators/url_args_generator.py +207 -0
- pyopenapi_gen/visit/endpoint/processors/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +78 -0
- pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +171 -0
- pyopenapi_gen/visit/exception_visitor.py +90 -0
- pyopenapi_gen/visit/model/__init__.py +0 -0
- pyopenapi_gen/visit/model/alias_generator.py +93 -0
- pyopenapi_gen/visit/model/dataclass_generator.py +553 -0
- pyopenapi_gen/visit/model/enum_generator.py +212 -0
- pyopenapi_gen/visit/model/model_visitor.py +198 -0
- pyopenapi_gen/visit/visitor.py +97 -0
- pyopenapi_gen-2.7.2.dist-info/METADATA +1169 -0
- pyopenapi_gen-2.7.2.dist-info/RECORD +137 -0
- pyopenapi_gen-2.7.2.dist-info/WHEEL +4 -0
- pyopenapi_gen-2.7.2.dist-info/entry_points.txt +2 -0
- pyopenapi_gen-2.7.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CodeWriter: Utility for building indented, well-formatted Python code blocks.
|
|
3
|
+
|
|
4
|
+
This module provides the CodeWriter class, which is responsible for managing code indentation,
|
|
5
|
+
writing lines and blocks, and supporting wrapped output for code and docstrings. It is designed
|
|
6
|
+
to be used by code generation visitors and emitters to ensure consistent, readable output.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
from .line_writer import LineWriter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CodeWriter:
|
|
15
|
+
"""
|
|
16
|
+
Utility for writing indented code blocks with support for line wrapping and function signatures.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
writer (LineWriter): The LineWriter instance used for writing lines and blocks.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, indent_str: str = " ", max_width: int = 120) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Initialize a new CodeWriter.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
indent_str (str): The string to use for one indentation level (default: 4 spaces).
|
|
28
|
+
max_width (int): The maximum line width for wrapping (default: 120).
|
|
29
|
+
"""
|
|
30
|
+
self.writer = LineWriter(indent_str=indent_str, max_width=max_width)
|
|
31
|
+
|
|
32
|
+
def write_line(self, line: str = "") -> None:
|
|
33
|
+
"""
|
|
34
|
+
Write a single line, respecting the current indentation level.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
line (str): The line to write. Defaults to an empty line.
|
|
38
|
+
"""
|
|
39
|
+
self.writer.append(line)
|
|
40
|
+
self.writer.newline()
|
|
41
|
+
|
|
42
|
+
def indent(self) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Increase the indentation level by one.
|
|
45
|
+
"""
|
|
46
|
+
self.writer.indent()
|
|
47
|
+
|
|
48
|
+
def dedent(self) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Decrease the indentation level by one (never below zero).
|
|
51
|
+
"""
|
|
52
|
+
self.writer.dedent()
|
|
53
|
+
|
|
54
|
+
def write_block(self, code: str) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Write a multi-line code block using the current indentation level.
|
|
57
|
+
Each non-empty line is prefixed with the current indentation.
|
|
58
|
+
Preserves empty lines.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
code (str): The code block to write (may be multiple lines).
|
|
62
|
+
"""
|
|
63
|
+
for line in code.splitlines():
|
|
64
|
+
self.write_line(line)
|
|
65
|
+
|
|
66
|
+
def get_code(self) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Get the full code as a single string.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: The accumulated code, joined by newlines.
|
|
72
|
+
"""
|
|
73
|
+
return self.writer.getvalue().rstrip("\n")
|
|
74
|
+
|
|
75
|
+
def write_wrapped_line(self, text: str, width: int = 120) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Write a line (or lines) wrapped to the given width, respecting current indentation.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
text (str): The text to write and wrap.
|
|
81
|
+
width (int): The maximum line width (default: 120).
|
|
82
|
+
"""
|
|
83
|
+
# Temporarily set max_width for this operation
|
|
84
|
+
old_width = self.writer.max_width
|
|
85
|
+
self.writer.max_width = width
|
|
86
|
+
self.writer.append_wrapped(text)
|
|
87
|
+
self.writer.newline()
|
|
88
|
+
self.writer.max_width = old_width
|
|
89
|
+
|
|
90
|
+
def write_function_signature(
|
|
91
|
+
self, name: str, args: List[str], return_type: str | None = None, async_: bool = False
|
|
92
|
+
) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Write a function or method signature, with each argument on its own line and correct indentation.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
name (str): The function or method name.
|
|
98
|
+
args (list): The list of argument strings.
|
|
99
|
+
return_type (str): The return type annotation, if any.
|
|
100
|
+
async_ (bool): Whether to emit 'async def' (default: False).
|
|
101
|
+
"""
|
|
102
|
+
def_prefix = "async def" if async_ else "def"
|
|
103
|
+
if args:
|
|
104
|
+
self.write_line(f"{def_prefix} {name}(")
|
|
105
|
+
self.indent()
|
|
106
|
+
for arg in args:
|
|
107
|
+
self.write_line(f"{arg},")
|
|
108
|
+
self.dedent()
|
|
109
|
+
if return_type:
|
|
110
|
+
self.write_line(f") -> {return_type}:")
|
|
111
|
+
else:
|
|
112
|
+
self.write_line("):")
|
|
113
|
+
else:
|
|
114
|
+
if return_type:
|
|
115
|
+
self.write_line(f"{def_prefix} {name}(self) -> {return_type}:")
|
|
116
|
+
else:
|
|
117
|
+
self.write_line(f"{def_prefix} {name}(self):")
|
|
118
|
+
|
|
119
|
+
def write_wrapped_docstring_line(self, prefix: str, text: str, width: int = 88) -> None:
|
|
120
|
+
"""
|
|
121
|
+
Write a docstring line (or lines) wrapped to the given width, with wrapped lines
|
|
122
|
+
indented to align after the prefix (for Args, Returns, etc).
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
prefix (str): The prefix for the first line (e.g., 'param (type): ').
|
|
126
|
+
text (str): The docstring text to wrap.
|
|
127
|
+
width (int): The maximum line width (default: 88).
|
|
128
|
+
"""
|
|
129
|
+
# Temporarily set max_width for this operation
|
|
130
|
+
old_width = self.writer.max_width
|
|
131
|
+
self.writer.max_width = width
|
|
132
|
+
self.writer.append(prefix)
|
|
133
|
+
self.writer.append_wrapped(text)
|
|
134
|
+
self.writer.newline()
|
|
135
|
+
self.writer.max_width = old_width
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DocumentationWriter: Utility for generating well-formatted, Google-style Python docstrings.
|
|
3
|
+
|
|
4
|
+
This module provides the DocumentationWriter and DocumentationBlock classes, which are responsible
|
|
5
|
+
for building comprehensive, type-rich docstrings for generated Python code. It supports argument
|
|
6
|
+
alignment, line wrapping, and section formatting for Args, Returns, and Raises.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import List, Tuple, Union
|
|
10
|
+
|
|
11
|
+
from .line_writer import LineWriter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DocumentationBlock:
|
|
15
|
+
"""
|
|
16
|
+
Data container for docstring content.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
summary (str | None): The summary line for the docstring.
|
|
20
|
+
description (str | None): The detailed description.
|
|
21
|
+
args (Optional[List[Union[Tuple[str, str, str], Tuple[str, str]]]]):
|
|
22
|
+
List of arguments as (name, type, desc) or (type, desc) 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
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
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
|
+
) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Initialize a DocumentationBlock.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
summary (str | None): The summary line.
|
|
40
|
+
description (str | None): The detailed description.
|
|
41
|
+
args (Optional[List[Union[Tuple[str, str, str], Tuple[str, str]]]]): Arguments.
|
|
42
|
+
returns (Tuple[str, str] | None): Return type and description.
|
|
43
|
+
raises (List[Tuple[str, str]] | None): Exceptions.
|
|
44
|
+
"""
|
|
45
|
+
self.summary: str | None = summary
|
|
46
|
+
self.description: str | None = description
|
|
47
|
+
self.args: List[Union[Tuple[str, str, str], Tuple[str, str]]] = args or []
|
|
48
|
+
self.returns: Tuple[str, str] | None = returns
|
|
49
|
+
self.raises: List[Tuple[str, str]] = raises or []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class DocumentationFormatter:
|
|
53
|
+
"""
|
|
54
|
+
Handles low-level formatting, wrapping, and alignment for docstring lines using LineWriter.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, width: int = 88, min_desc_col: int = 30) -> None:
|
|
58
|
+
self.width: int = width
|
|
59
|
+
self.min_desc_col: int = min_desc_col
|
|
60
|
+
|
|
61
|
+
def wrap(self, text: str, indent: int, prefix: str | None = None) -> List[str]:
|
|
62
|
+
if not text:
|
|
63
|
+
return []
|
|
64
|
+
writer = LineWriter(max_width=self.width)
|
|
65
|
+
for _ in range(indent // len(writer.indent_str)):
|
|
66
|
+
writer.indent()
|
|
67
|
+
if prefix is not None:
|
|
68
|
+
writer.append(prefix)
|
|
69
|
+
writer.append_wrapped(text)
|
|
70
|
+
return writer.getvalue().splitlines()
|
|
71
|
+
|
|
72
|
+
def get_arg_prefix(self, arg: Union[Tuple[str, str, str], Tuple[str, str]]) -> str:
|
|
73
|
+
if len(arg) == 3:
|
|
74
|
+
name, typ, _ = arg
|
|
75
|
+
return f"{name} ({typ})"
|
|
76
|
+
return f"{arg[0]}"
|
|
77
|
+
|
|
78
|
+
def render_short_prefix_arg(self, prefix: str, desc: str, indent: int, desc_col: int) -> List[str]:
|
|
79
|
+
writer = LineWriter(max_width=self.width)
|
|
80
|
+
for _ in range(indent // len(writer.indent_str)):
|
|
81
|
+
writer.indent()
|
|
82
|
+
writer.append(prefix)
|
|
83
|
+
writer.move_to_column(desc_col)
|
|
84
|
+
writer.append(": ")
|
|
85
|
+
writer.append_wrapped(desc)
|
|
86
|
+
return writer.getvalue().splitlines()
|
|
87
|
+
|
|
88
|
+
def render_long_prefix_arg(
|
|
89
|
+
self,
|
|
90
|
+
prefix: str,
|
|
91
|
+
desc: str,
|
|
92
|
+
indent: int,
|
|
93
|
+
min_col: int,
|
|
94
|
+
) -> List[str]:
|
|
95
|
+
writer = LineWriter(max_width=self.width)
|
|
96
|
+
for _ in range(indent // len(writer.indent_str)):
|
|
97
|
+
writer.indent()
|
|
98
|
+
writer.append(prefix)
|
|
99
|
+
writer.newline()
|
|
100
|
+
writer.move_to_column(min_col)
|
|
101
|
+
writer.append(": ")
|
|
102
|
+
# writer.move_to_column(min_col + 2)
|
|
103
|
+
writer.append_wrapped(desc)
|
|
104
|
+
return writer.getvalue().splitlines()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class DocstringSectionRenderer:
|
|
108
|
+
"""
|
|
109
|
+
Renders Args, Returns, and Raises sections for Google-style docstrings using LineWriter.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(self, formatter: DocumentationFormatter) -> None:
|
|
113
|
+
self.formatter = formatter
|
|
114
|
+
|
|
115
|
+
def _render_short_prefix_arg(self, prefix: str, desc: str, indent: int, min_col: int) -> list[str]:
|
|
116
|
+
return self.formatter.render_short_prefix_arg(prefix, desc, indent, min_col)
|
|
117
|
+
|
|
118
|
+
def _render_exact_prefix_arg(self, prefix: str, desc: str, indent: int, min_col: int) -> list[str]:
|
|
119
|
+
return self.formatter.render_short_prefix_arg(prefix, desc, indent, min_col)
|
|
120
|
+
|
|
121
|
+
def _render_long_prefix_arg(self, prefix: str, desc: str, indent: int, min_col: int) -> list[str]:
|
|
122
|
+
return self.formatter.render_long_prefix_arg(prefix, desc, indent, min_col)
|
|
123
|
+
|
|
124
|
+
def render_args(self, args: List[Union[Tuple[str, str, str], Tuple[str, str]]], indent: int) -> List[str]:
|
|
125
|
+
lines: List[str] = []
|
|
126
|
+
min_col = self.formatter.min_desc_col
|
|
127
|
+
for arg in args:
|
|
128
|
+
prefix = self.formatter.get_arg_prefix(arg)
|
|
129
|
+
desc = arg[2] if len(arg) == 3 else arg[1]
|
|
130
|
+
prefix_len = indent + len(prefix)
|
|
131
|
+
if prefix_len < min_col:
|
|
132
|
+
lines.extend(self._render_short_prefix_arg(prefix, desc, indent, min_col))
|
|
133
|
+
elif prefix_len == min_col:
|
|
134
|
+
lines.extend(self._render_exact_prefix_arg(prefix, desc, indent, min_col))
|
|
135
|
+
else:
|
|
136
|
+
lines.extend(self._render_long_prefix_arg(prefix, desc, indent, min_col))
|
|
137
|
+
return lines
|
|
138
|
+
|
|
139
|
+
def render_returns(self, returns: Tuple[str, str], indent: int) -> List[str]:
|
|
140
|
+
typ, desc = returns
|
|
141
|
+
prefix = f"{typ}:"
|
|
142
|
+
writer = LineWriter(max_width=self.formatter.width)
|
|
143
|
+
for _ in range(indent // len(writer.indent_str)):
|
|
144
|
+
writer.indent()
|
|
145
|
+
writer.append(prefix)
|
|
146
|
+
writer.append(" ")
|
|
147
|
+
writer.append_wrapped(desc)
|
|
148
|
+
return writer.getvalue().splitlines()
|
|
149
|
+
|
|
150
|
+
def render_raises(self, raises: List[Tuple[str, str]], indent: int) -> List[str]:
|
|
151
|
+
writer = LineWriter(max_width=self.formatter.width)
|
|
152
|
+
for _ in range(indent // len(writer.indent_str)):
|
|
153
|
+
writer.indent()
|
|
154
|
+
if not raises:
|
|
155
|
+
return writer.lines
|
|
156
|
+
writer.append("HttpError:")
|
|
157
|
+
for code, desc in raises:
|
|
158
|
+
writer.newline()
|
|
159
|
+
writer.append(f" {code}:")
|
|
160
|
+
if desc.strip():
|
|
161
|
+
writer.append(" ")
|
|
162
|
+
writer.append_wrapped(desc)
|
|
163
|
+
return writer.getvalue().splitlines()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class DocumentationWriter:
|
|
167
|
+
"""
|
|
168
|
+
Renders a DocumentationBlock into a Google-style Python docstring.
|
|
169
|
+
Delegates all formatting to DocumentationFormatter and section rendering to DocstringSectionRenderer.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def __init__(self, width: int = 88, min_desc_col: int = 30) -> None:
|
|
173
|
+
"""
|
|
174
|
+
Initialize a DocumentationWriter.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
width (int): The maximum line width for wrapping (default: 88).
|
|
178
|
+
min_desc_col (int): The minimum column for aligning descriptions (default: 30).
|
|
179
|
+
"""
|
|
180
|
+
self.formatter = DocumentationFormatter(width=width, min_desc_col=min_desc_col)
|
|
181
|
+
self.section_renderer = DocstringSectionRenderer(self.formatter)
|
|
182
|
+
self.width = width
|
|
183
|
+
self.min_desc_col = min_desc_col
|
|
184
|
+
|
|
185
|
+
def render_docstring(self, doc: DocumentationBlock, indent: int = 0) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Render a Google-style docstring from a DocumentationBlock.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
doc (DocumentationBlock): The docstring structure to render.
|
|
191
|
+
indent (int): The indentation level (in spaces) for the docstring block.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
str: The formatted docstring as a string.
|
|
195
|
+
"""
|
|
196
|
+
lines: List[str] = []
|
|
197
|
+
lines.append(f'"""')
|
|
198
|
+
# Summary
|
|
199
|
+
if doc.summary:
|
|
200
|
+
lines.extend(self.formatter.wrap(doc.summary, indent))
|
|
201
|
+
# Description
|
|
202
|
+
if doc.description:
|
|
203
|
+
if doc.summary:
|
|
204
|
+
lines.append("")
|
|
205
|
+
lines.extend(self.formatter.wrap(doc.description, indent))
|
|
206
|
+
# Args
|
|
207
|
+
if doc.args:
|
|
208
|
+
lines.append("")
|
|
209
|
+
lines.append("Args:")
|
|
210
|
+
lines.extend(self.section_renderer.render_args(doc.args, indent + 4))
|
|
211
|
+
# Returns
|
|
212
|
+
if doc.returns:
|
|
213
|
+
lines.append("")
|
|
214
|
+
lines.append("Returns:")
|
|
215
|
+
lines.extend(self.section_renderer.render_returns(doc.returns, indent + 4))
|
|
216
|
+
# Raises
|
|
217
|
+
if doc.raises:
|
|
218
|
+
lines.append("")
|
|
219
|
+
lines.append("Raises:")
|
|
220
|
+
lines.extend(self.section_renderer.render_raises(doc.raises, indent + 4))
|
|
221
|
+
lines.append('"""')
|
|
222
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LineWriter: Utility for building and formatting lines of text with precise control over indentation and width.
|
|
3
|
+
|
|
4
|
+
This class is designed for use in both code and documentation generation, providing methods to append text, start
|
|
5
|
+
new lines, and query the current line's width.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LineWriter:
|
|
12
|
+
"""
|
|
13
|
+
Utility for building lines of text with indentation and width tracking.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
lines (List[str]): The accumulated lines of text.
|
|
17
|
+
indent_level (int): The current indentation level.
|
|
18
|
+
indent_str (str): The string used for one indentation level (default: 4 spaces).
|
|
19
|
+
max_width (int): The maximum line width for wrapping.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, indent_str: str = " ", max_width: int = 80) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Initialize a new LineWriter with a single empty line, indentation state, and max line width.
|
|
25
|
+
Args:
|
|
26
|
+
indent_str (str): The string used for one indentation level (default: 4 spaces).
|
|
27
|
+
max_width (int): The maximum line width for wrapping (default: 80).
|
|
28
|
+
"""
|
|
29
|
+
self.lines: List[str] = [""]
|
|
30
|
+
self.indent_level: int = 0
|
|
31
|
+
self.indent_str: str = indent_str
|
|
32
|
+
self._just_newlined: bool = True
|
|
33
|
+
self.max_width: int = max_width
|
|
34
|
+
|
|
35
|
+
def indent(self) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Increase the indentation level by one.
|
|
38
|
+
"""
|
|
39
|
+
self.indent_level += 1
|
|
40
|
+
|
|
41
|
+
def dedent(self) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Decrease the indentation level by one (never below zero).
|
|
44
|
+
"""
|
|
45
|
+
self.indent_level = max(0, self.indent_level - 1)
|
|
46
|
+
|
|
47
|
+
def append(self, text: str) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Append text to the current line, respecting current indentation if the line is empty and was just newlined.
|
|
50
|
+
Args:
|
|
51
|
+
text (str): The text to append.
|
|
52
|
+
"""
|
|
53
|
+
if self.lines[-1] == "" and self._just_newlined:
|
|
54
|
+
self.lines[-1] = self.indent_str * self.indent_level + text
|
|
55
|
+
else:
|
|
56
|
+
self.lines[-1] += text
|
|
57
|
+
self._just_newlined = False
|
|
58
|
+
|
|
59
|
+
def newline(self) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Start a new line, with current indentation.
|
|
62
|
+
"""
|
|
63
|
+
self.lines.append("")
|
|
64
|
+
self._just_newlined = True
|
|
65
|
+
|
|
66
|
+
def current_width(self) -> int:
|
|
67
|
+
"""
|
|
68
|
+
Get the width (number of characters) of the current line.
|
|
69
|
+
Returns:
|
|
70
|
+
int: The width of the current line.
|
|
71
|
+
"""
|
|
72
|
+
return len(self.lines[-1])
|
|
73
|
+
|
|
74
|
+
def current_line(self) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Get the current line.
|
|
77
|
+
"""
|
|
78
|
+
return self.lines[-1]
|
|
79
|
+
|
|
80
|
+
def getvalue(self) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Get the full text as a single string, joined by newlines.
|
|
83
|
+
Returns:
|
|
84
|
+
str: The full text.
|
|
85
|
+
"""
|
|
86
|
+
return "\n".join(self.lines)
|
|
87
|
+
|
|
88
|
+
def wrap_and_append(self, text: str, width: int, prefix: str = "") -> None:
|
|
89
|
+
"""
|
|
90
|
+
Wrap the given text to the specified width and append it, using the given prefix for the first line.
|
|
91
|
+
Args:
|
|
92
|
+
text (str): The text to wrap and append.
|
|
93
|
+
width (int): The maximum line width.
|
|
94
|
+
prefix (str): The prefix for the first line (default: empty).
|
|
95
|
+
"""
|
|
96
|
+
import textwrap
|
|
97
|
+
|
|
98
|
+
wrapped = textwrap.wrap(text, width=width, initial_indent=prefix, subsequent_indent=" " * len(prefix))
|
|
99
|
+
for i, line in enumerate(wrapped):
|
|
100
|
+
if i > 0:
|
|
101
|
+
self.newline()
|
|
102
|
+
self.append(line)
|
|
103
|
+
|
|
104
|
+
def _get_current_column(self) -> int:
|
|
105
|
+
"""
|
|
106
|
+
Get the current column of the cursor.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
line_width = self.current_width()
|
|
110
|
+
if line_width == 0:
|
|
111
|
+
return self.indent_level * len(self.indent_str)
|
|
112
|
+
return line_width
|
|
113
|
+
|
|
114
|
+
def append_wrapped(self, text: str) -> None:
|
|
115
|
+
"""
|
|
116
|
+
Append text to the current line, wrapping as needed.
|
|
117
|
+
|
|
118
|
+
The first line wraps at (self.max_width - current cursor position), so wrapping always respects the available
|
|
119
|
+
space on the current line, including indentation and any already-appended text. Subsequent lines wrap
|
|
120
|
+
at (self.max_width - current column), and are aligned to the current column at the time of the call.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
text (str): The text to append and wrap.
|
|
124
|
+
"""
|
|
125
|
+
import textwrap
|
|
126
|
+
|
|
127
|
+
if not text:
|
|
128
|
+
return
|
|
129
|
+
wrap_col = self._get_current_column()
|
|
130
|
+
first_line_width = self.max_width - wrap_col
|
|
131
|
+
if first_line_width <= 0:
|
|
132
|
+
self.newline()
|
|
133
|
+
wrap_col = self._get_current_column()
|
|
134
|
+
first_line_width = self.max_width - wrap_col
|
|
135
|
+
|
|
136
|
+
prefix = self.current_line()
|
|
137
|
+
while len(prefix) < wrap_col:
|
|
138
|
+
prefix += " "
|
|
139
|
+
wrap_col = max(wrap_col, len(prefix))
|
|
140
|
+
|
|
141
|
+
wrapper = textwrap.TextWrapper(
|
|
142
|
+
width=self.max_width,
|
|
143
|
+
initial_indent="",
|
|
144
|
+
subsequent_indent=" " * wrap_col,
|
|
145
|
+
break_long_words=True,
|
|
146
|
+
break_on_hyphens=True,
|
|
147
|
+
)
|
|
148
|
+
wrapped_lines = wrapper.wrap(prefix + text)
|
|
149
|
+
if wrapped_lines:
|
|
150
|
+
self.replace_current_line(wrapped_lines[0])
|
|
151
|
+
for line in wrapped_lines[1:]:
|
|
152
|
+
self.newline()
|
|
153
|
+
self.replace_current_line(line)
|
|
154
|
+
|
|
155
|
+
def replace_current_line(self, line: str) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Replace the current line with the given line.
|
|
158
|
+
"""
|
|
159
|
+
self.lines[-1] = line
|
|
160
|
+
|
|
161
|
+
def move_to_column(self, col: int) -> None:
|
|
162
|
+
"""
|
|
163
|
+
Pad the current line with spaces until the cursor is at column col.
|
|
164
|
+
Args:
|
|
165
|
+
col (int): The target column to move the cursor to.
|
|
166
|
+
"""
|
|
167
|
+
current = len(self.lines[-1])
|
|
168
|
+
if current < col:
|
|
169
|
+
self.lines[-1] += " " * (col - current - 1)
|
|
170
|
+
# If already at or past col, do nothing
|
|
171
|
+
|
|
172
|
+
def append_wrapped_at_column(self, text: str, width: int, col: int | None = None) -> None:
|
|
173
|
+
"""
|
|
174
|
+
Append text, wrapping as needed, so that the first line continues from the current position,
|
|
175
|
+
and all subsequent lines start at column `col`.
|
|
176
|
+
|
|
177
|
+
If `col` is None, the current line width at the time of the call is used as the wrap column.
|
|
178
|
+
This allows ergonomic alignment after any prefix or content.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
text (str) : The text to append and wrap.
|
|
182
|
+
width (int) : The maximum line width.
|
|
183
|
+
col (int | None) : The column at which to start wrapped lines. If None, uses current
|
|
184
|
+
line width.
|
|
185
|
+
"""
|
|
186
|
+
import textwrap
|
|
187
|
+
|
|
188
|
+
if not text:
|
|
189
|
+
return # Do nothing for empty text
|
|
190
|
+
# Determine wrap column
|
|
191
|
+
wrap_col = self.current_width() if col is None else col
|
|
192
|
+
# First line: fill up to current position, then append as much as fits
|
|
193
|
+
current = len(self.lines[-1])
|
|
194
|
+
available = max(0, width - current)
|
|
195
|
+
if available <= 0:
|
|
196
|
+
# No space left, start a new line at wrap_col
|
|
197
|
+
self.newline()
|
|
198
|
+
self.move_to_column(wrap_col)
|
|
199
|
+
current = len(self.lines[-1])
|
|
200
|
+
available = max(0, width - current)
|
|
201
|
+
words = text.split()
|
|
202
|
+
first_line = ""
|
|
203
|
+
|
|
204
|
+
while words and len(first_line) + len(words[0]) + (1 if first_line else 0) <= available:
|
|
205
|
+
if first_line:
|
|
206
|
+
first_line += " "
|
|
207
|
+
first_line += words.pop(0)
|
|
208
|
+
if first_line:
|
|
209
|
+
self.append(first_line)
|
|
210
|
+
# Remaining lines: wrap at width, start at wrap_col
|
|
211
|
+
if words:
|
|
212
|
+
rest = " ".join(words)
|
|
213
|
+
wrapped = textwrap.wrap(rest, width=width - wrap_col)
|
|
214
|
+
for line in wrapped:
|
|
215
|
+
self.newline()
|
|
216
|
+
self.move_to_column(wrap_col)
|
|
217
|
+
self.append(line)
|