Update Modules/_docstrings.py
Browse files- Modules/_docstrings.py +1 -74
Modules/_docstrings.py
CHANGED
|
@@ -1,22 +1,12 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
-
import ast
|
| 4 |
import inspect
|
| 5 |
-
import
|
| 6 |
-
import types
|
| 7 |
-
from typing import Any, Annotated, get_args, get_origin, get_type_hints, Union
|
| 8 |
|
| 9 |
|
| 10 |
def _typename(tp: Any) -> str:
|
| 11 |
"""Return a readable type name from a type or annotation."""
|
| 12 |
try:
|
| 13 |
-
# Unwrap Optional[T] -> T
|
| 14 |
-
origin = get_origin(tp)
|
| 15 |
-
if origin is Union or (sys.version_info >= (3, 10) and origin is types.UnionType):
|
| 16 |
-
args = [a for a in get_args(tp) if a is not type(None)]
|
| 17 |
-
if len(args) == 1:
|
| 18 |
-
return _typename(args[0])
|
| 19 |
-
|
| 20 |
if hasattr(tp, "__name__"):
|
| 21 |
return tp.__name__ # e.g. int, str
|
| 22 |
if getattr(tp, "__module__", None) and getattr(tp, "__qualname__", None):
|
|
@@ -42,43 +32,6 @@ def _extract_base_and_meta(annotation: Any) -> tuple[Any, str | None]:
|
|
| 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
|
|
@@ -105,24 +58,7 @@ def autodoc(summary: str | None = None, returns: str | None = None, *, force: bo
|
|
| 105 |
# include_extras=True to retain Annotated metadata
|
| 106 |
hints = get_type_hints(func, include_extras=True, globalns=getattr(func, "__globals__", None))
|
| 107 |
except Exception:
|
| 108 |
-
# Fallback: try to evaluate annotations manually if they are strings
|
| 109 |
hints = {}
|
| 110 |
-
sig = inspect.signature(func)
|
| 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
|
| 126 |
|
| 127 |
sig = inspect.signature(func)
|
| 128 |
|
|
@@ -144,16 +80,7 @@ def autodoc(summary: str | None = None, returns: str | None = None, *, force: bo
|
|
| 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):
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
| 3 |
import inspect
|
| 4 |
+
from typing import Any, Annotated, get_args, get_origin, get_type_hints
|
|
|
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
def _typename(tp: Any) -> str:
|
| 8 |
"""Return a readable type name from a type or annotation."""
|
| 9 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
if hasattr(tp, "__name__"):
|
| 11 |
return tp.__name__ # e.g. int, str
|
| 12 |
if getattr(tp, "__module__", None) and getattr(tp, "__qualname__", None):
|
|
|
|
| 32 |
return annotation, None
|
| 33 |
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
def autodoc(summary: str | None = None, returns: str | None = None, *, force: bool = False):
|
| 36 |
"""
|
| 37 |
Decorator that auto-generates a concise Google-style docstring from a function's
|
|
|
|
| 58 |
# include_extras=True to retain Annotated metadata
|
| 59 |
hints = get_type_hints(func, include_extras=True, globalns=getattr(func, "__globals__", None))
|
| 60 |
except Exception:
|
|
|
|
| 61 |
hints = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
sig = inspect.signature(func)
|
| 64 |
|
|
|
|
| 80 |
if name == "self":
|
| 81 |
continue
|
| 82 |
annot = hints.get(name, param.annotation)
|
|
|
|
| 83 |
base, meta = _extract_base_and_meta(annot)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
tname = _typename(base) if base is not inspect._empty else None
|
| 85 |
desc = meta or ""
|
| 86 |
if tname and tname != str(inspect._empty):
|