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
Duplication
introduced
by
![]() |
|||
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 |