onetool-mcp 1.0.0b1__py3-none-any.whl → 1.0.0rc2__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.
- onetool/cli.py +63 -4
- onetool_mcp-1.0.0rc2.dist-info/METADATA +266 -0
- onetool_mcp-1.0.0rc2.dist-info/RECORD +129 -0
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/LICENSE.txt +1 -1
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/NOTICE.txt +54 -64
- ot/__main__.py +6 -6
- ot/config/__init__.py +48 -46
- ot/config/global_templates/__init__.py +2 -2
- ot/config/{defaults → global_templates}/diagram-templates/api-flow.mmd +33 -33
- ot/config/{defaults → global_templates}/diagram-templates/c4-context.puml +30 -30
- ot/config/{defaults → global_templates}/diagram-templates/class-diagram.mmd +87 -87
- ot/config/{defaults → global_templates}/diagram-templates/feature-mindmap.mmd +70 -70
- ot/config/{defaults → global_templates}/diagram-templates/microservices.d2 +81 -81
- ot/config/{defaults → global_templates}/diagram-templates/project-gantt.mmd +37 -37
- ot/config/{defaults → global_templates}/diagram-templates/state-machine.mmd +42 -42
- ot/config/global_templates/diagram.yaml +167 -0
- ot/config/global_templates/onetool.yaml +3 -1
- ot/config/{defaults → global_templates}/prompts.yaml +102 -97
- ot/config/global_templates/security.yaml +31 -0
- ot/config/global_templates/servers.yaml +93 -12
- ot/config/global_templates/snippets.yaml +5 -26
- ot/config/{defaults → global_templates}/tool_templates/__init__.py +7 -7
- ot/config/loader.py +221 -105
- ot/config/mcp.py +5 -1
- ot/config/secrets.py +192 -190
- ot/decorators.py +116 -116
- ot/executor/__init__.py +35 -35
- ot/executor/base.py +16 -16
- ot/executor/fence_processor.py +83 -83
- ot/executor/linter.py +142 -142
- ot/executor/pep723.py +288 -288
- ot/executor/runner.py +20 -6
- ot/executor/simple.py +163 -163
- ot/executor/validator.py +603 -164
- ot/http_client.py +145 -145
- ot/logging/__init__.py +37 -37
- ot/logging/entry.py +213 -213
- ot/logging/format.py +191 -188
- ot/logging/span.py +349 -349
- ot/meta.py +236 -14
- ot/paths.py +32 -49
- ot/prompts.py +218 -218
- ot/proxy/manager.py +14 -2
- ot/registry/__init__.py +189 -189
- ot/registry/parser.py +269 -269
- ot/server.py +330 -315
- ot/shortcuts/__init__.py +15 -15
- ot/shortcuts/aliases.py +87 -87
- ot/shortcuts/snippets.py +258 -258
- ot/stats/__init__.py +35 -35
- ot/stats/html.py +2 -2
- ot/stats/reader.py +354 -354
- ot/stats/timing.py +57 -57
- ot/support.py +63 -63
- ot/tools.py +1 -1
- ot/utils/batch.py +161 -161
- ot/utils/cache.py +120 -120
- ot/utils/exceptions.py +23 -23
- ot/utils/factory.py +178 -179
- ot/utils/format.py +65 -65
- ot/utils/http.py +202 -202
- ot/utils/platform.py +45 -45
- ot/utils/truncate.py +69 -69
- ot_tools/__init__.py +4 -4
- ot_tools/_convert/__init__.py +12 -12
- ot_tools/_convert/pdf.py +254 -254
- ot_tools/diagram.yaml +167 -167
- ot_tools/scaffold.py +2 -2
- ot_tools/transform.py +124 -19
- ot_tools/web_fetch.py +94 -43
- onetool_mcp-1.0.0b1.dist-info/METADATA +0 -163
- onetool_mcp-1.0.0b1.dist-info/RECORD +0 -132
- ot/config/defaults/bench.yaml +0 -4
- ot/config/defaults/onetool.yaml +0 -25
- ot/config/defaults/servers.yaml +0 -7
- ot/config/defaults/snippets.yaml +0 -4
- ot_tools/firecrawl.py +0 -732
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/WHEEL +0 -0
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/entry_points.txt +0 -0
- /ot/config/{defaults → global_templates}/tool_templates/extension.py +0 -0
- /ot/config/{defaults → global_templates}/tool_templates/isolated.py +0 -0
ot/registry/parser.py
CHANGED
|
@@ -1,269 +1,269 @@
|
|
|
1
|
-
"""AST parsing utilities for extracting function information."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import ast
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from docstring_parser import parse as parse_docstring_lib
|
|
9
|
-
|
|
10
|
-
from .models import ArgInfo, ToolInfo
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def parse_function(
|
|
14
|
-
node: ast.FunctionDef, module: str, pack: str | None = None
|
|
15
|
-
) -> ToolInfo:
|
|
16
|
-
"""Extract information from a function AST node.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
node: AST FunctionDef node.
|
|
20
|
-
module: Module path for the function.
|
|
21
|
-
pack: Optional pack name for namespace-qualified tool names.
|
|
22
|
-
|
|
23
|
-
Returns:
|
|
24
|
-
ToolInfo with extracted signature and docstring info.
|
|
25
|
-
"""
|
|
26
|
-
# Extract signature (with pack-qualified name if pack is provided)
|
|
27
|
-
signature = extract_signature(node, pack=pack)
|
|
28
|
-
|
|
29
|
-
# Parse docstring
|
|
30
|
-
docstring = ast.get_docstring(node) or ""
|
|
31
|
-
doc_info = parse_docstring(docstring)
|
|
32
|
-
|
|
33
|
-
# Extract args
|
|
34
|
-
args = extract_args(node, doc_info.get("args", {}))
|
|
35
|
-
|
|
36
|
-
# Extract @tool decorator metadata if present
|
|
37
|
-
decorator_info = extract_tool_decorator(node)
|
|
38
|
-
|
|
39
|
-
# Decorator description overrides docstring if provided
|
|
40
|
-
description = decorator_info.get("description") or doc_info.get("description", "")
|
|
41
|
-
|
|
42
|
-
# Use pack-qualified name if pack is provided
|
|
43
|
-
qualified_name = f"{pack}.{node.name}" if pack else node.name
|
|
44
|
-
|
|
45
|
-
return ToolInfo(
|
|
46
|
-
name=qualified_name,
|
|
47
|
-
pack=pack,
|
|
48
|
-
module=module,
|
|
49
|
-
signature=signature,
|
|
50
|
-
description=description,
|
|
51
|
-
args=args,
|
|
52
|
-
returns=doc_info.get("returns", ""),
|
|
53
|
-
examples=decorator_info.get("examples", []),
|
|
54
|
-
tags=decorator_info.get("tags", []),
|
|
55
|
-
enabled=decorator_info.get("enabled", True),
|
|
56
|
-
deprecated=decorator_info.get("deprecated", False),
|
|
57
|
-
deprecated_message=decorator_info.get("deprecated_message"),
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def extract_tool_decorator(node: ast.FunctionDef) -> dict[str, Any]:
|
|
62
|
-
"""Extract metadata from @tool decorator if present.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
node: AST FunctionDef node.
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
Dict with decorator metadata (description, examples, tags, enabled, deprecated).
|
|
69
|
-
"""
|
|
70
|
-
result: dict[str, Any] = {}
|
|
71
|
-
|
|
72
|
-
for decorator in node.decorator_list:
|
|
73
|
-
# Look for @tool(...) decorator
|
|
74
|
-
if isinstance(decorator, ast.Call):
|
|
75
|
-
func = decorator.func
|
|
76
|
-
if isinstance(func, ast.Name) and func.id == "tool":
|
|
77
|
-
# Extract keyword arguments
|
|
78
|
-
for keyword in decorator.keywords:
|
|
79
|
-
if keyword.arg == "description":
|
|
80
|
-
if isinstance(keyword.value, ast.Constant):
|
|
81
|
-
result["description"] = keyword.value.value
|
|
82
|
-
elif keyword.arg == "examples":
|
|
83
|
-
if isinstance(keyword.value, ast.List):
|
|
84
|
-
result["examples"] = [
|
|
85
|
-
elt.value
|
|
86
|
-
for elt in keyword.value.elts
|
|
87
|
-
if isinstance(elt, ast.Constant)
|
|
88
|
-
]
|
|
89
|
-
elif keyword.arg == "tags":
|
|
90
|
-
if isinstance(keyword.value, ast.List):
|
|
91
|
-
result["tags"] = [
|
|
92
|
-
elt.value
|
|
93
|
-
for elt in keyword.value.elts
|
|
94
|
-
if isinstance(elt, ast.Constant)
|
|
95
|
-
]
|
|
96
|
-
elif keyword.arg == "enabled":
|
|
97
|
-
if isinstance(keyword.value, ast.Constant):
|
|
98
|
-
result["enabled"] = keyword.value.value
|
|
99
|
-
elif keyword.arg == "deprecated" and isinstance(
|
|
100
|
-
keyword.value, ast.Constant
|
|
101
|
-
):
|
|
102
|
-
result["deprecated"] = keyword.value.value
|
|
103
|
-
elif keyword.arg == "deprecated_message" and isinstance(
|
|
104
|
-
keyword.value, ast.Constant
|
|
105
|
-
):
|
|
106
|
-
result["deprecated_message"] = keyword.value.value
|
|
107
|
-
break
|
|
108
|
-
|
|
109
|
-
return result
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def extract_signature(node: ast.FunctionDef, pack: str | None = None) -> str:
|
|
113
|
-
"""Extract function signature string.
|
|
114
|
-
|
|
115
|
-
Args:
|
|
116
|
-
node: AST FunctionDef node.
|
|
117
|
-
pack: Optional pack name for namespace-qualified tool names.
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
Signature string like 'pack.func_name(arg: type = default) -> return_type'
|
|
121
|
-
"""
|
|
122
|
-
parts: list[str] = []
|
|
123
|
-
|
|
124
|
-
# Process regular args
|
|
125
|
-
args = node.args
|
|
126
|
-
defaults = args.defaults
|
|
127
|
-
num_defaults = len(defaults)
|
|
128
|
-
num_args = len(args.args)
|
|
129
|
-
|
|
130
|
-
for i, arg in enumerate(args.args):
|
|
131
|
-
arg_str = arg.arg
|
|
132
|
-
# Add type annotation
|
|
133
|
-
if arg.annotation:
|
|
134
|
-
arg_str += f": {annotation_to_str(arg.annotation)}"
|
|
135
|
-
|
|
136
|
-
# Add default value if present
|
|
137
|
-
default_idx = i - (num_args - num_defaults)
|
|
138
|
-
if default_idx >= 0:
|
|
139
|
-
default = defaults[default_idx]
|
|
140
|
-
arg_str += f" = {value_to_str(default)}"
|
|
141
|
-
|
|
142
|
-
parts.append(arg_str)
|
|
143
|
-
|
|
144
|
-
# Process keyword-only args
|
|
145
|
-
kw_defaults = args.kw_defaults
|
|
146
|
-
for i, arg in enumerate(args.kwonlyargs):
|
|
147
|
-
arg_str = arg.arg
|
|
148
|
-
if arg.annotation:
|
|
149
|
-
arg_str += f": {annotation_to_str(arg.annotation)}"
|
|
150
|
-
if kw_defaults[i] is not None:
|
|
151
|
-
arg_str += f" = {value_to_str(kw_defaults[i])}"
|
|
152
|
-
parts.append(arg_str)
|
|
153
|
-
|
|
154
|
-
# Build signature with pack-qualified name if pack is provided
|
|
155
|
-
func_name = f"{pack}.{node.name}" if pack else node.name
|
|
156
|
-
sig = f"{func_name}({', '.join(parts)})"
|
|
157
|
-
|
|
158
|
-
# Add return type
|
|
159
|
-
if node.returns:
|
|
160
|
-
sig += f" -> {annotation_to_str(node.returns)}"
|
|
161
|
-
|
|
162
|
-
return sig
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def annotation_to_str(node: ast.expr) -> str:
|
|
166
|
-
"""Convert AST annotation node to string.
|
|
167
|
-
|
|
168
|
-
Args:
|
|
169
|
-
node: AST expression node representing a type annotation.
|
|
170
|
-
|
|
171
|
-
Returns:
|
|
172
|
-
String representation of the type.
|
|
173
|
-
"""
|
|
174
|
-
return ast.unparse(node)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def value_to_str(node: ast.expr | None) -> str:
|
|
178
|
-
"""Convert AST value node to string representation.
|
|
179
|
-
|
|
180
|
-
Args:
|
|
181
|
-
node: AST expression node representing a default value.
|
|
182
|
-
|
|
183
|
-
Returns:
|
|
184
|
-
String representation of the value.
|
|
185
|
-
"""
|
|
186
|
-
if node is None:
|
|
187
|
-
return "None"
|
|
188
|
-
return ast.unparse(node)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def extract_args(
|
|
192
|
-
node: ast.FunctionDef, docstring_args: dict[str, str]
|
|
193
|
-
) -> list[ArgInfo]:
|
|
194
|
-
"""Extract argument information from function node.
|
|
195
|
-
|
|
196
|
-
Args:
|
|
197
|
-
node: AST FunctionDef node.
|
|
198
|
-
docstring_args: Dict of arg_name -> description from docstring.
|
|
199
|
-
|
|
200
|
-
Returns:
|
|
201
|
-
List of ArgInfo objects.
|
|
202
|
-
"""
|
|
203
|
-
result: list[ArgInfo] = []
|
|
204
|
-
args = node.args
|
|
205
|
-
defaults = args.defaults
|
|
206
|
-
num_defaults = len(defaults)
|
|
207
|
-
num_args = len(args.args)
|
|
208
|
-
|
|
209
|
-
for i, arg in enumerate(args.args):
|
|
210
|
-
type_str = "Any"
|
|
211
|
-
if arg.annotation:
|
|
212
|
-
type_str = annotation_to_str(arg.annotation)
|
|
213
|
-
|
|
214
|
-
default_str: str | None = None
|
|
215
|
-
default_idx = i - (num_args - num_defaults)
|
|
216
|
-
if default_idx >= 0:
|
|
217
|
-
default_str = value_to_str(defaults[default_idx])
|
|
218
|
-
|
|
219
|
-
result.append(
|
|
220
|
-
ArgInfo(
|
|
221
|
-
name=arg.arg,
|
|
222
|
-
type=type_str,
|
|
223
|
-
default=default_str,
|
|
224
|
-
description=docstring_args.get(arg.arg, ""),
|
|
225
|
-
)
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
# Keyword-only args
|
|
229
|
-
kw_defaults = args.kw_defaults
|
|
230
|
-
for i, arg in enumerate(args.kwonlyargs):
|
|
231
|
-
type_str = "Any"
|
|
232
|
-
if arg.annotation:
|
|
233
|
-
type_str = annotation_to_str(arg.annotation)
|
|
234
|
-
|
|
235
|
-
default_str = None
|
|
236
|
-
if kw_defaults[i] is not None:
|
|
237
|
-
default_str = value_to_str(kw_defaults[i])
|
|
238
|
-
|
|
239
|
-
result.append(
|
|
240
|
-
ArgInfo(
|
|
241
|
-
name=arg.arg,
|
|
242
|
-
type=type_str,
|
|
243
|
-
default=default_str,
|
|
244
|
-
description=docstring_args.get(arg.arg, ""),
|
|
245
|
-
)
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
return result
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def parse_docstring(docstring: str) -> dict[str, Any]:
|
|
252
|
-
"""Parse Google-style docstring.
|
|
253
|
-
|
|
254
|
-
Args:
|
|
255
|
-
docstring: The docstring to parse.
|
|
256
|
-
|
|
257
|
-
Returns:
|
|
258
|
-
Dict with 'description', 'args', and 'returns' keys.
|
|
259
|
-
"""
|
|
260
|
-
if not docstring:
|
|
261
|
-
return {"description": "", "args": {}, "returns": ""}
|
|
262
|
-
|
|
263
|
-
parsed = parse_docstring_lib(docstring)
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
"description": parsed.short_description or "",
|
|
267
|
-
"args": {p.arg_name: p.description or "" for p in parsed.params},
|
|
268
|
-
"returns": parsed.returns.description if parsed.returns else "",
|
|
269
|
-
}
|
|
1
|
+
"""AST parsing utilities for extracting function information."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from docstring_parser import parse as parse_docstring_lib
|
|
9
|
+
|
|
10
|
+
from .models import ArgInfo, ToolInfo
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_function(
|
|
14
|
+
node: ast.FunctionDef, module: str, pack: str | None = None
|
|
15
|
+
) -> ToolInfo:
|
|
16
|
+
"""Extract information from a function AST node.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
node: AST FunctionDef node.
|
|
20
|
+
module: Module path for the function.
|
|
21
|
+
pack: Optional pack name for namespace-qualified tool names.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
ToolInfo with extracted signature and docstring info.
|
|
25
|
+
"""
|
|
26
|
+
# Extract signature (with pack-qualified name if pack is provided)
|
|
27
|
+
signature = extract_signature(node, pack=pack)
|
|
28
|
+
|
|
29
|
+
# Parse docstring
|
|
30
|
+
docstring = ast.get_docstring(node) or ""
|
|
31
|
+
doc_info = parse_docstring(docstring)
|
|
32
|
+
|
|
33
|
+
# Extract args
|
|
34
|
+
args = extract_args(node, doc_info.get("args", {}))
|
|
35
|
+
|
|
36
|
+
# Extract @tool decorator metadata if present
|
|
37
|
+
decorator_info = extract_tool_decorator(node)
|
|
38
|
+
|
|
39
|
+
# Decorator description overrides docstring if provided
|
|
40
|
+
description = decorator_info.get("description") or doc_info.get("description", "")
|
|
41
|
+
|
|
42
|
+
# Use pack-qualified name if pack is provided
|
|
43
|
+
qualified_name = f"{pack}.{node.name}" if pack else node.name
|
|
44
|
+
|
|
45
|
+
return ToolInfo(
|
|
46
|
+
name=qualified_name,
|
|
47
|
+
pack=pack,
|
|
48
|
+
module=module,
|
|
49
|
+
signature=signature,
|
|
50
|
+
description=description,
|
|
51
|
+
args=args,
|
|
52
|
+
returns=doc_info.get("returns", ""),
|
|
53
|
+
examples=decorator_info.get("examples", []),
|
|
54
|
+
tags=decorator_info.get("tags", []),
|
|
55
|
+
enabled=decorator_info.get("enabled", True),
|
|
56
|
+
deprecated=decorator_info.get("deprecated", False),
|
|
57
|
+
deprecated_message=decorator_info.get("deprecated_message"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def extract_tool_decorator(node: ast.FunctionDef) -> dict[str, Any]:
|
|
62
|
+
"""Extract metadata from @tool decorator if present.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
node: AST FunctionDef node.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dict with decorator metadata (description, examples, tags, enabled, deprecated).
|
|
69
|
+
"""
|
|
70
|
+
result: dict[str, Any] = {}
|
|
71
|
+
|
|
72
|
+
for decorator in node.decorator_list:
|
|
73
|
+
# Look for @tool(...) decorator
|
|
74
|
+
if isinstance(decorator, ast.Call):
|
|
75
|
+
func = decorator.func
|
|
76
|
+
if isinstance(func, ast.Name) and func.id == "tool":
|
|
77
|
+
# Extract keyword arguments
|
|
78
|
+
for keyword in decorator.keywords:
|
|
79
|
+
if keyword.arg == "description":
|
|
80
|
+
if isinstance(keyword.value, ast.Constant):
|
|
81
|
+
result["description"] = keyword.value.value
|
|
82
|
+
elif keyword.arg == "examples":
|
|
83
|
+
if isinstance(keyword.value, ast.List):
|
|
84
|
+
result["examples"] = [
|
|
85
|
+
elt.value
|
|
86
|
+
for elt in keyword.value.elts
|
|
87
|
+
if isinstance(elt, ast.Constant)
|
|
88
|
+
]
|
|
89
|
+
elif keyword.arg == "tags":
|
|
90
|
+
if isinstance(keyword.value, ast.List):
|
|
91
|
+
result["tags"] = [
|
|
92
|
+
elt.value
|
|
93
|
+
for elt in keyword.value.elts
|
|
94
|
+
if isinstance(elt, ast.Constant)
|
|
95
|
+
]
|
|
96
|
+
elif keyword.arg == "enabled":
|
|
97
|
+
if isinstance(keyword.value, ast.Constant):
|
|
98
|
+
result["enabled"] = keyword.value.value
|
|
99
|
+
elif keyword.arg == "deprecated" and isinstance(
|
|
100
|
+
keyword.value, ast.Constant
|
|
101
|
+
):
|
|
102
|
+
result["deprecated"] = keyword.value.value
|
|
103
|
+
elif keyword.arg == "deprecated_message" and isinstance(
|
|
104
|
+
keyword.value, ast.Constant
|
|
105
|
+
):
|
|
106
|
+
result["deprecated_message"] = keyword.value.value
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def extract_signature(node: ast.FunctionDef, pack: str | None = None) -> str:
|
|
113
|
+
"""Extract function signature string.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
node: AST FunctionDef node.
|
|
117
|
+
pack: Optional pack name for namespace-qualified tool names.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Signature string like 'pack.func_name(arg: type = default) -> return_type'
|
|
121
|
+
"""
|
|
122
|
+
parts: list[str] = []
|
|
123
|
+
|
|
124
|
+
# Process regular args
|
|
125
|
+
args = node.args
|
|
126
|
+
defaults = args.defaults
|
|
127
|
+
num_defaults = len(defaults)
|
|
128
|
+
num_args = len(args.args)
|
|
129
|
+
|
|
130
|
+
for i, arg in enumerate(args.args):
|
|
131
|
+
arg_str = arg.arg
|
|
132
|
+
# Add type annotation
|
|
133
|
+
if arg.annotation:
|
|
134
|
+
arg_str += f": {annotation_to_str(arg.annotation)}"
|
|
135
|
+
|
|
136
|
+
# Add default value if present
|
|
137
|
+
default_idx = i - (num_args - num_defaults)
|
|
138
|
+
if default_idx >= 0:
|
|
139
|
+
default = defaults[default_idx]
|
|
140
|
+
arg_str += f" = {value_to_str(default)}"
|
|
141
|
+
|
|
142
|
+
parts.append(arg_str)
|
|
143
|
+
|
|
144
|
+
# Process keyword-only args
|
|
145
|
+
kw_defaults = args.kw_defaults
|
|
146
|
+
for i, arg in enumerate(args.kwonlyargs):
|
|
147
|
+
arg_str = arg.arg
|
|
148
|
+
if arg.annotation:
|
|
149
|
+
arg_str += f": {annotation_to_str(arg.annotation)}"
|
|
150
|
+
if kw_defaults[i] is not None:
|
|
151
|
+
arg_str += f" = {value_to_str(kw_defaults[i])}"
|
|
152
|
+
parts.append(arg_str)
|
|
153
|
+
|
|
154
|
+
# Build signature with pack-qualified name if pack is provided
|
|
155
|
+
func_name = f"{pack}.{node.name}" if pack else node.name
|
|
156
|
+
sig = f"{func_name}({', '.join(parts)})"
|
|
157
|
+
|
|
158
|
+
# Add return type
|
|
159
|
+
if node.returns:
|
|
160
|
+
sig += f" -> {annotation_to_str(node.returns)}"
|
|
161
|
+
|
|
162
|
+
return sig
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def annotation_to_str(node: ast.expr) -> str:
|
|
166
|
+
"""Convert AST annotation node to string.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
node: AST expression node representing a type annotation.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
String representation of the type.
|
|
173
|
+
"""
|
|
174
|
+
return ast.unparse(node)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def value_to_str(node: ast.expr | None) -> str:
|
|
178
|
+
"""Convert AST value node to string representation.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
node: AST expression node representing a default value.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
String representation of the value.
|
|
185
|
+
"""
|
|
186
|
+
if node is None:
|
|
187
|
+
return "None"
|
|
188
|
+
return ast.unparse(node)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def extract_args(
|
|
192
|
+
node: ast.FunctionDef, docstring_args: dict[str, str]
|
|
193
|
+
) -> list[ArgInfo]:
|
|
194
|
+
"""Extract argument information from function node.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
node: AST FunctionDef node.
|
|
198
|
+
docstring_args: Dict of arg_name -> description from docstring.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
List of ArgInfo objects.
|
|
202
|
+
"""
|
|
203
|
+
result: list[ArgInfo] = []
|
|
204
|
+
args = node.args
|
|
205
|
+
defaults = args.defaults
|
|
206
|
+
num_defaults = len(defaults)
|
|
207
|
+
num_args = len(args.args)
|
|
208
|
+
|
|
209
|
+
for i, arg in enumerate(args.args):
|
|
210
|
+
type_str = "Any"
|
|
211
|
+
if arg.annotation:
|
|
212
|
+
type_str = annotation_to_str(arg.annotation)
|
|
213
|
+
|
|
214
|
+
default_str: str | None = None
|
|
215
|
+
default_idx = i - (num_args - num_defaults)
|
|
216
|
+
if default_idx >= 0:
|
|
217
|
+
default_str = value_to_str(defaults[default_idx])
|
|
218
|
+
|
|
219
|
+
result.append(
|
|
220
|
+
ArgInfo(
|
|
221
|
+
name=arg.arg,
|
|
222
|
+
type=type_str,
|
|
223
|
+
default=default_str,
|
|
224
|
+
description=docstring_args.get(arg.arg, ""),
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Keyword-only args
|
|
229
|
+
kw_defaults = args.kw_defaults
|
|
230
|
+
for i, arg in enumerate(args.kwonlyargs):
|
|
231
|
+
type_str = "Any"
|
|
232
|
+
if arg.annotation:
|
|
233
|
+
type_str = annotation_to_str(arg.annotation)
|
|
234
|
+
|
|
235
|
+
default_str = None
|
|
236
|
+
if kw_defaults[i] is not None:
|
|
237
|
+
default_str = value_to_str(kw_defaults[i])
|
|
238
|
+
|
|
239
|
+
result.append(
|
|
240
|
+
ArgInfo(
|
|
241
|
+
name=arg.arg,
|
|
242
|
+
type=type_str,
|
|
243
|
+
default=default_str,
|
|
244
|
+
description=docstring_args.get(arg.arg, ""),
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
return result
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def parse_docstring(docstring: str) -> dict[str, Any]:
|
|
252
|
+
"""Parse Google-style docstring.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
docstring: The docstring to parse.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Dict with 'description', 'args', and 'returns' keys.
|
|
259
|
+
"""
|
|
260
|
+
if not docstring:
|
|
261
|
+
return {"description": "", "args": {}, "returns": ""}
|
|
262
|
+
|
|
263
|
+
parsed = parse_docstring_lib(docstring)
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
"description": parsed.short_description or "",
|
|
267
|
+
"args": {p.arg_name: p.description or "" for p in parsed.params},
|
|
268
|
+
"returns": parsed.returns.description if parsed.returns else "",
|
|
269
|
+
}
|