]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-113317: Change how Argument Clinic lists converters (#116853)
authorVictor Stinner <vstinner@python.org>
Wed, 27 Mar 2024 22:10:14 +0000 (23:10 +0100)
committerGitHub <noreply@github.com>
Wed, 27 Mar 2024 22:10:14 +0000 (23:10 +0100)
* Add a new create_parser_namespace() function for
  PythonParser to pass objects to executed code.
* In run_clinic(), list converters using 'converters' and
  'return_converters' dictionarties.
* test_clinic: add 'object()' return converter.
* Use also create_parser_namespace() in eval_ast_expr().

Co-authored-by: Erlend E. Aasland <erlend@python.org>
Lib/test/test_clinic.py
Tools/clinic/clinic.py

index a60f087ef2816e4b9ea4d5423859ab9c167fd9a7..52cb4d6e18785542304e3b92c68b5d07e76efe4f 100644 (file)
@@ -2657,6 +2657,7 @@ class ClinicExternalTest(TestCase):
                 float()
                 int()
                 long()
+                object()
                 Py_ssize_t()
                 size_t()
                 unsigned_int()
index c9641cb9c82bf794a7cf88544298442dc440b70a..ea480e61ba9a2b16a8c2d30f3dbcaa56d163183d 100755 (executable)
@@ -56,7 +56,7 @@ from libclinic.language import Language, PythonLanguage
 from libclinic.block_parser import Block, BlockParser
 from libclinic.crenderdata import CRenderData, Include, TemplateDict
 from libclinic.converter import (
-    CConverter, CConverterClassT,
+    CConverter, CConverterClassT, ConverterType,
     converters, legacy_converters)
 
 
@@ -1988,13 +1988,38 @@ def parse_file(
     libclinic.write_file(output, cooked)
 
 
+@functools.cache
+def _create_parser_base_namespace() -> dict[str, Any]:
+    ns = dict(
+        CConverter=CConverter,
+        CReturnConverter=CReturnConverter,
+        buffer=buffer,
+        robuffer=robuffer,
+        rwbuffer=rwbuffer,
+        unspecified=unspecified,
+        NoneType=NoneType,
+    )
+    for name, converter in converters.items():
+        ns[f'{name}_converter'] = converter
+    for name, return_converter in return_converters.items():
+        ns[f'{name}_return_converter'] = return_converter
+    return ns
+
+
+def create_parser_namespace() -> dict[str, Any]:
+    base_namespace = _create_parser_base_namespace()
+    return base_namespace.copy()
+
+
+
 class PythonParser:
     def __init__(self, clinic: Clinic) -> None:
         pass
 
     def parse(self, block: Block) -> None:
+        namespace = create_parser_namespace()
         with contextlib.redirect_stdout(io.StringIO()) as s:
-            exec(block.input)
+            exec(block.input, namespace)
             block.output = s.getvalue()
 
 
@@ -3443,7 +3468,6 @@ class float_return_converter(double_return_converter):
 
 def eval_ast_expr(
         node: ast.expr,
-        globals: dict[str, Any],
         *,
         filename: str = '-'
 ) -> Any:
@@ -3460,8 +3484,9 @@ def eval_ast_expr(
         node = node.value
 
     expr = ast.Expression(node)
+    namespace = create_parser_namespace()
     co = compile(expr, filename, 'eval')
-    fn = FunctionType(co, globals)
+    fn = FunctionType(co, namespace)
     return fn()
 
 
@@ -4463,12 +4488,11 @@ class DSLParser:
             case ast.Name(name):
                 return name, False, {}
             case ast.Call(func=ast.Name(name)):
-                symbols = globals()
                 kwargs: ConverterArgs = {}
                 for node in annotation.keywords:
                     if not isinstance(node.arg, str):
                         fail("Cannot use a kwarg splat in a function-call annotation")
-                    kwargs[node.arg] = eval_ast_expr(node.value, symbols)
+                    kwargs[node.arg] = eval_ast_expr(node.value)
                 return name, False, kwargs
             case _:
                 fail(
@@ -4984,25 +5008,21 @@ def run_clinic(parser: argparse.ArgumentParser, ns: argparse.Namespace) -> None:
             parser.error(
                 "can't specify --converters and a filename at the same time"
             )
-        converters: list[tuple[str, str]] = []
-        return_converters: list[tuple[str, str]] = []
-        ignored = set("""
-            add_c_converter
-            add_c_return_converter
-            add_default_legacy_c_converter
-            add_legacy_c_converter
-            """.strip().split())
-        module = globals()
-        for name in module:
-            for suffix, ids in (
-                ("_return_converter", return_converters),
-                ("_converter", converters),
-            ):
-                if name in ignored:
-                    continue
-                if name.endswith(suffix):
-                    ids.append((name, name.removesuffix(suffix)))
-                    break
+        AnyConverterType = ConverterType | ReturnConverterType
+        converter_list: list[tuple[str, AnyConverterType]] = []
+        return_converter_list: list[tuple[str, AnyConverterType]] = []
+
+        for name, converter in converters.items():
+            converter_list.append((
+                name,
+                converter,
+            ))
+        for name, return_converter in return_converters.items():
+            return_converter_list.append((
+                name,
+                return_converter
+            ))
+
         print()
 
         print("Legacy converters:")
@@ -5012,15 +5032,17 @@ def run_clinic(parser: argparse.ArgumentParser, ns: argparse.Namespace) -> None:
         print()
 
         for title, attribute, ids in (
-            ("Converters", 'converter_init', converters),
-            ("Return converters", 'return_converter_init', return_converters),
+            ("Converters", 'converter_init', converter_list),
+            ("Return converters", 'return_converter_init', return_converter_list),
         ):
             print(title + ":")
+
+            ids.sort(key=lambda item: item[0].lower())
             longest = -1
-            for name, short_name in ids:
-                longest = max(longest, len(short_name))
-            for name, short_name in sorted(ids, key=lambda x: x[1].lower()):
-                cls = module[name]
+            for name, _ in ids:
+                longest = max(longest, len(name))
+
+            for name, cls in ids:
                 callable = getattr(cls, attribute, None)
                 if not callable:
                     continue
@@ -5033,7 +5055,7 @@ def run_clinic(parser: argparse.ArgumentParser, ns: argparse.Namespace) -> None:
                         else:
                             s = parameter_name
                         parameters.append(s)
-                print('    {}({})'.format(short_name, ', '.join(parameters)))
+                print('    {}({})'.format(name, ', '.join(parameters)))
             print()
         print("All converters also accept (c_default=None, py_default=None, annotation=None).")
         print("All return converters also accept (py_default=None).")