Skip to content

Commit

Permalink
gh-100039: enhance __signature__ to work with str and callables (GH-1…
Browse files Browse the repository at this point in the history
…00168)

Callables should be either class- or static-methods.
Enum now uses the classmethod version to greatly improve the help
given for enums and flags.
  • Loading branch information
ethanfurman committed Dec 16, 2022
1 parent 5234e1c commit a5a7cea
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 4 deletions.
9 changes: 8 additions & 1 deletion Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
'member order does not match _order_:\n %r\n %r'
% (enum_class._member_names_, _order_)
)

#
return enum_class

def __bool__(cls):
Expand Down Expand Up @@ -1083,6 +1083,13 @@ class Enum(metaclass=EnumType):
attributes -- see the documentation for details.
"""

@classmethod
def __signature__(cls):
if cls._member_names_:
return '(*values)'
else:
return '(new_class_name, /, names, *, module=None, qualname=None, type=None, start=1, boundary=None)'

def __new__(cls, value):
# all enum instances are actually created during class construction
# without calling this method; this method is called by the metaclass'
Expand Down
10 changes: 9 additions & 1 deletion Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2443,10 +2443,18 @@ def _signature_from_callable(obj, *,
pass
else:
if sig is not None:
# since __text_signature__ is not writable on classes, __signature__
# may contain text (or be a callable that returns text);
# if so, convert it
o_sig = sig
if not isinstance(sig, (Signature, str)) and callable(sig):
sig = sig()
if isinstance(sig, str):
sig = _signature_fromstr(sigcls, obj, sig)
if not isinstance(sig, Signature):
raise TypeError(
'unexpected object {!r} in __signature__ '
'attribute'.format(sig))
'attribute'.format(o_sig))
return sig

try:
Expand Down
25 changes: 23 additions & 2 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -4259,7 +4259,7 @@ class TestEnumTypeSubclassing(unittest.TestCase):
Help on class Color in module %s:
class Color(enum.Enum)
| Color(value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None)
| Color(*values)
|
| Method resolution order:
| Color
Expand Down Expand Up @@ -4315,7 +4315,7 @@ class Color(enum.Enum)
Help on class Color in module %s:
class Color(enum.Enum)
| Color(value, names=None, *values, module=None, qualname=None, type=None, start=1)
| Color(*values)
|
| Method resolution order:
| Color
Expand Down Expand Up @@ -4462,6 +4462,27 @@ def test_inspect_classify_class_attrs(self):
if failed:
self.fail("result does not equal expected, see print above")

def test_inspect_signatures(self):
from inspect import signature, Signature, Parameter
self.assertEqual(
signature(Enum),
Signature([
Parameter('new_class_name', Parameter.POSITIONAL_ONLY),
Parameter('names', Parameter.POSITIONAL_OR_KEYWORD),
Parameter('module', Parameter.KEYWORD_ONLY, default=None),
Parameter('qualname', Parameter.KEYWORD_ONLY, default=None),
Parameter('type', Parameter.KEYWORD_ONLY, default=None),
Parameter('start', Parameter.KEYWORD_ONLY, default=1),
Parameter('boundary', Parameter.KEYWORD_ONLY, default=None),
]),
)
self.assertEqual(
signature(enum.FlagBoundary),
Signature([
Parameter('values', Parameter.VAR_POSITIONAL),
]),
)

def test_test_simple_enum(self):
@_simple_enum(Enum)
class SimpleColor:
Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3614,6 +3614,38 @@ def foo(): pass
self.assertEqual(signature_func(foo), inspect.Signature())
self.assertEqual(inspect.get_annotations(foo), {})

def test_signature_as_str(self):
self.maxDiff = None
class S:
__signature__ = '(a, b=2)'

self.assertEqual(self.signature(S),
((('a', ..., ..., 'positional_or_keyword'),
('b', 2, ..., 'positional_or_keyword')),
...))

def test_signature_as_callable(self):
# __signature__ should be either a staticmethod or a bound classmethod
class S:
@classmethod
def __signature__(cls):
return '(a, b=2)'

self.assertEqual(self.signature(S),
((('a', ..., ..., 'positional_or_keyword'),
('b', 2, ..., 'positional_or_keyword')),
...))

class S:
@staticmethod
def __signature__():
return '(a, b=2)'

self.assertEqual(self.signature(S),
((('a', ..., ..., 'positional_or_keyword'),
('b', 2, ..., 'positional_or_keyword')),
...))


class TestParameterObject(unittest.TestCase):
def test_signature_parameter_kinds(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve signatures for enums and flags.

0 comments on commit a5a7cea

Please sign in to comment.