diff --git a/Lib/inspect.py b/Lib/inspect.py index 0e7b40eb39bce8..c01c5593029766 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2228,11 +2228,21 @@ def _signature_fromstr(cls, obj, s, skip_bound_arg=True): sys_module_dict = sys.modules.copy() def parse_name(node): - assert isinstance(node, ast.arg) - if node.annotation is not None: - raise ValueError("Annotations are not currently supported") return node.arg + def parse_annotation(annotation): + if annotation: + expr = ast.unparse(annotation) + try: + value = eval(expr, module_dict) + except NameError: + try: + value = eval(expr, sys_module_dict) + except NameError: + raise ValueError + return value + return empty + def wrap_value(s): try: value = eval(s, module_dict) @@ -2280,6 +2290,7 @@ def visit_BinOp(self, node): raise ValueError def p(name_node, default_node, default=empty): + assert isinstance(name_node, ast.arg) name = parse_name(name_node) if default_node and default_node is not _empty: try: @@ -2287,7 +2298,11 @@ def p(name_node, default_node, default=empty): default = ast.literal_eval(default_node) except ValueError: raise ValueError("{!r} builtin has invalid signature".format(obj)) from None - parameters.append(Parameter(name, kind, default=default, annotation=empty)) + try: + annotation = parse_annotation(name_node.annotation) + except ValueError: + raise ValueError("{!r} builtin has invalid signature".format(obj)) from None + parameters.append(Parameter(name, kind, default=default, annotation=annotation)) # non-keyword-only parameters total_non_kw_args = len(f.args.posonlyargs) + len(f.args.args) @@ -2334,7 +2349,12 @@ def p(name_node, default_node, default=empty): p = parameters[0].replace(kind=Parameter.POSITIONAL_ONLY) parameters[0] = p - return cls(parameters, return_annotation=cls.empty) + try: + return_annotation = parse_annotation(f.returns) + except ValueError: + raise ValueError("{!r} builtin has invalid signature".format(obj)) from None + + return cls(parameters, return_annotation=return_annotation) def _signature_from_builtin(cls, func, skip_bound_arg=True): diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index d39c3ccdc847bd..f9629260c516bf 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5875,6 +5875,40 @@ class MyBufferedReader(BufferedReader): sig = inspect.signature(MyBufferedReader) self.assertEqual(str(sig), '(raw, buffer_size=8192)') + def test_annotations_in_text_signature(self): + import fractions + + def func(*args, **kwargs): + pass + + func.__text_signature__ = '($self, a: int) -> list' + sig = inspect.signature(func) + self.assertIsNotNone(sig) + self.assertEqual(str(sig), '(self, /, a: int) -> list') + + func.__text_signature__ = '($self, a: int | float)' + sig = inspect.signature(func) + self.assertIsNotNone(sig) + self.assertEqual(str(sig), '(self, /, a: int | float)') + + func.__text_signature__ = '($self, a: tuple[int, ...])' + sig = inspect.signature(func) + self.assertIsNotNone(sig) + self.assertEqual(str(sig), '(self, /, a: tuple[int, ...])') + + func.__text_signature__ = '($self, x: spam)' + with self.assertRaises(ValueError): + inspect.signature(func) + + func.__text_signature__ = '($self, x) -> spam' + with self.assertRaises(ValueError): + inspect.signature(func) + + func.__text_signature__ = '($self, x) -> fractions.Fraction' + sig = inspect.signature(func) + self.assertIsNotNone(sig) + self.assertEqual(str(sig), '(self, /, x) -> fractions.Fraction') + class NTimesUnwrappable: def __init__(self, n): diff --git a/Misc/NEWS.d/next/Library/2023-02-20-07-33-54.gh-issue-81677.bSmaNE.rst b/Misc/NEWS.d/next/Library/2023-02-20-07-33-54.gh-issue-81677.bSmaNE.rst new file mode 100644 index 00000000000000..0d1e2c91fedbf6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-20-07-33-54.gh-issue-81677.bSmaNE.rst @@ -0,0 +1,2 @@ +Basic support of annotations in :func:`inspect.signature` handling of +``__text_signature__``. Patch by Sergey B Kirpichev. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 7d01b680605a38..754f0cde24163c 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -685,8 +685,8 @@ find_signature(const char *name, const char *doc) return doc; } -#define SIGNATURE_END_MARKER ")\n--\n\n" -#define SIGNATURE_END_MARKER_LENGTH 6 +#define SIGNATURE_END_MARKER "\n--\n\n" +#define SIGNATURE_END_MARKER_LENGTH 5 /* * skips past the end of the docstring's introspection signature. * (assumes doc starts with a valid signature prefix.) @@ -803,11 +803,12 @@ _PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_d Py_RETURN_NONE; } - /* back "end" up until it points just past the final ')' */ - end -= SIGNATURE_END_MARKER_LENGTH - 1; + /* back "end" up until it points just to the end marker */ + end -= SIGNATURE_END_MARKER_LENGTH; assert((end - start) >= 2); /* should be "()" at least */ - assert(end[-1] == ')'); assert(end[0] == '\n'); + assert(end[1] == '-'); + assert(end[2] == '-'); return PyUnicode_FromStringAndSize(start, end - start); }