77from __future__ import annotations
88
99import argparse
10- import ast
1110import collections .abc
1211import copy
1312import enum
1413import functools
1514import importlib
1615import importlib .machinery
1716import inspect
18- import itertools
1917import os
2018import pkgutil
2119import re
@@ -1528,178 +1526,6 @@ def is_read_only_property(runtime: object) -> bool:
15281526 return isinstance (runtime , property ) and runtime .fset is None
15291527
15301528
1531- def _signature_fromstr (
1532- cls : type [inspect .Signature ], obj : Any , s : str , skip_bound_arg : bool = True
1533- ) -> inspect .Signature :
1534- """Private helper to parse content of '__text_signature__'
1535- and return a Signature based on it.
1536-
1537- This is a copy of inspect._signature_fromstr from 3.13, which we need
1538- for python/cpython#115270, an important fix for working with
1539- built-in instance methods.
1540- """
1541- Parameter = cls ._parameter_cls # type: ignore[attr-defined]
1542-
1543- if sys .version_info >= (3 , 12 ):
1544- clean_signature , self_parameter = inspect ._signature_strip_non_python_syntax (s ) # type: ignore[attr-defined]
1545- else :
1546- clean_signature , self_parameter , last_positional_only = inspect ._signature_strip_non_python_syntax (s ) # type: ignore[attr-defined]
1547-
1548- program = "def foo" + clean_signature + ": pass"
1549-
1550- try :
1551- module_ast = ast .parse (program )
1552- except SyntaxError :
1553- module_ast = None
1554-
1555- if not isinstance (module_ast , ast .Module ):
1556- raise ValueError ("{!r} builtin has invalid signature" .format (obj ))
1557-
1558- f = module_ast .body [0 ]
1559- assert isinstance (f , ast .FunctionDef )
1560-
1561- parameters = []
1562- empty = Parameter .empty
1563-
1564- module = None
1565- module_dict : dict [str , Any ] = {}
1566-
1567- module_name = getattr (obj , "__module__" , None )
1568- if not module_name :
1569- objclass = getattr (obj , "__objclass__" , None )
1570- module_name = getattr (objclass , "__module__" , None )
1571-
1572- if module_name :
1573- module = sys .modules .get (module_name , None )
1574- if module :
1575- module_dict = module .__dict__
1576- sys_module_dict = sys .modules .copy ()
1577-
1578- def parse_name (node : ast .arg ) -> str :
1579- assert isinstance (node , ast .arg )
1580- if node .annotation is not None :
1581- raise ValueError ("Annotations are not currently supported" )
1582- return node .arg
1583-
1584- def wrap_value (s : str ) -> ast .Constant :
1585- try :
1586- value = eval (s , module_dict )
1587- except NameError :
1588- try :
1589- value = eval (s , sys_module_dict )
1590- except NameError as err :
1591- raise ValueError from err
1592-
1593- if isinstance (value , (str , int , float , bytes , bool , type (None ))):
1594- return ast .Constant (value )
1595- raise ValueError
1596-
1597- class RewriteSymbolics (ast .NodeTransformer ):
1598- def visit_Attribute (self , node : ast .Attribute ) -> Any : # noqa: N802
1599- a = []
1600- n : ast .expr = node
1601- while isinstance (n , ast .Attribute ):
1602- a .append (n .attr )
1603- n = n .value
1604- if not isinstance (n , ast .Name ):
1605- raise ValueError
1606- a .append (n .id )
1607- value = "." .join (reversed (a ))
1608- return wrap_value (value )
1609-
1610- def visit_Name (self , node : ast .Name ) -> Any : # noqa: N802
1611- if not isinstance (node .ctx , ast .Load ):
1612- raise ValueError ()
1613- return wrap_value (node .id )
1614-
1615- def visit_BinOp (self , node : ast .BinOp ) -> Any : # noqa: N802
1616- # Support constant folding of a couple simple binary operations
1617- # commonly used to define default values in text signatures
1618- left = self .visit (node .left )
1619- right = self .visit (node .right )
1620- if not isinstance (left , ast .Constant ) or not isinstance (right , ast .Constant ):
1621- raise ValueError
1622- if isinstance (node .op , ast .Add ):
1623- return ast .Constant (left .value + right .value )
1624- elif isinstance (node .op , ast .Sub ):
1625- return ast .Constant (left .value - right .value )
1626- elif isinstance (node .op , ast .BitOr ):
1627- return ast .Constant (left .value | right .value )
1628- raise ValueError
1629-
1630- def p (name_node : ast .arg , default_node : Any , default : Any = empty ) -> None :
1631- name = parse_name (name_node )
1632- if default_node and default_node is not inspect ._empty :
1633- try :
1634- default_node = RewriteSymbolics ().visit (default_node )
1635- default = ast .literal_eval (default_node )
1636- except ValueError :
1637- raise ValueError ("{!r} builtin has invalid signature" .format (obj )) from None
1638- parameters .append (Parameter (name , kind , default = default , annotation = empty ))
1639-
1640- # non-keyword-only parameters
1641- if sys .version_info >= (3 , 12 ):
1642- total_non_kw_args = len (f .args .posonlyargs ) + len (f .args .args )
1643- required_non_kw_args = total_non_kw_args - len (f .args .defaults )
1644- defaults = itertools .chain (itertools .repeat (None , required_non_kw_args ), f .args .defaults )
1645-
1646- kind = Parameter .POSITIONAL_ONLY
1647- for name , default in zip (f .args .posonlyargs , defaults ):
1648- p (name , default )
1649-
1650- kind = Parameter .POSITIONAL_OR_KEYWORD
1651- for name , default in zip (f .args .args , defaults ):
1652- p (name , default )
1653-
1654- else :
1655- args = reversed (f .args .args )
1656- defaults = reversed (f .args .defaults )
1657- iter = itertools .zip_longest (args , defaults , fillvalue = None )
1658- if last_positional_only is not None :
1659- kind = Parameter .POSITIONAL_ONLY
1660- else :
1661- kind = Parameter .POSITIONAL_OR_KEYWORD
1662- for i , (name , default ) in enumerate (reversed (list (iter ))):
1663- assert name is not None
1664- p (name , default )
1665- if i == last_positional_only :
1666- kind = Parameter .POSITIONAL_OR_KEYWORD
1667-
1668- # *args
1669- if f .args .vararg :
1670- kind = Parameter .VAR_POSITIONAL
1671- p (f .args .vararg , empty )
1672-
1673- # keyword-only arguments
1674- kind = Parameter .KEYWORD_ONLY
1675- for name , default in zip (f .args .kwonlyargs , f .args .kw_defaults ):
1676- p (name , default )
1677-
1678- # **kwargs
1679- if f .args .kwarg :
1680- kind = Parameter .VAR_KEYWORD
1681- p (f .args .kwarg , empty )
1682-
1683- if self_parameter is not None :
1684- # Possibly strip the bound argument:
1685- # - We *always* strip first bound argument if
1686- # it is a module.
1687- # - We don't strip first bound argument if
1688- # skip_bound_arg is False.
1689- assert parameters
1690- _self = getattr (obj , "__self__" , None )
1691- self_isbound = _self is not None
1692- self_ismodule = inspect .ismodule (_self )
1693- if self_isbound and (self_ismodule or skip_bound_arg ):
1694- parameters .pop (0 )
1695- else :
1696- # for builtins, self parameter is always positional-only!
1697- p = parameters [0 ].replace (kind = Parameter .POSITIONAL_ONLY )
1698- parameters [0 ] = p
1699-
1700- return cls (parameters , return_annotation = cls .empty )
1701-
1702-
17031529def safe_inspect_signature (runtime : Any ) -> inspect .Signature | None :
17041530 if (
17051531 hasattr (runtime , "__name__" )
@@ -1713,15 +1539,22 @@ def safe_inspect_signature(runtime: Any) -> inspect.Signature | None:
17131539 ):
17141540 # This is an __init__ method with the generic C-class signature.
17151541 # In this case, the underlying class usually has a better signature,
1716- # which we can convert into an __init__ signature by adding $self
1717- # at the start. If we hit an error, failover to the normal
1718- # path without trying to recover.
1719- if "/" in runtime .__objclass__ .__text_signature__ :
1720- new_sig = f"($self, { runtime .__objclass__ .__text_signature__ [1 :]} "
1721- else :
1722- new_sig = f"($self, /, { runtime .__objclass__ .__text_signature__ [1 :]} "
1542+ # which we can convert into an __init__ signature by adding in the
1543+ # self parameter.
1544+
1545+ # TODO: Handle classes who get __init__ from object, but would have
1546+ # a better signature on the actual objclass if we had access to it
1547+ # here. This would probably require a second parameter on
1548+ # safe_inspect_signature to pass in the original class that this
1549+ # runtime method object was collected from?
17231550 try :
1724- return _signature_fromstr (inspect .Signature , runtime , new_sig )
1551+ s = inspect .signature (runtime .__objclass__ )
1552+ return s .replace (
1553+ parameters = [
1554+ inspect .Parameter ("self" , inspect .Parameter .POSITIONAL_OR_KEYWORD ),
1555+ * s .parameters .values (),
1556+ ]
1557+ )
17251558 except Exception :
17261559 pass
17271560
0 commit comments