Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion i2/tests/test_partials_and_methodize.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Tests for partialx(_rm_partialize), curried, methodize(default_from) and the
docstring parser fixes of i2mint/i2#111, #112, #113 and #116."""
docstring parser fixes of i2mint/i2#111, #112, #113, #116 and #118."""

import asyncio
import inspect
Expand Down Expand Up @@ -82,6 +82,27 @@ class K:
assert (k.bar(3), k.bar(3, 10), k.bar(3, z=0)) == (7, 31, 1)


def test_methodize_keeps_self_before_position_only_params():
"""i2mint/i2#118: ``self`` used to land after the ``/``."""

def foo(a, /, b, *, c=3):
return (a, b, c)

@dataclass
class K:
a: int = 1
b: int = 7
bound = methodize(foo, bind="b")
defaulted = methodize(foo, default_from="b")

assert str(inspect.signature(K.bound)) == "(self, a, /, *, c=3)"
assert str(inspect.signature(K.defaulted)) == "(self, a, /, b=self.b, *, c=3)"
k = K()
assert k.bound(5) == (5, 7, 3)
assert (k.defaulted(5), k.defaulted(5, 0)) == ((5, 7, 3), (5, 0, 3))
assert methodize(foo, bind="a")(k, 2) == (1, 2, 3)


def test_methodize_default_from_rejects_variadic_and_unknown_params():
def foo(x, *args, **kwargs):
return x
Expand Down
22 changes: 21 additions & 1 deletion i2/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2042,7 +2042,7 @@ def func_to_method_func(
method_sig = _with_instance_attr_defaults(
method_sig, default_from, instance_arg_name=instance_arg_name
)
method_sig = instance_arg_name + method_sig
method_sig = _prepend_instance_param(method_sig, instance_arg_name)
# make the ingress function that will map method_sig's interface to sig's.
ingress = Ingress(
inner_sig=sig,
Expand All @@ -2062,6 +2062,26 @@ def func_to_method_func(
return method_func


def _prepend_instance_param(sig: Sig, instance_arg_name: str = "self") -> Sig:
"""Put the instance param first in ``sig``.

If ``sig`` has position-only params, the instance param is position-only too, so
it stays before the ``/`` (i2mint/i2#118).

>>> def f(a, /, b): ...
>>> _prepend_instance_param(Sig(f))
<Sig (self, a, /, b)>
>>> _prepend_instance_param(Sig(lambda b, *, c: 0))
<Sig (self, b, *, c)>
"""
if PO not in sig.kinds.values():
return instance_arg_name + sig
return Sig(
[Parameter(instance_arg_name, PO), *sig.params],
return_annotation=sig.return_annotation,
)


def _with_instance_attr_defaults(
sig: Sig, default_from: dict, *, instance_arg_name: str = "self"
) -> Sig:
Expand Down
Loading