Update Modules/_docstrings.py
Browse files- Modules/_docstrings.py +37 -0
Modules/_docstrings.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import inspect
|
|
|
|
| 4 |
from typing import Any, Annotated, get_args, get_origin, get_type_hints
|
| 5 |
|
| 6 |
|
|
@@ -16,9 +17,45 @@ def _typename(tp: Any) -> str:
|
|
| 16 |
return str(tp)
|
| 17 |
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
def _extract_base_and_meta(annotation: Any) -> tuple[Any, str | None]:
|
| 20 |
"""Given an annotation, return (base_type, first string metadata) if Annotated, else (annotation, None)."""
|
| 21 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
if get_origin(annotation) is Annotated:
|
| 23 |
args = get_args(annotation)
|
| 24 |
base = args[0] if args else annotation
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import inspect
|
| 4 |
+
import re
|
| 5 |
from typing import Any, Annotated, get_args, get_origin, get_type_hints
|
| 6 |
|
| 7 |
|
|
|
|
| 17 |
return str(tp)
|
| 18 |
|
| 19 |
|
| 20 |
+
def _parse_string_annotation(annot_str: str) -> tuple[str | None, str | None]:
|
| 21 |
+
"""
|
| 22 |
+
Parse a string annotation like "Annotated[Optional[str], 'description']"
|
| 23 |
+
and extract the base type name and the description metadata.
|
| 24 |
+
|
| 25 |
+
Returns (base_type_name, description) or (None, None) if parsing fails.
|
| 26 |
+
"""
|
| 27 |
+
if not isinstance(annot_str, str):
|
| 28 |
+
return None, None
|
| 29 |
+
|
| 30 |
+
# Match Annotated[..., 'description'] or Annotated[..., "description"]
|
| 31 |
+
# Pattern: Annotated[<base_type>, '<description>'] or with double quotes
|
| 32 |
+
match = re.match(
|
| 33 |
+
r"^Annotated\[(.+?),\s*['\"](.+?)['\"]\s*\]$",
|
| 34 |
+
annot_str.strip(),
|
| 35 |
+
re.DOTALL,
|
| 36 |
+
)
|
| 37 |
+
if match:
|
| 38 |
+
base_type_str = match.group(1).strip()
|
| 39 |
+
description = match.group(2)
|
| 40 |
+
# Simplify Optional[X] -> just the base type for display
|
| 41 |
+
opt_match = re.match(r"^Optional\[(.+)\]$", base_type_str)
|
| 42 |
+
if opt_match:
|
| 43 |
+
base_type_str = opt_match.group(1).strip()
|
| 44 |
+
return base_type_str, description
|
| 45 |
+
|
| 46 |
+
return None, None
|
| 47 |
+
|
| 48 |
+
|
| 49 |
def _extract_base_and_meta(annotation: Any) -> tuple[Any, str | None]:
|
| 50 |
"""Given an annotation, return (base_type, first string metadata) if Annotated, else (annotation, None)."""
|
| 51 |
try:
|
| 52 |
+
# Handle string annotations from PEP 563 (__future__.annotations)
|
| 53 |
+
if isinstance(annotation, str):
|
| 54 |
+
base_str, meta = _parse_string_annotation(annotation)
|
| 55 |
+
if meta:
|
| 56 |
+
return base_str or annotation, meta
|
| 57 |
+
return annotation, None
|
| 58 |
+
|
| 59 |
if get_origin(annotation) is Annotated:
|
| 60 |
args = get_args(annotation)
|
| 61 |
base = args[0] if args else annotation
|