Issue #20144: Argument Clinic now supports simple constants as parameter
default values. inspect.Signature correspondingly supports them in __text_signature__ fields for builtins.
This commit is contained in:
parent
0bce6e7462
commit
16c5191ab3
|
@ -1974,18 +1974,60 @@ class Signature:
|
|||
|
||||
parameters = []
|
||||
empty = Parameter.empty
|
||||
invalid = object()
|
||||
|
||||
def parse_attribute(node):
|
||||
if not isinstance(node.ctx, ast.Load):
|
||||
return None
|
||||
|
||||
value = node.value
|
||||
o = parse_node(value)
|
||||
if o is invalid:
|
||||
return invalid
|
||||
|
||||
if isinstance(value, ast.Name):
|
||||
name = o
|
||||
if name not in sys.modules:
|
||||
return invalid
|
||||
o = sys.modules[name]
|
||||
|
||||
return getattr(o, node.attr, invalid)
|
||||
|
||||
def parse_node(node):
|
||||
if isinstance(node, ast.arg):
|
||||
if node.annotation != None:
|
||||
raise ValueError("Annotations are not currently supported")
|
||||
return node.arg
|
||||
if isinstance(node, ast.Num):
|
||||
return node.n
|
||||
if isinstance(node, ast.Str):
|
||||
return node.s
|
||||
if isinstance(node, ast.NameConstant):
|
||||
return node.value
|
||||
if isinstance(node, ast.Attribute):
|
||||
return parse_attribute(node)
|
||||
if isinstance(node, ast.Name):
|
||||
if not isinstance(node.ctx, ast.Load):
|
||||
return invalid
|
||||
return node.id
|
||||
return invalid
|
||||
|
||||
def p(name_node, default_node, default=empty):
|
||||
name = name_node.arg
|
||||
|
||||
if isinstance(default_node, ast.Num):
|
||||
default = default.n
|
||||
elif isinstance(default_node, ast.NameConstant):
|
||||
default = default_node.value
|
||||
name = parse_node(name_node)
|
||||
if name is invalid:
|
||||
return None
|
||||
if default_node:
|
||||
o = parse_node(default_node)
|
||||
if o is invalid:
|
||||
return None
|
||||
default = o if o is not invalid else default
|
||||
parameters.append(Parameter(name, kind, default=default, annotation=empty))
|
||||
|
||||
# non-keyword-only parameters
|
||||
for name, default in reversed(list(itertools.zip_longest(reversed(f.args.args), reversed(f.args.defaults), fillvalue=None))):
|
||||
args = reversed(f.args.args)
|
||||
defaults = reversed(f.args.defaults)
|
||||
iter = itertools.zip_longest(args, defaults, fillvalue=None)
|
||||
for name, default in reversed(list(iter)):
|
||||
p(name, default)
|
||||
|
||||
# *args
|
||||
|
|
|
@ -15,6 +15,7 @@ try:
|
|||
from concurrent.futures import ThreadPoolExecutor
|
||||
except ImportError:
|
||||
ThreadPoolExecutor = None
|
||||
import _testcapi
|
||||
|
||||
from test.support import run_unittest, TESTFN, DirsOnSysPath
|
||||
from test.support import MISSING_C_DOCSTRINGS
|
||||
|
@ -1593,9 +1594,19 @@ class TestSignatureObject(unittest.TestCase):
|
|||
@unittest.skipIf(MISSING_C_DOCSTRINGS,
|
||||
"Signature information for builtins requires docstrings")
|
||||
def test_signature_on_builtins(self):
|
||||
# min doesn't have a signature (yet)
|
||||
self.assertEqual(inspect.signature(min), None)
|
||||
signature = inspect.signature(os.stat)
|
||||
|
||||
signature = inspect.signature(_testcapi.docstring_with_signature_with_defaults)
|
||||
self.assertTrue(isinstance(signature, inspect.Signature))
|
||||
def p(name): return signature.parameters[name].default
|
||||
self.assertEqual(p('s'), 'avocado')
|
||||
self.assertEqual(p('d'), 3.14)
|
||||
self.assertEqual(p('i'), 35)
|
||||
self.assertEqual(p('c'), sys.maxsize)
|
||||
self.assertEqual(p('n'), None)
|
||||
self.assertEqual(p('t'), True)
|
||||
self.assertEqual(p('f'), False)
|
||||
|
||||
def test_signature_on_non_function(self):
|
||||
with self.assertRaisesRegex(TypeError, 'is not a callable object'):
|
||||
|
|
|
@ -13,11 +13,17 @@ Core and Builtins
|
|||
Library
|
||||
-------
|
||||
|
||||
- Issue #20144: inspect.Signature now supports parsing simple symbolic
|
||||
constants as parameter default values in __text_signature__.
|
||||
|
||||
- Issue #20072: Fixed multiple errors in tkinter with wantobjects is False.
|
||||
|
||||
Tools/Demos
|
||||
-----------
|
||||
|
||||
- Issue #20144: Argument Clinic now supports simple symbolic constants
|
||||
as parameter default values.
|
||||
|
||||
- Issue #20143: The line numbers reported in Argument Clinic errors are
|
||||
now more accurate.
|
||||
|
||||
|
|
|
@ -526,21 +526,58 @@ sre_search(SRE_STATE* state, SRE_CODE* pattern)
|
|||
return sre_ucs4_search(state, pattern);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
pattern_match(PatternObject* self, PyObject* args, PyObject* kw)
|
||||
/*[clinic]
|
||||
module _sre
|
||||
class _sre.SRE_Pattern
|
||||
|
||||
_sre.SRE_Pattern.match as pattern_match
|
||||
|
||||
self: self(type="PatternObject *")
|
||||
pattern: object
|
||||
pos: Py_ssize_t = 0
|
||||
endpos: Py_ssize_t(c_default="PY_SSIZE_T_MAX") = sys.maxsize
|
||||
|
||||
Matches zero or more characters at the beginning of the string.
|
||||
[clinic]*/
|
||||
|
||||
PyDoc_STRVAR(pattern_match__doc__,
|
||||
"match(pattern, pos=0, endpos=sys.maxsize)\n"
|
||||
"Matches zero or more characters at the beginning of the string.");
|
||||
|
||||
#define PATTERN_MATCH_METHODDEF \
|
||||
{"match", (PyCFunction)pattern_match, METH_VARARGS|METH_KEYWORDS, pattern_match__doc__},
|
||||
|
||||
static PyObject *
|
||||
pattern_match_impl(PatternObject *self, PyObject *pattern, Py_ssize_t pos, Py_ssize_t endpos);
|
||||
|
||||
static PyObject *
|
||||
pattern_match(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
static char *_keywords[] = {"pattern", "pos", "endpos", NULL};
|
||||
PyObject *pattern;
|
||||
Py_ssize_t pos = 0;
|
||||
Py_ssize_t endpos = PY_SSIZE_T_MAX;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
||||
"O|nn:match", _keywords,
|
||||
&pattern, &pos, &endpos))
|
||||
goto exit;
|
||||
return_value = pattern_match_impl((PatternObject *)self, pattern, pos, endpos);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pattern_match_impl(PatternObject *self, PyObject *pattern, Py_ssize_t pos, Py_ssize_t endpos)
|
||||
/*[clinic checksum: 63e59c5f3019efe6c1f3acdec42b2d3595e14a09]*/
|
||||
{
|
||||
SRE_STATE state;
|
||||
Py_ssize_t status;
|
||||
PyObject *string;
|
||||
|
||||
PyObject* string;
|
||||
Py_ssize_t start = 0;
|
||||
Py_ssize_t end = PY_SSIZE_T_MAX;
|
||||
static char* kwlist[] = { "pattern", "pos", "endpos", NULL };
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "O|nn:match", kwlist,
|
||||
&string, &start, &end))
|
||||
return NULL;
|
||||
|
||||
string = state_init(&state, self, string, start, end);
|
||||
string = state_init(&state, (PatternObject *)self, pattern, pos, endpos);
|
||||
if (!string)
|
||||
return NULL;
|
||||
|
||||
|
@ -556,7 +593,7 @@ pattern_match(PatternObject* self, PyObject* args, PyObject* kw)
|
|||
|
||||
state_fini(&state);
|
||||
|
||||
return pattern_new_match(self, &state, status);
|
||||
return (PyObject *)pattern_new_match(self, &state, status);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
|
@ -1254,10 +1291,6 @@ done:
|
|||
return result;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(pattern_match_doc,
|
||||
"match(string[, pos[, endpos]]) -> match object or None.\n\
|
||||
Matches zero or more characters at the beginning of the string");
|
||||
|
||||
PyDoc_STRVAR(pattern_fullmatch_doc,
|
||||
"fullmatch(string[, pos[, endpos]]) -> match object or None.\n\
|
||||
Matches against all of the string");
|
||||
|
@ -1295,8 +1328,7 @@ PyDoc_STRVAR(pattern_subn_doc,
|
|||
PyDoc_STRVAR(pattern_doc, "Compiled regular expression objects");
|
||||
|
||||
static PyMethodDef pattern_methods[] = {
|
||||
{"match", (PyCFunction) pattern_match, METH_VARARGS|METH_KEYWORDS,
|
||||
pattern_match_doc},
|
||||
PATTERN_MATCH_METHODDEF
|
||||
{"fullmatch", (PyCFunction) pattern_fullmatch, METH_VARARGS|METH_KEYWORDS,
|
||||
pattern_fullmatch_doc},
|
||||
{"search", (PyCFunction) pattern_search, METH_VARARGS|METH_KEYWORDS,
|
||||
|
|
|
@ -2869,6 +2869,15 @@ PyDoc_STRVAR(docstring_with_signature_and_extra_newlines,
|
|||
"This docstring has a valid signature and some extra newlines."
|
||||
);
|
||||
|
||||
PyDoc_STRVAR(docstring_with_signature_with_defaults,
|
||||
"docstring_with_signature_with_defaults(s='avocado', d=3.14, i=35, c=sys.maxsize, n=None, t=True, f=False)\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"This docstring has a valid signature with parameters,\n"
|
||||
"and the parameters take defaults of varying types."
|
||||
);
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
typedef struct {
|
||||
PyThread_type_lock start_event;
|
||||
|
@ -3087,6 +3096,9 @@ static PyMethodDef TestMethods[] = {
|
|||
{"docstring_with_signature_and_extra_newlines",
|
||||
(PyCFunction)test_with_docstring, METH_NOARGS,
|
||||
docstring_with_signature_and_extra_newlines},
|
||||
{"docstring_with_signature_with_defaults",
|
||||
(PyCFunction)test_with_docstring, METH_NOARGS,
|
||||
docstring_with_signature_with_defaults},
|
||||
#ifdef WITH_THREAD
|
||||
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O,
|
||||
PyDoc_STR("set_error_class(error_class) -> None")},
|
||||
|
|
|
@ -1362,15 +1362,15 @@ class CConverter(metaclass=CConverterAutoRegister):
|
|||
# Only used by format units ending with '#'.
|
||||
length = False
|
||||
|
||||
def __init__(self, name, function, default=unspecified, *, doc_default=None, required=False, annotation=unspecified, **kwargs):
|
||||
def __init__(self, name, function, default=unspecified, *, doc_default=None, c_default=None, py_default=None, required=False, annotation=unspecified, **kwargs):
|
||||
self.function = function
|
||||
self.name = name
|
||||
|
||||
if default is not unspecified:
|
||||
self.default = default
|
||||
self.py_default = py_repr(default)
|
||||
self.py_default = py_default if py_default is not None else py_repr(default)
|
||||
self.doc_default = doc_default if doc_default is not None else self.py_default
|
||||
self.c_default = c_repr(default)
|
||||
self.c_default = c_default if c_default is not None else c_repr(default)
|
||||
elif doc_default is not None:
|
||||
fail(function.fullname + " argument " + name + " specified a 'doc_default' without having a 'default'")
|
||||
if annotation != unspecified:
|
||||
|
@ -2315,18 +2315,36 @@ class DSLParser:
|
|||
function_args = module.body[0].args
|
||||
parameter = function_args.args[0]
|
||||
|
||||
py_default = None
|
||||
|
||||
parameter_name = parameter.arg
|
||||
name, legacy, kwargs = self.parse_converter(parameter.annotation)
|
||||
|
||||
if function_args.defaults:
|
||||
expr = function_args.defaults[0]
|
||||
# mild hack: explicitly support NULL as a default value
|
||||
if isinstance(expr, ast.Name) and expr.id == 'NULL':
|
||||
value = NULL
|
||||
elif isinstance(expr, ast.Attribute):
|
||||
a = []
|
||||
n = expr
|
||||
while isinstance(n, ast.Attribute):
|
||||
a.append(n.attr)
|
||||
n = n.value
|
||||
if not isinstance(n, ast.Name):
|
||||
fail("Malformed default value (looked like a Python constant)")
|
||||
a.append(n.id)
|
||||
py_default = ".".join(reversed(a))
|
||||
value = None
|
||||
c_default = kwargs.get("c_default")
|
||||
if not (isinstance(c_default, str) and c_default):
|
||||
fail("When you specify a named constant (" + repr(py_default) + ") as your default value,\nyou MUST specify a valid c_default.")
|
||||
kwargs["py_default"] = py_default
|
||||
else:
|
||||
value = ast.literal_eval(expr)
|
||||
else:
|
||||
value = unspecified
|
||||
|
||||
parameter_name = parameter.arg
|
||||
name, legacy, kwargs = self.parse_converter(parameter.annotation)
|
||||
dict = legacy_converters if legacy else converters
|
||||
legacy_str = "legacy " if legacy else ""
|
||||
if name not in dict:
|
||||
|
|
Loading…
Reference in New Issue