Issues (74)

tests/test_partial_functions.py (1 issue)

1
import inspect
2
from typing import Generator, Any, ClassVar
3
4
import pytest
5
6
from lagom import Container, bind_to_container, injectable
7
from lagom.exceptions import UnresolvableType
8
from lagom.interfaces import WriteableContainer
9
10
11
class MyDep:
12
    loaded: ClassVar[bool] = False
13
    value: str
14
15
    def __init__(self, value="testing"):
16
        MyDep.loaded = True
17
        self.value = value
18
19
20
class CantBeAutoConstructed:
21
    def __init__(self, something):
22
        pass
23
24
25
container = Container()
26
27
28
def example_function(message: str, resolved: MyDep = injectable) -> str:
29
    return resolved.value + message
30
31
32
def example_function_with_to_injectables(
33
    one: MyDep = injectable, two: MyDep = injectable
34
) -> str:
35
    return one.value + two.value
36
37
38
def example_generator(
39
    message: str, resolved: MyDep = injectable
40
) -> Generator[str, Any, Any]:
41
    yield resolved.value + message
42
    yield resolved.value + " finished"
43
44
45
@bind_to_container(container)
46
def another_example_function(message: str, resolved: MyDep = injectable) -> str:
47
    """
48
    I am DOCS
49
    """
50
    return resolved.value + message
51
52
53
@bind_to_container(container)
54
def failing_to_construct_function(
55
    try_to_resolve: CantBeAutoConstructed = injectable,
56
) -> str:
57
    return "doesnt matter"
58
59
60
def test_partial_application_can_be_applied_to_functions_with_named_args():
61
    partial = container.partial(example_function)
62
    assert partial(message=" world") == "testing world"
63
64
65
def test_partial_application_returns_something_that_is_considered_a_function():
66
    partial = container.partial(example_function)
67
    inspect.isfunction(partial)
68
69
70
def test_partial_application_can_be_applied_to_functions_with_positional_args_first():
71
    partial = container.partial(example_function)
72
    assert partial(" world") == "testing world"
73
74
75
def test_passed_in_arguments_are_used_over_container_generated_ones_when_positional():
76
    partial = container.partial(example_function)
77
    assert partial(" world", MyDep("overridden")) == "overridden world"
78
79
80
def test_passed_in_arguments_are_used_over_container_generated_ones_when_named():
81
    partial = container.partial(example_function)
82
    assert partial(message=" world", resolved=MyDep("overridden")) == "overridden world"
83
84
85
def test_injected_arguments_can_be_skipped():
86
    partial = container.partial(example_function_with_to_injectables)
87
    assert partial(two=MyDep(" 2nd")) == "testing 2nd"
88
89
90
def test_a_decorator_can_be_used_to_bind_as_well():
91
    assert another_example_function(message=" hello") == "testing hello"
92
93
94
def test_a_decorator_can_be_used_to_bind_and_with_positional_arguments():
95
    assert another_example_function(" hello") == "testing hello"
96
97
98
def test_container_values_can_be_overridden():
99
    assert (
100
        another_example_function(resolved=MyDep("replaced"), message=" hello")
101
        == "replaced hello"
102
    )
103
104
105
def test_missing_call_arguments_results_in_sensible_error_messages():
106
    with pytest.raises(TypeError) as err:
107
        another_example_function()
108
    assert "another_example_function() missing 1 required positional argument" in str(
109
        err.value
110
    )
111
112
113
def test_incorrect_arguments_are_handled_well():
114
    with pytest.raises(TypeError) as err:
115
        another_example_function(not_the_message=" no")
116
    assert "unexpected keyword argument 'not_the_message'" in str(err.value)
117
118
119
def test_if_a_typed_argument_cant_be_constructed_a_helpful_exception_is_returned():
120
    with pytest.raises(UnresolvableType) as err:
121
        failing_to_construct_function()
122
    assert "Unable to construct dependency of type CantBeAutoConstructed" in str(
123
        err.value
124
    )
125
126
127
def test_partial_application_can_be_applied_to_generators():
128
    partial = container.partial(example_generator)
129
    results = [result for result in partial(message=" world")]
130
    assert results == ["testing world", "testing finished"]
131
132
133
def test_deps_are_loaded_at_call_time_not_definition_time():
134
    MyDep.loaded = False
135
136
    @bind_to_container(container)
137
    def some_random_unused_function(message: str, resolved: MyDep = injectable) -> str:
138
        return resolved.value + message
139
140
    assert not MyDep.loaded
141
142
143
def test_name_and_docs_are_kept():
144
    assert another_example_function.__name__ == "another_example_function"
145
    # Note: whitespace stripping because some versions of python format the docs differently
146
    #       and we don't care too much about that. just that content is kept.
147
    assert another_example_function.__doc__.strip() == "I am DOCS"  # type: ignore[union-attr]
148
149
150 View Code Duplication
def test_partials_can_be_provided_with_an_update_method(container: Container):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
151
    def _my_func(a, b: MyDep = injectable):
152
        return a, b
153
154
    def _dep_two_is_dep_one(c: WriteableContainer, a, k):
155
        # We'll do something a bit weird and say the container
156
        # always injects the first supplied argument when asked for
157
        # a MyDep. Don't do this for real.
158
        c[MyDep] = a[0]
159
160
    weird = container.partial(_my_func, container_updater=_dep_two_is_dep_one)
161
162
    arg_1, arg_2 = weird("hello")
163
    assert arg_1 == arg_2
164