File: test_funcutils_fb_py3.py

package info (click to toggle)
python-boltons 25.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,236 kB
  • sloc: python: 12,133; makefile: 159; sh: 7
file content (318 lines) | stat: -rw-r--r-- 9,218 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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
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
313
314
315
316
317
318
import time
import inspect
import functools
from collections import defaultdict

import pytest

from boltons.funcutils import wraps, FunctionBuilder, update_wrapper, copy_function
import boltons.funcutils as funcutils


def wrappable_func(a, b):
    return a, b


def wrappable_varkw_func(a, b, **kw):
    return a, b


def pita_wrap(flag=False):

    def cedar_dec(func):
        @wraps(func)
        def cedar_wrapper(*a, **kw):
            return (flag, func.__name__, func(*a, **kw))
        return cedar_wrapper

    return cedar_dec


def test_wraps_py3():

    @pita_wrap(flag=True)
    def annotations(a: int, b: float=1, c: defaultdict=()) -> defaultdict:
        return a, b, c

    assert annotations(0) == (True, "annotations", (0, 1, ()))
    assert annotations.__annotations__ == {'a': int, 'b': float,
                                           'c': defaultdict,
                                           'return': defaultdict}

    @pita_wrap(flag=False)
    def kwonly_arg(a, *, b, c=2):
        return a, b, c

    with pytest.raises(TypeError):
        kwonly_arg(0)

    assert kwonly_arg(0, b=1) == (False, "kwonly_arg", (0, 1, 2))
    assert kwonly_arg(0, b=1, c=3) == (False, "kwonly_arg", (0, 1, 3))

    @pita_wrap(flag=True)
    def kwonly_non_roundtrippable_repr(*, x=lambda y: y + 1):
        return x(1)

    assert kwonly_non_roundtrippable_repr() == (
        True, 'kwonly_non_roundtrippable_repr', 2)


def test_copy_function_kw_defaults_py3():
    # test that the copy works with keyword-only defaults
    f = lambda x, *, y=2: x * y
    f_copy = copy_function(f)
    assert f(21) == f_copy(21) == 42


@pytest.mark.parametrize('partial_kind', (functools, funcutils))
def test_update_wrapper_partial(partial_kind):
    wrapper = partial_kind.partial(wrappable_varkw_func, b=1)

    fully_wrapped = update_wrapper(wrapper, wrappable_varkw_func)
    assert fully_wrapped(1) == (1, 1)


def test_remove_kwonly_arg():
    # example adapted from https://github.com/mahmoud/boltons/issues/123

    def darkhelm_inject_loop(func):
        sig = inspect.signature(func)
        loop_param = sig.parameters['loop'].replace(default=None)
        sig = sig.replace(parameters=[loop_param])

        def add_loop(args, kwargs):
            bargs = sig.bind(*args, **kwargs)
            bargs.apply_defaults()
            if bargs.arguments['loop'] is None:
                bargs.arguments['loop'] = "don't look at me, I just use gevent"

            return bargs.arguments

        def wrapper(*args, **kwargs):
            return func(**add_loop(args, kwargs))

        return wraps(func, injected=['loop'])(wrapper)

    @darkhelm_inject_loop
    def example(test='default', *, loop='lol'):
        return loop

    fb_example = FunctionBuilder.from_func(example)
    assert 'test' in fb_example.args
    assert fb_example.get_defaults_dict()['test'] == 'default'

    assert 'loop' not in fb_example.kwonlyargs
    assert 'loop' not in fb_example.kwonlydefaults


def test_defaults_dict():
    def example(req, test='default', *, loop='lol'):
        return loop

    fb_example = FunctionBuilder.from_func(example)
    assert 'test' in fb_example.args
    dd = fb_example.get_defaults_dict()
    assert dd['test'] == 'default'
    assert dd['loop'] == 'lol'
    assert 'req' not in dd


def test_get_arg_names():
    def example(req, test='default', *, loop='lol'):
        return loop

    fb_example = FunctionBuilder.from_func(example)
    assert 'test' in fb_example.args
    assert fb_example.get_arg_names() == ('req', 'test', 'loop')
    assert fb_example.get_arg_names(only_required=True) == ('req',)


@pytest.mark.parametrize('signature,should_match',
                         [('a, *, b', True),
                          ('a,*,b', True),
                          ('a, * , b', True),
                          ('a, *,\nb', True),
                          ('a, *\n,b', True),
                          ('a, b', False),
                          ('a, *args', False),
                          ('a, *args, **kwargs', False),
                          ('*args', False),
                          ('*args, **kwargs', False)])
def test_FunctionBuilder_KWONLY_MARKER(signature, should_match):
    """
    _KWONLY_MARKER matches the keyword-only argument separator,
    regardless of whitespace.

    Note: it assumes the signature is valid Python.
    """
    matched = bool(FunctionBuilder._KWONLY_MARKER.search(signature))
    message = "{!r}: should_match was {}, but result was {}".format(
        signature, should_match, matched)
    assert bool(matched) == should_match, message


def test_FunctionBuilder_add_arg_kwonly():
    fb = FunctionBuilder('return_val', doc='returns the value',
                         body='return val')

    broken_func = fb.get_func()
    with pytest.raises(NameError):
        broken_func()

    fb.add_arg('val', default='default_val', kwonly=True)

    better_func = fb.get_func()
    assert better_func() == 'default_val'

    with pytest.raises(ValueError):
        fb.add_arg('val')

    assert better_func(val='keyword') == 'keyword'

    with pytest.raises(TypeError):
        assert better_func('positional')
    return


@pytest.mark.parametrize(
    "args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, invocation_str, sig_str",
    [
        (
            None,
            "args",
            "kwargs",
            None,
            "a",
            dict(a="a"),
            "*args, a=a, **kwargs",
            "(*args, a, **kwargs)",
        )
    ],
)
def test_get_invocation_sig_str(
    args,
    varargs,
    varkw,
    defaults,
    kwonlyargs,
    kwonlydefaults,
    invocation_str,
    sig_str,
):
    fb = FunctionBuilder(
        name="return_five",
        body="return 5",
        args=args,
        varargs=varargs,
        varkw=varkw,
        defaults=defaults,
        kwonlyargs=kwonlyargs,
        kwonlydefaults=kwonlydefaults,
    )

    assert fb.get_invocation_str() == invocation_str
    assert fb.get_sig_str() == sig_str


def test_wraps_inner_kwarg_only():
    """from https://github.com/mahmoud/boltons/issues/261

    mh responds to the issue:

    You'll notice that when kw-only args are involved the first time
    (wraps(f)(g)) it works fine. The other way around, however,
    wraps(g)(f) fails, because by the very nature of funcutils.wraps,
    you're trying to give f the same signature as g. And f's signature
    is not like g's. g supports positional b and f() does not.

    If you want to make a wrapper which converts a keyword-only
    argument to one that can be positional or keyword only, that'll
    require a different approach for now.

    A potential fix would be to pass all function arguments as
    keywords. But doubt that's the right direction, because, while I
    have yet to add positional argument only support, that'll
    definitely throw a wrench into things.
    """
    from boltons.funcutils import wraps

    def g(a: float, b=10):
        return a * b

    def f(a: int,  *, b=1):
        return a * b

    # all is well here...
    assert f(3) == 3
    assert g(3) == 30
    assert wraps(f)(g)(3) == 3  # yay, g got the f default (not so with functools.wraps!)

    # but this doesn't work
    with pytest.raises(TypeError):
        wraps(g)(f)(3)

    return


def test_wraps_async():
    # from https://github.com/mahmoud/boltons/issues/194
    import asyncio

    def delayed(func):
        @wraps(func)
        async def wrapped(*args, **kw):
            await asyncio.sleep(1.0)
            return await func(*args, **kw)

        return wrapped


    async def f():
        await asyncio.sleep(0.1)

    assert asyncio.iscoroutinefunction(f)

    f2 = delayed(f)

    assert asyncio.iscoroutinefunction(f2)

    # from https://github.com/mahmoud/boltons/pull/195
    def yolo():
        def make_time_decorator(wrapped):
            @wraps(wrapped)
            async def decorator(*args, **kw):
                return (await wrapped(*args, **kw))
            return decorator

        return make_time_decorator


    @yolo()
    async def foo(x):
        await asyncio.sleep(x)

    start_time = time.monotonic()
    asyncio.run(foo(0.3))
    duration = time.monotonic() - start_time

    # lol windows py37 somehow completes this in under 0.3
    # "assert 0.29700000000002547 > 0.3" https://ci.appveyor.com/project/mahmoud/boltons/builds/22261051/job/3jfq1tq2233csqp6
    assert duration > 0.25


def test_wraps_hide_wrapped():
    new_func = wraps(wrappable_func, injected='b')(lambda a: wrappable_func(a, b=1))
    new_sig = inspect.signature(new_func, follow_wrapped=True)

    assert list(new_sig.parameters.keys()) == ['a', 'b']

    new_func = wraps(wrappable_func, injected='b', hide_wrapped=True)(lambda a: wrappable_func(a, b=1))
    new_sig = inspect.signature(new_func, follow_wrapped=True)

    assert list(new_sig.parameters.keys()) == ['a']

    new_func = wraps(wrappable_func, injected='b')(lambda a: wrappable_func(a, b=1))
    new_new_func = wraps(new_func, injected='a', hide_wrapped=True)(lambda: new_func(a=1))
    new_new_sig = inspect.signature(new_new_func, follow_wrapped=True)

    assert len(new_new_sig.parameters) == 0