From 198e2023d4eca0d2ac4c488ae4a60dadfe0e3ba9 Mon Sep 17 00:00:00 2001 From: Krishna Anubhav Date: Wed, 7 Oct 2026 00:50:05 +0530 Subject: [PATCH] Don't fail compose signature when last function has no signature Compose.__signature__ only needs the last function for the return annotation, but called inspect.signature on it unguarded. For builtins like max or set this raised ValueError, so inspect.signature(compose(max, f)) failed even though f has a normal signature. Fall back to no return annotation in that case. --- toolz/functoolz.py | 8 +++++++- toolz/tests/test_functoolz.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/toolz/functoolz.py b/toolz/functoolz.py index b4cacac4..a3cb73c8 100644 --- a/toolz/functoolz.py +++ b/toolz/functoolz.py @@ -568,7 +568,13 @@ def __get__(self, obj, objtype=None): @instanceproperty def __signature__(self): base = inspect.signature(self.first) - last = inspect.signature(self.funcs[-1]) + try: + last = inspect.signature(self.funcs[-1]) + except (TypeError, ValueError): + # The last function is only consulted for its return annotation, + # so a callable without an introspectable signature (e.g. + # ``max``) should not make the whole composition fail. + return base.replace(return_annotation=inspect.Signature.empty) return base.replace(return_annotation=last.return_annotation) __wrapped__ = instanceproperty(attrgetter('first')) diff --git a/toolz/tests/test_functoolz.py b/toolz/tests/test_functoolz.py index d35e0de2..19caa0d3 100644 --- a/toolz/tests/test_functoolz.py +++ b/toolz/tests/test_functoolz.py @@ -664,6 +664,17 @@ class MyClass: assert len(inspect.signature(MyClass().method).parameters) == 4 +def test_compose_signature_last_func_without_signature(): + # ``max`` has no introspectable signature, but it is only used for the + # return annotation of the composition, so it must not break inspection. + def first(a: int, b: int) -> int: + return a + b + + sig = inspect.signature(compose(max, first)) + assert list(sig.parameters) == ['a', 'b'] + assert sig.return_annotation is inspect.Signature.empty + + def test_compose_annotations(): # Parameter annotations come from the first function applied and the # return annotation from the last one: the same view as __signature__.