@@ -1124,3 +1124,63 @@ into an ordinary helper method that the hook calls, or into a
11241124through normal attribute access and therefore honour decorators applied
11251125via ``@wrapt.decorator ``.
11261126
1127+
1128+
1129+ Signature of PartialCallableObjectProxy over a bound method
1130+ -----------------------------------------------------------
1131+
1132+ ``inspect.signature() `` applied to a ``PartialCallableObjectProxy ``, or to
1133+ the result of ``wrapt.partial() ``, reports the signature of the wrapped
1134+ callable with the bound positional and keyword arguments removed, in the
1135+ same way as it does for ``functools.partial ``. There is one case where
1136+ this does not hold, which is when the callable being wrapped is itself an
1137+ already bound method.
1138+
1139+ ::
1140+
1141+ import inspect
1142+ import functools
1143+ import wrapt
1144+
1145+ class Database:
1146+ def query(self, sql, *args, timeout=None):
1147+ pass
1148+
1149+ db = Database()
1150+
1151+ inspect.signature(functools.partial(db.query, "SELECT 1"))
1152+ # <Signature (*args, timeout=None)>
1153+
1154+ inspect.signature(wrapt.partial(db.query, "SELECT 1"))
1155+ # <Signature (sql, *args, timeout=None)>
1156+
1157+ For the ``wrapt `` proxy the parameter filled by the bound argument is
1158+ still present. This happens because ``inspect.signature() `` first checks
1159+ whether the object it was given is an instance of ``types.MethodType ``,
1160+ before it looks for a ``__signature__ `` attribute. An object proxy
1161+ reports the class of the object it wraps, so for a proxy around a bound
1162+ method that check succeeds. ``inspect `` then reads the ``__func__ ``
1163+ attribute, which the proxy also forwards to the bound method, takes the
1164+ signature of the underlying function, and removes only its first
1165+ parameter. The ``__signature__ `` attribute of the proxy, which would
1166+ give the correct result, is never consulted.
1167+
1168+ This cannot be corrected within ``wrapt ``. Not reporting the class of the
1169+ wrapped object would break the fundamental contract of an object proxy,
1170+ and returning a different object from ``__func__ `` would mislead the
1171+ many other consumers of that attribute in order to satisfy one. The
1172+ appropriate fix is for ``inspect `` to consult ``__signature__ `` before
1173+ relying on the class of the object.
1174+
1175+ Partials over a plain function are unaffected, including the partial
1176+ created by ``FunctionWrapper `` when a wrapped method is called via the
1177+ class with the instance passed explicitly, since in that case the
1178+ wrapped callable is the plain function and the instance is one of the
1179+ bound arguments. Where the signature of a partial over a bound method is
1180+ needed, pass the underlying function and the instance instead of the
1181+ bound method.
1182+
1183+ ::
1184+
1185+ inspect.signature(wrapt.partial(Database.query, db, "SELECT 1"))
1186+ # <Signature (*args, timeout=None)>
0 commit comments