Update Modules/_docstrings.py
Browse files- Modules/_docstrings.py +57 -2
Modules/_docstrings.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
| 3 |
import inspect
|
| 4 |
import sys
|
| 5 |
import types
|
|
@@ -41,6 +42,43 @@ def _extract_base_and_meta(annotation: Any) -> tuple[Any, str | None]:
|
|
| 41 |
return annotation, None
|
| 42 |
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
def autodoc(summary: str | None = None, returns: str | None = None, *, force: bool = False):
|
| 45 |
"""
|
| 46 |
Decorator that auto-generates a concise Google-style docstring from a function's
|
|
@@ -73,7 +111,15 @@ def autodoc(summary: str | None = None, returns: str | None = None, *, force: bo
|
|
| 73 |
for name, param in sig.parameters.items():
|
| 74 |
if isinstance(param.annotation, str):
|
| 75 |
try:
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
hints[name] = val
|
| 78 |
except Exception:
|
| 79 |
pass
|
|
@@ -98,7 +144,16 @@ def autodoc(summary: str | None = None, returns: str | None = None, *, force: bo
|
|
| 98 |
if name == "self":
|
| 99 |
continue
|
| 100 |
annot = hints.get(name, param.annotation)
|
|
|
|
| 101 |
base, meta = _extract_base_and_meta(annot)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
tname = _typename(base) if base is not inspect._empty else None
|
| 103 |
desc = meta or ""
|
| 104 |
if tname and tname != str(inspect._empty):
|
|
@@ -127,4 +182,4 @@ def autodoc(summary: str | None = None, returns: str | None = None, *, force: bo
|
|
| 127 |
return decorator
|
| 128 |
|
| 129 |
|
| 130 |
-
__all__ = ["autodoc"]
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
import ast
|
| 4 |
import inspect
|
| 5 |
import sys
|
| 6 |
import types
|
|
|
|
| 42 |
return annotation, None
|
| 43 |
|
| 44 |
|
| 45 |
+
def _parse_annotated_string(annot_str: str) -> tuple[str, str | None]:
|
| 46 |
+
"""Fallback: parse 'Annotated[Type, "desc"]' string using AST."""
|
| 47 |
+
try:
|
| 48 |
+
expr = ast.parse(annot_str, mode='eval').body
|
| 49 |
+
if isinstance(expr, ast.Subscript):
|
| 50 |
+
val = expr.value
|
| 51 |
+
is_annotated = False
|
| 52 |
+
if isinstance(val, ast.Name) and val.id == 'Annotated':
|
| 53 |
+
is_annotated = True
|
| 54 |
+
elif isinstance(val, ast.Attribute) and val.attr == 'Annotated':
|
| 55 |
+
is_annotated = True
|
| 56 |
+
|
| 57 |
+
if is_annotated:
|
| 58 |
+
sl = expr.slice
|
| 59 |
+
# In 3.9+, slice is the node. In <3.9, it might be Index/ExtSlice.
|
| 60 |
+
if isinstance(sl, ast.Tuple):
|
| 61 |
+
elts = sl.elts
|
| 62 |
+
if len(elts) >= 2:
|
| 63 |
+
# elts[0] is type, elts[1] is metadata
|
| 64 |
+
meta_node = elts[1]
|
| 65 |
+
desc = None
|
| 66 |
+
if isinstance(meta_node, ast.Constant) and isinstance(meta_node.value, str):
|
| 67 |
+
desc = meta_node.value
|
| 68 |
+
elif isinstance(meta_node, ast.Str):
|
| 69 |
+
desc = meta_node.s
|
| 70 |
+
|
| 71 |
+
if desc:
|
| 72 |
+
if hasattr(ast, 'unparse'):
|
| 73 |
+
type_str = ast.unparse(elts[0])
|
| 74 |
+
else:
|
| 75 |
+
type_str = "Any"
|
| 76 |
+
return type_str, desc
|
| 77 |
+
except Exception:
|
| 78 |
+
pass
|
| 79 |
+
return annot_str, None
|
| 80 |
+
|
| 81 |
+
|
| 82 |
def autodoc(summary: str | None = None, returns: str | None = None, *, force: bool = False):
|
| 83 |
"""
|
| 84 |
Decorator that auto-generates a concise Google-style docstring from a function's
|
|
|
|
| 111 |
for name, param in sig.parameters.items():
|
| 112 |
if isinstance(param.annotation, str):
|
| 113 |
try:
|
| 114 |
+
# Ensure typing is available in eval context
|
| 115 |
+
globs = getattr(func, "__globals__", {}).copy()
|
| 116 |
+
import typing
|
| 117 |
+
globs['typing'] = typing
|
| 118 |
+
for t in ['Annotated', 'Literal', 'Optional', 'Union', 'List', 'Dict', 'Any']:
|
| 119 |
+
if t not in globs:
|
| 120 |
+
globs[t] = getattr(typing, t)
|
| 121 |
+
|
| 122 |
+
val = eval(param.annotation, globs)
|
| 123 |
hints[name] = val
|
| 124 |
except Exception:
|
| 125 |
pass
|
|
|
|
| 144 |
if name == "self":
|
| 145 |
continue
|
| 146 |
annot = hints.get(name, param.annotation)
|
| 147 |
+
|
| 148 |
base, meta = _extract_base_and_meta(annot)
|
| 149 |
+
|
| 150 |
+
# If meta is missing and annot is a string, try AST fallback
|
| 151 |
+
if meta is None and isinstance(annot, str):
|
| 152 |
+
base_str, meta_str = _parse_annotated_string(annot)
|
| 153 |
+
if meta_str:
|
| 154 |
+
base = base_str
|
| 155 |
+
meta = meta_str
|
| 156 |
+
|
| 157 |
tname = _typename(base) if base is not inspect._empty else None
|
| 158 |
desc = meta or ""
|
| 159 |
if tname and tname != str(inspect._empty):
|
|
|
|
| 182 |
return decorator
|
| 183 |
|
| 184 |
|
| 185 |
+
__all__ = ["autodoc"]
|