test_kwargs_and_defaults.py 12.7 KB
Newer Older
1
# -*- coding: utf-8 -*-
Dean Moldovan's avatar
Dean Moldovan committed
2
import pytest
3
4

import env  # noqa: F401
5
from pybind11_tests import kwargs_and_defaults as m
Dean Moldovan's avatar
Dean Moldovan committed
6
7
8


def test_function_signatures(doc):
9
10
    assert doc(m.kw_func0) == "kw_func0(arg0: int, arg1: int) -> str"
    assert doc(m.kw_func1) == "kw_func1(x: int, y: int) -> str"
11
12
13
14
15
    assert doc(m.kw_func2) == "kw_func2(x: int = 100, y: int = 200) -> str"
    assert doc(m.kw_func3) == "kw_func3(data: str = 'Hello world!') -> None"
    assert doc(m.kw_func4) == "kw_func4(myList: List[int] = [13, 17]) -> str"
    assert doc(m.kw_func_udl) == "kw_func_udl(x: int, y: int = 300) -> str"
    assert doc(m.kw_func_udl_z) == "kw_func_udl_z(x: int, y: int = 0) -> str"
16
    assert doc(m.args_function) == "args_function(*args) -> tuple"
17
18
19
20
21
22
23
24
25
26
27
    assert (
        doc(m.args_kwargs_function) == "args_kwargs_function(*args, **kwargs) -> tuple"
    )
    assert (
        doc(m.KWClass.foo0)
        == "foo0(self: m.kwargs_and_defaults.KWClass, arg0: int, arg1: float) -> None"
    )
    assert (
        doc(m.KWClass.foo1)
        == "foo1(self: m.kwargs_and_defaults.KWClass, x: int, y: float) -> None"
    )
Dean Moldovan's avatar
Dean Moldovan committed
28
29


30
def test_named_arguments(msg):
31
    assert m.kw_func0(5, 10) == "x=5, y=10"
Dean Moldovan's avatar
Dean Moldovan committed
32

33
34
35
    assert m.kw_func1(5, 10) == "x=5, y=10"
    assert m.kw_func1(5, y=10) == "x=5, y=10"
    assert m.kw_func1(y=10, x=5) == "x=5, y=10"
36

37
38
39
40
41
42
    assert m.kw_func2() == "x=100, y=200"
    assert m.kw_func2(5) == "x=5, y=200"
    assert m.kw_func2(x=5) == "x=5, y=200"
    assert m.kw_func2(y=10) == "x=100, y=10"
    assert m.kw_func2(5, 10) == "x=5, y=10"
    assert m.kw_func2(x=5, y=10) == "x=5, y=10"
Dean Moldovan's avatar
Dean Moldovan committed
43
44
45

    with pytest.raises(TypeError) as excinfo:
        # noinspection PyArgumentList
46
        m.kw_func2(x=5, y=10, z=12)
47
    assert excinfo.match(
48
49
50
        r"(?s)^kw_func2\(\): incompatible.*Invoked with: kwargs: ((x=5|y=10|z=12)(, |$))"
        + "{3}$"
    )
Dean Moldovan's avatar
Dean Moldovan committed
51

52
53
    assert m.kw_func4() == "{13 17}"
    assert m.kw_func4(myList=[1, 2, 3]) == "{1 2 3}"
Dean Moldovan's avatar
Dean Moldovan committed
54

55
56
    assert m.kw_func_udl(x=5, y=10) == "x=5, y=10"
    assert m.kw_func_udl_z(x=5) == "x=5, y=0"
Dean Moldovan's avatar
Dean Moldovan committed
57
58


59
def test_arg_and_kwargs():
60
    args = "arg1_value", "arg2_value", 3
61
    assert m.args_function(*args) == args
62

63
64
    args = "a1", "a2"
    kwargs = dict(arg3="a3", arg4=4)
65
    assert m.args_kwargs_function(*args, **kwargs) == (args, kwargs)
66
67
68


def test_mixed_args_and_kwargs(msg):
69
70
71
72
    mpa = m.mixed_plus_args
    mpk = m.mixed_plus_kwargs
    mpak = m.mixed_plus_args_kwargs
    mpakd = m.mixed_plus_args_kwargs_defaults
73
74
75
76
77

    assert mpa(1, 2.5, 4, 99.5, None) == (1, 2.5, (4, 99.5, None))
    assert mpa(1, 2.5) == (1, 2.5, ())
    with pytest.raises(TypeError) as excinfo:
        assert mpa(1)
78
79
80
    assert (
        msg(excinfo.value)
        == """
81
82
83
84
        mixed_plus_args(): incompatible function arguments. The following argument types are supported:
            1. (arg0: int, arg1: float, *args) -> tuple

        Invoked with: 1
85
    """  # noqa: E501 line too long
86
    )
87
88
    with pytest.raises(TypeError) as excinfo:
        assert mpa()
89
90
91
    assert (
        msg(excinfo.value)
        == """
92
93
94
95
        mixed_plus_args(): incompatible function arguments. The following argument types are supported:
            1. (arg0: int, arg1: float, *args) -> tuple

        Invoked with:
96
    """  # noqa: E501 line too long
97
    )
98

99
100
101
102
103
    assert mpk(-2, 3.5, pi=3.14159, e=2.71828) == (
        -2,
        3.5,
        {"e": 2.71828, "pi": 3.14159},
    )
104
    assert mpak(7, 7.7, 7.77, 7.777, 7.7777, minusseven=-7) == (
105
106
107
108
109
        7,
        7.7,
        (7.77, 7.777, 7.7777),
        {"minusseven": -7},
    )
110
111
112
    assert mpakd() == (1, 3.14159, (), {})
    assert mpakd(3) == (3, 3.14159, (), {})
    assert mpakd(j=2.71828) == (1, 2.71828, (), {})
113
    assert mpakd(k=42) == (1, 3.14159, (), {"k": 42})
114
    assert mpakd(1, 1, 2, 3, 5, 8, then=13, followedby=21) == (
115
116
117
118
119
        1,
        1,
        (2, 3, 5, 8),
        {"then": 13, "followedby": 21},
    )
120
    # Arguments specified both positionally and via kwargs should fail:
121
122
    with pytest.raises(TypeError) as excinfo:
        assert mpakd(1, i=1)
123
124
125
    assert (
        msg(excinfo.value)
        == """
126
        mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
127
            1. (i: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple
128
129
130

        Invoked with: 1; kwargs: i=1
    """  # noqa: E501 line too long
131
    )
132
133
    with pytest.raises(TypeError) as excinfo:
        assert mpakd(1, 2, j=1)
134
135
136
    assert (
        msg(excinfo.value)
        == """
137
        mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
138
            1. (i: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple
139
140
141

        Invoked with: 1, 2; kwargs: j=1
    """  # noqa: E501 line too long
142
    )
143

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
    # Arguments after a py::args are automatically keyword-only (pybind 2.9+)
    assert m.args_kwonly(2, 2.5, z=22) == (2, 2.5, (), 22)
    assert m.args_kwonly(2, 2.5, "a", "b", "c", z=22) == (2, 2.5, ("a", "b", "c"), 22)
    assert m.args_kwonly(z=22, i=4, j=16) == (4, 16, (), 22)

    with pytest.raises(TypeError) as excinfo:
        assert m.args_kwonly(2, 2.5, 22)  # missing z= keyword
    assert (
        msg(excinfo.value)
        == """
        args_kwonly(): incompatible function arguments. The following argument types are supported:
            1. (i: int, j: float, *args, z: int) -> tuple

        Invoked with: 2, 2.5, 22
    """
    )

    assert m.args_kwonly_kwargs(i=1, k=4, j=10, z=-1, y=9) == (
        1,
        10,
        (),
        -1,
        {"k": 4, "y": 9},
    )
    assert m.args_kwonly_kwargs(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, z=11, y=12) == (
        1,
        2,
        (3, 4, 5, 6, 7, 8, 9, 10),
        11,
        {"y": 12},
    )
    assert (
        m.args_kwonly_kwargs.__doc__
        == "args_kwonly_kwargs(i: int, j: float, *args, z: int, **kwargs) -> tuple\n"
    )

    assert (
        m.args_kwonly_kwargs_defaults.__doc__
        == "args_kwonly_kwargs_defaults(i: int = 1, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n"  # noqa: E501 line too long
    )
    assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {})
    assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {})
    assert m.args_kwonly_kwargs_defaults(z=-99) == (1, 3.14159, (), -99, {})
    assert m.args_kwonly_kwargs_defaults(5, 6, 7, 8) == (5, 6, (7, 8), 42, {})
    assert m.args_kwonly_kwargs_defaults(5, 6, 7, m=8) == (5, 6, (7,), 42, {"m": 8})
    assert m.args_kwonly_kwargs_defaults(5, 6, 7, m=8, z=9) == (5, 6, (7,), 9, {"m": 8})

191

192
def test_keyword_only_args(msg):
Henry Schreiner's avatar
Henry Schreiner committed
193
194
    assert m.kw_only_all(i=1, j=2) == (1, 2)
    assert m.kw_only_all(j=1, i=2) == (2, 1)
195
196

    with pytest.raises(TypeError) as excinfo:
Henry Schreiner's avatar
Henry Schreiner committed
197
        assert m.kw_only_all(i=1) == (1,)
198
199
200
    assert "incompatible function arguments" in str(excinfo.value)

    with pytest.raises(TypeError) as excinfo:
Henry Schreiner's avatar
Henry Schreiner committed
201
        assert m.kw_only_all(1, 2) == (1, 2)
202
203
    assert "incompatible function arguments" in str(excinfo.value)

Henry Schreiner's avatar
Henry Schreiner committed
204
    assert m.kw_only_some(1, k=3, j=2) == (1, 2, 3)
205

Henry Schreiner's avatar
Henry Schreiner committed
206
207
208
209
    assert m.kw_only_with_defaults(z=8) == (3, 4, 5, 8)
    assert m.kw_only_with_defaults(2, z=8) == (2, 4, 5, 8)
    assert m.kw_only_with_defaults(2, j=7, k=8, z=9) == (2, 7, 8, 9)
    assert m.kw_only_with_defaults(2, 7, z=9, k=8) == (2, 7, 8, 9)
210

Henry Schreiner's avatar
Henry Schreiner committed
211
212
213
    assert m.kw_only_mixed(1, j=2) == (1, 2)
    assert m.kw_only_mixed(j=2, i=3) == (3, 2)
    assert m.kw_only_mixed(i=2, j=3) == (2, 3)
214

215
216
217
    assert m.kw_only_plus_more(4, 5, k=6, extra=7) == (4, 5, 6, {"extra": 7})
    assert m.kw_only_plus_more(3, k=5, j=4, extra=6) == (3, 4, 5, {"extra": 6})
    assert m.kw_only_plus_more(2, k=3, extra=4) == (2, -1, 3, {"extra": 4})
218
219

    with pytest.raises(TypeError) as excinfo:
Henry Schreiner's avatar
Henry Schreiner committed
220
        assert m.kw_only_mixed(i=1) == (1,)
221
222
223
    assert "incompatible function arguments" in str(excinfo.value)

    with pytest.raises(RuntimeError) as excinfo:
Henry Schreiner's avatar
Henry Schreiner committed
224
        m.register_invalid_kw_only(m)
225
226
227
    assert (
        msg(excinfo.value)
        == """
228
        arg(): cannot specify an unnamed argument after a kw_only() annotation or args() argument
229
    """
230
    )
231
232


Henry Schreiner's avatar
Henry Schreiner committed
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def test_positional_only_args(msg):
    assert m.pos_only_all(1, 2) == (1, 2)
    assert m.pos_only_all(2, 1) == (2, 1)

    with pytest.raises(TypeError) as excinfo:
        m.pos_only_all(i=1, j=2)
    assert "incompatible function arguments" in str(excinfo.value)

    assert m.pos_only_mix(1, 2) == (1, 2)
    assert m.pos_only_mix(2, j=1) == (2, 1)

    with pytest.raises(TypeError) as excinfo:
        m.pos_only_mix(i=1, j=2)
    assert "incompatible function arguments" in str(excinfo.value)

    assert m.pos_kw_only_mix(1, 2, k=3) == (1, 2, 3)
    assert m.pos_kw_only_mix(1, j=2, k=3) == (1, 2, 3)

    with pytest.raises(TypeError) as excinfo:
        m.pos_kw_only_mix(i=1, j=2, k=3)
    assert "incompatible function arguments" in str(excinfo.value)

    with pytest.raises(TypeError) as excinfo:
        m.pos_kw_only_mix(1, 2, 3)
    assert "incompatible function arguments" in str(excinfo.value)

    with pytest.raises(TypeError) as excinfo:
        m.pos_only_def_mix()
    assert "incompatible function arguments" in str(excinfo.value)

    assert m.pos_only_def_mix(1) == (1, 2, 3)
    assert m.pos_only_def_mix(1, 4) == (1, 4, 3)
    assert m.pos_only_def_mix(1, 4, 7) == (1, 4, 7)
    assert m.pos_only_def_mix(1, 4, k=7) == (1, 4, 7)

    with pytest.raises(TypeError) as excinfo:
        m.pos_only_def_mix(1, j=4)
    assert "incompatible function arguments" in str(excinfo.value)

272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
    # Mix it with args and kwargs:
    assert (
        m.args_kwonly_full_monty.__doc__
        == "args_kwonly_full_monty(arg0: int = 1, arg1: int = 2, /, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n"  # noqa: E501 line too long
    )
    assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {})
    assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {})
    assert m.args_kwonly_full_monty(8, 9) == (8, 9, 3.14159, (), 42, {})
    assert m.args_kwonly_full_monty(8, 9, 10) == (8, 9, 10.0, (), 42, {})
    assert m.args_kwonly_full_monty(3, 4, 5, 6, 7, m=8, z=9) == (
        3,
        4,
        5.0,
        (
            6,
            7,
        ),
        9,
        {"m": 8},
    )
    assert m.args_kwonly_full_monty(3, 4, 5, 6, 7, m=8, z=9) == (
        3,
        4,
        5.0,
        (
            6,
            7,
        ),
        9,
        {"m": 8},
    )
    assert m.args_kwonly_full_monty(5, j=7, m=8, z=9) == (5, 2, 7.0, (), 9, {"m": 8})
    assert m.args_kwonly_full_monty(i=5, j=7, m=8, z=9) == (
        1,
        2,
        7.0,
        (),
        9,
        {"i": 5, "m": 8},
    )

Henry Schreiner's avatar
Henry Schreiner committed
313
314
315
316
317
318

def test_signatures():
    assert "kw_only_all(*, i: int, j: int) -> tuple\n" == m.kw_only_all.__doc__
    assert "kw_only_mixed(i: int, *, j: int) -> tuple\n" == m.kw_only_mixed.__doc__
    assert "pos_only_all(i: int, j: int, /) -> tuple\n" == m.pos_only_all.__doc__
    assert "pos_only_mix(i: int, /, j: int) -> tuple\n" == m.pos_only_mix.__doc__
319
320
321
322
    assert (
        "pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n"
        == m.pos_kw_only_mix.__doc__
    )
Henry Schreiner's avatar
Henry Schreiner committed
323
324


325
@pytest.mark.xfail("env.PYPY and env.PY2", reason="PyPy2 doesn't double count")
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
def test_args_refcount():
    """Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular
    arguments"""
    refcount = m.arg_refcount_h

    myval = 54321
    expected = refcount(myval)
    assert m.arg_refcount_h(myval) == expected
    assert m.arg_refcount_o(myval) == expected + 1
    assert m.arg_refcount_h(myval) == expected
    assert refcount(myval) == expected

    assert m.mixed_plus_args(1, 2.0, "a", myval) == (1, 2.0, ("a", myval))
    assert refcount(myval) == expected

    assert m.mixed_plus_kwargs(3, 4.0, a=1, b=myval) == (3, 4.0, {"a": 1, "b": myval})
    assert refcount(myval) == expected

    assert m.args_function(-1, myval) == (-1, myval)
    assert refcount(myval) == expected

347
348
349
350
351
352
    assert m.mixed_plus_args_kwargs(5, 6.0, myval, a=myval) == (
        5,
        6.0,
        (myval,),
        {"a": myval},
    )
353
354
    assert refcount(myval) == expected

355
356
357
358
    assert m.args_kwargs_function(7, 8, myval, a=1, b=myval) == (
        (7, 8, myval),
        {"a": 1, "b": myval},
    )
359
360
361
362
363
364
365
366
367
368
369
370
    assert refcount(myval) == expected

    exp3 = refcount(myval, myval, myval)
    assert m.args_refcount(myval, myval, myval) == (exp3, exp3, exp3)
    assert refcount(myval) == expected

    # This function takes the first arg as a `py::object` and the rest as a `py::args`.  Unlike the
    # previous case, when we have both positional and `py::args` we need to construct a new tuple
    # for the `py::args`; in the previous case, we could simply inc_ref and pass on Python's input
    # tuple without having to inc_ref the individual elements, but here we can't, hence the extra
    # refs.
    assert m.mixed_args_refcount(myval, myval, myval) == (exp3 + 3, exp3 + 3, exp3 + 3)
371
372

    assert m.class_default_argument() == "<class 'decimal.Decimal'>"