[Python-checkins] gh-95065: Produce nicer deprecation messages in Argument Clinic (#107808)

erlend-aasland webhook-mailer at python.org
Wed Aug 9 09:28:23 EDT 2023


https://github.com/python/cpython/commit/0a7f48b9a8d54809f1e9272337aefe2444158e64
commit: 0a7f48b9a8d54809f1e9272337aefe2444158e64
branch: main
author: Erlend E. Aasland <erlend at python.org>
committer: erlend-aasland <erlend.aasland at protonmail.com>
date: 2023-08-09T13:28:18Z
summary:

gh-95065: Produce nicer deprecation messages in Argument Clinic (#107808)

files:
M Lib/test/clinic.test.c
M Tools/clinic/clinic.py

diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c
index 9fcee0dad7118..c7063e1139b0a 100644
--- a/Lib/test/clinic.test.c
+++ b/Lib/test/clinic.test.c
@@ -4,9 +4,11 @@ output preset block
 /*[clinic end generated code: output=da39a3ee5e6b4b0d input=3c81ac2402d06a8b]*/
 
 /*[clinic input]
+module m
+class m.T "TestObj *" "TestType"
 class Test "TestObj *" "TestType"
 [clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=fc7e50384d12b83f]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=f761b4d55cb179cf]*/
 
 /*[clinic input]
 test_object_converter
@@ -6437,3 +6439,192 @@ test_deprecate_positional_pos2_len3_with_kwdonly_impl(PyObject *module,
                                                       PyObject *d,
                                                       PyObject *e)
 /*[clinic end generated code: output=383d56b03f7c2dcb input=154fd450448d8935]*/
+
+
+/*[clinic input]
+ at classmethod
+Test.__new__
+    * [from 3.14]
+    a: object
+The deprecation message should use the class name instead of __new__.
+[clinic start generated code]*/
+
+PyDoc_STRVAR(Test__doc__,
+"Test(a)\n"
+"--\n"
+"\n"
+"The deprecation message should use the class name instead of __new__.\n"
+"\n"
+"Note: Passing positional arguments to Test() is deprecated. Parameter\n"
+"\'a\' will become a keyword-only parameter in Python 3.14.\n"
+"");
+
+static PyObject *
+Test_impl(PyTypeObject *type, PyObject *a);
+
+static PyObject *
+Test(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+    PyObject *return_value = NULL;
+    #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+    #define NUM_KEYWORDS 1
+    static struct {
+        PyGC_Head _this_is_not_used;
+        PyObject_VAR_HEAD
+        PyObject *ob_item[NUM_KEYWORDS];
+    } _kwtuple = {
+        .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+        .ob_item = { &_Py_ID(a), },
+    };
+    #undef NUM_KEYWORDS
+    #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+    #else  // !Py_BUILD_CORE
+    #  define KWTUPLE NULL
+    #endif  // !Py_BUILD_CORE
+
+    static const char * const _keywords[] = {"a", NULL};
+    static _PyArg_Parser _parser = {
+        .keywords = _keywords,
+        .fname = "Test",
+        .kwtuple = KWTUPLE,
+    };
+    #undef KWTUPLE
+    PyObject *argsbuf[1];
+    PyObject * const *fastargs;
+    Py_ssize_t nargs = PyTuple_GET_SIZE(args);
+    PyObject *a;
+
+    // Emit compiler warnings when we get to Python 3.14.
+    #if PY_VERSION_HEX >= 0x030e00C0
+    #  error \
+            "In clinic.test.c, update parameter(s) 'a' in the clinic input of" \
+            " 'Test.__new__' to be keyword-only."
+    #elif PY_VERSION_HEX >= 0x030e00A0
+    #  ifdef _MSC_VER
+    #    pragma message ( \
+            "In clinic.test.c, update parameter(s) 'a' in the clinic input of" \
+            " 'Test.__new__' to be keyword-only.")
+    #  else
+    #    warning \
+            "In clinic.test.c, update parameter(s) 'a' in the clinic input of" \
+            " 'Test.__new__' to be keyword-only."
+    #  endif
+    #endif
+    if (nargs == 1) {
+        if (PyErr_WarnEx(PyExc_DeprecationWarning,
+                "Passing positional arguments to Test() is deprecated. Parameter "
+                "'a' will become a keyword-only parameter in Python 3.14.", 1))
+        {
+                goto exit;
+        }
+    }
+    fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf);
+    if (!fastargs) {
+        goto exit;
+    }
+    a = fastargs[0];
+    return_value = Test_impl(type, a);
+
+exit:
+    return return_value;
+}
+
+static PyObject *
+Test_impl(PyTypeObject *type, PyObject *a)
+/*[clinic end generated code: output=d15a69ea37ec6502 input=f133dc077aef49ec]*/
+
+
+/*[clinic input]
+m.T.__init__
+    * [from 3.14]
+    a: object
+The deprecation message should use the class name instead of __init__.
+[clinic start generated code]*/
+
+PyDoc_STRVAR(m_T___init____doc__,
+"T(a)\n"
+"--\n"
+"\n"
+"The deprecation message should use the class name instead of __init__.\n"
+"\n"
+"Note: Passing positional arguments to m.T() is deprecated. Parameter\n"
+"\'a\' will become a keyword-only parameter in Python 3.14.\n"
+"");
+
+static int
+m_T___init___impl(TestObj *self, PyObject *a);
+
+static int
+m_T___init__(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    int return_value = -1;
+    #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+    #define NUM_KEYWORDS 1
+    static struct {
+        PyGC_Head _this_is_not_used;
+        PyObject_VAR_HEAD
+        PyObject *ob_item[NUM_KEYWORDS];
+    } _kwtuple = {
+        .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+        .ob_item = { &_Py_ID(a), },
+    };
+    #undef NUM_KEYWORDS
+    #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+    #else  // !Py_BUILD_CORE
+    #  define KWTUPLE NULL
+    #endif  // !Py_BUILD_CORE
+
+    static const char * const _keywords[] = {"a", NULL};
+    static _PyArg_Parser _parser = {
+        .keywords = _keywords,
+        .fname = "T",
+        .kwtuple = KWTUPLE,
+    };
+    #undef KWTUPLE
+    PyObject *argsbuf[1];
+    PyObject * const *fastargs;
+    Py_ssize_t nargs = PyTuple_GET_SIZE(args);
+    PyObject *a;
+
+    // Emit compiler warnings when we get to Python 3.14.
+    #if PY_VERSION_HEX >= 0x030e00C0
+    #  error \
+            "In clinic.test.c, update parameter(s) 'a' in the clinic input of" \
+            " 'm.T.__init__' to be keyword-only."
+    #elif PY_VERSION_HEX >= 0x030e00A0
+    #  ifdef _MSC_VER
+    #    pragma message ( \
+            "In clinic.test.c, update parameter(s) 'a' in the clinic input of" \
+            " 'm.T.__init__' to be keyword-only.")
+    #  else
+    #    warning \
+            "In clinic.test.c, update parameter(s) 'a' in the clinic input of" \
+            " 'm.T.__init__' to be keyword-only."
+    #  endif
+    #endif
+    if (nargs == 1) {
+        if (PyErr_WarnEx(PyExc_DeprecationWarning,
+                "Passing positional arguments to m.T() is deprecated. Parameter "
+                "'a' will become a keyword-only parameter in Python 3.14.", 1))
+        {
+                goto exit;
+        }
+    }
+    fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf);
+    if (!fastargs) {
+        goto exit;
+    }
+    a = fastargs[0];
+    return_value = m_T___init___impl((TestObj *)self, a);
+
+exit:
+    return return_value;
+}
+
+static int
+m_T___init___impl(TestObj *self, PyObject *a)
+/*[clinic end generated code: output=ef43c425816a549f input=f71b51dbe19fa657]*/
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
index 3490d4871b891..70b066cce82fa 100755
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -928,7 +928,7 @@ def deprecate_positional_use(
             if first_pos:
                 preamble = f"Passing {first_pos+1} positional arguments to "
             depr_message = preamble + (
-                f"{func.full_name}() is deprecated. Parameter {pstr} will "
+                f"{func.fulldisplayname}() is deprecated. Parameter {pstr} will "
                 f"become a keyword-only parameter in Python {major}.{minor}."
             )
         else:
@@ -939,7 +939,7 @@ def deprecate_positional_use(
                     f"argument{'s' if first_pos != 1 else ''} to "
                 )
             depr_message = preamble + (
-                f"{func.full_name}() is deprecated. Parameters {pstr} will "
+                f"{func.fulldisplayname}() is deprecated. Parameters {pstr} will "
                 f"become keyword-only parameters in Python {major}.{minor}."
             )
 
@@ -1673,14 +1673,7 @@ def render_function(
 
         full_name = f.full_name
         template_dict = {'full_name': full_name}
-
-        if new_or_init:
-            assert isinstance(f.cls, Class)
-            name = f.cls.name
-        else:
-            name = f.name
-
-        template_dict['name'] = name
+        template_dict['name'] = f.displayname
 
         if f.c_basename:
             c_basename = f.c_basename
@@ -2678,6 +2671,21 @@ def __post_init__(self) -> None:
         self.self_converter: self_converter | None = None
         self.__render_parameters__: list[Parameter] | None = None
 
+    @functools.cached_property
+    def displayname(self) -> str:
+        """Pretty-printable name."""
+        if self.kind.new_or_init:
+            assert isinstance(self.cls, Class)
+            return self.cls.name
+        else:
+            return self.name
+
+    @functools.cached_property
+    def fulldisplayname(self) -> str:
+        if isinstance(self.module, Module):
+            return f"{self.module.name}.{self.displayname}"
+        return self.displayname
+
     @property
     def render_parameters(self) -> list[Parameter]:
         if not self.__render_parameters__:
@@ -5522,13 +5530,7 @@ def format_docstring_signature(
         self, f: Function, parameters: list[Parameter]
     ) -> str:
         text, add, output = _text_accumulator()
-        if f.kind.new_or_init:
-            # classes get *just* the name of the class
-            # not __new__, not __init__, and not module.classname
-            assert f.cls
-            add(f.cls.name)
-        else:
-            add(f.name)
+        add(f.displayname)
         if self.forced_text_signature:
             add(self.forced_text_signature)
         else:



More information about the Python-checkins mailing list