Coverage for tests/library/test_custom.py: 100%
144 statements
« prev ^ index » next coverage.py v7.17.3, created at 2026-11-13 05:19 +0000
« prev ^ index » next coverage.py v7.17.3, created at 2026-11-13 05:19 +0000
1import logging 1abcdefghijklmnopqrst
2from time import monotonic_ns 1abcdefghijklmnopqrst
3from typing import Annotated, Any 1abcdefghijklmnopqrst
5import anyio 1abcdefghijklmnopqrst
6import pytest 1abcdefghijklmnopqrst
8from fast_depends import Depends, inject 1abcdefghijklmnopqrst
9from fast_depends.exceptions import ValidationError 1abcdefghijklmnopqrst
10from fast_depends.library import CustomField 1abcdefghijklmnopqrst
11from tests.marks import serializer 1abcdefghijklmnopqrst
14class Header(CustomField): 1abcdefghijklmnopqrst
15 def use(self, /, **kwargs: Any) -> dict[str, Any]: 1abcdefghijklmnopqrst
16 kwargs = super().use(**kwargs) 1abcdefghijklmnopqrst
17 if v := kwargs.get("headers", {}).get(self.param_name): 1abcdefghijklmnopqrst
18 kwargs[self.param_name] = v 1abcdefghijklmnopqrst
19 return kwargs 1abcdefghijklmnopqrst
22class FieldHeader(Header): 1abcdefghijklmnopqrst
23 def __init__(self, *, cast: bool = True, required: bool = True) -> None: 1abcdefghijklmnopqrst
24 super().__init__(cast=cast, required=required) 1abcdefghijklmnopqr
25 self.field = True 1abcdefghijklmnopqr
27 def use_field(self, kwargs: Any) -> None: 1abcdefghijklmnopqrst
28 if v := kwargs.get("headers", {}).get(self.param_name): # pragma: no branch 1abcdefghijklmnopqr
29 kwargs[self.param_name] = v 1abcdefghijklmnopqr
32class AsyncHeader(Header): 1abcdefghijklmnopqrst
33 async def use(self, /, **kwargs: Any) -> dict[str, Any]: 1abcdefghijklmnopqrst
34 return super().use(**kwargs) 1abcdefghijklmnopqr
37class AsyncFieldHeader(Header): 1abcdefghijklmnopqrst
38 def __init__(self, *, cast: bool = True, required: bool = True) -> None: 1abcdefghijklmnopqrst
39 super().__init__(cast=cast, required=required) 1abcdefghijklmnopqr
40 self.field = True 1abcdefghijklmnopqr
42 async def use_field(self, kwargs: Any) -> None: 1abcdefghijklmnopqrst
43 await anyio.sleep(0.1) 1abcdefghijklmnopqr
44 if v := kwargs.get("headers", {}).get(self.param_name): # pragma: no branch 1abcdefghijklmnopqr
45 kwargs[self.param_name] = v 1abcdefghijklmnopqr
48class FailingAsyncFieldHeader(Header): 1abcdefghijklmnopqrst
49 def __init__(self) -> None: 1abcdefghijklmnopqrst
50 super().__init__() 1abcdefghijklmnopqrst
51 self.field = True 1abcdefghijklmnopqrst
53 async def use_field(self, kwargs: Any) -> None: 1abcdefghijklmnopqrst
54 raise ValueError("failed to resolve field") 1abcdefghijklmnopqrst
57def test_header(): 1abcdefghijklmnopqrst
58 @inject 1abcdefghijklmnopqrst
59 def sync_catch(key: int = Header()): # noqa: B008 1abcdefghijklmnopqrst
60 return key 1abcdefghijklmnopqrst
62 assert sync_catch(headers={"key": 1}) == 1 1abcdefghijklmnopqrst
65@pytest.mark.anyio 1abcdefghijklmnopqrst
66async def test_async_field_header_error_is_unwrapped(): 1abcdefghijklmnopqrst
67 @inject 1abcdefghijklmnopqrst
68 async def async_catch(key=FailingAsyncFieldHeader()): # noqa: B008 1abcdefghijklmnopqrst
69 raise AssertionError("unreachable")
71 # `field=True` customs are resolved inside an `anyio` task group, which wraps
72 # failures into an `ExceptionGroup` - the original error must be re-raised as is
73 with pytest.raises(ValueError, match="failed to resolve field"): 1abcdefghijklmnopqrst
74 await async_catch(headers={"key": 1}) 1abcdefghijklmnopqrst
77def test_custom_with_class(): 1abcdefghijklmnopqrst
78 class T: 1abcdefghijklmnopqrst
79 @inject 1abcdefghijklmnopqrst
80 def __init__(self, key: int = Header()): 1abcdefghijklmnopqrst
81 self.key = key 1abcdefghijklmnopqrst
83 assert T(headers={"key": 1}).key == 1 1abcdefghijklmnopqrst
86def test_reusable_annotated() -> None: 1abcdefghijklmnopqrst
87 HeaderKey = Annotated[float, Header(cast=False)] 1abcdefghijklmnopqrst
89 @inject 1abcdefghijklmnopqrst
90 def sync_catch(key: HeaderKey) -> float: 1abcdefghijklmnopqrst
91 return key 1abcdefghijklmnopqrst
93 @inject 1abcdefghijklmnopqrst
94 def sync_catch2(key2: HeaderKey) -> float: 1abcdefghijklmnopqrst
95 return key2 1abcdefghijklmnopqrst
97 assert sync_catch(headers={"key": 1}) == 1 1abcdefghijklmnopqrst
98 assert sync_catch2(headers={"key2": 1}) == 1 1abcdefghijklmnopqrst
101def test_arguments_mapping(): 1abcdefghijklmnopqrst
102 @inject 1abcdefghijklmnopqrst
103 def func( 1abcdefghijklmnopqrst
104 d: int = CustomField(cast=False),
105 b: int = CustomField(cast=False),
106 c: int = CustomField(cast=False),
107 a: int = CustomField(cast=False),
108 ):
109 assert d == 4 1abcdefghijklmnopqrst
110 assert b == 2 1abcdefghijklmnopqrst
111 assert c == 3 1abcdefghijklmnopqrst
112 assert a == 1 1abcdefghijklmnopqrst
114 for _ in range(50): 1abcdefghijklmnopqrst
115 func(4, 2, 3, 1) 1abcdefghijklmnopqrst
118@serializer 1abcdefghijklmnopqrst
119class TestSerializer: 1abcdefghijklmnopqrst
120 @pytest.mark.anyio 1abcdefghijklmnopqrst
121 async def test_header_async(self): 1abcdefghijklmnopqrst
122 @inject 1abcdefghijklmnopqr
123 async def async_catch(key: int = Header()): # noqa: B008 1abcdefghijklmnopqr
124 return key 1abcdefghijklmnopqr
126 assert (await async_catch(headers={"key": "1"})) == 1 1abcdefghijklmnopqr
128 def test_multiple_header(self): 1abcdefghijklmnopqrst
129 @inject 1abcdefghijklmnopqr
130 def sync_catch(key: str = Header(), key2: int = Header()): # noqa: B008 1abcdefghijklmnopqr
131 assert key == "1" 1abcdefghijklmnopqr
132 assert key2 == 2 1abcdefghijklmnopqr
134 sync_catch(headers={"key": "1", "key2": "2"}) 1abcdefghijklmnopqr
136 @pytest.mark.anyio 1abcdefghijklmnopqrst
137 async def test_async_header_async(self): 1abcdefghijklmnopqrst
138 @inject 1abcdefghijklmnopqr
139 async def async_catch( # noqa: B008 1abcdefghijklmnopqr
140 key: float = AsyncHeader(), key2: int = AsyncHeader()
141 ):
142 return key, key2 1abcdefghijklmnopqr
144 assert (await async_catch(headers={"key": "1", "key2": 1})) == (1.0, 1) 1abcdefghijklmnopqr
146 def test_sync_field_header(self): 1abcdefghijklmnopqrst
147 @inject 1abcdefghijklmnopqr
148 def sync_catch(key: float = FieldHeader(), key2: int = FieldHeader()): # noqa: B008 1abcdefghijklmnopqr
149 return key, key2 1abcdefghijklmnopqr
151 assert sync_catch(headers={"key": "1", "key2": 1}) == (1.0, 1) 1abcdefghijklmnopqr
153 @pytest.mark.anyio 1abcdefghijklmnopqrst
154 async def test_async_field_header(self): 1abcdefghijklmnopqrst
155 @inject 1abcdefghijklmnopqr
156 async def async_catch( # noqa: B008 1abcdefghijklmnopqr
157 key: float = AsyncFieldHeader(), key2: int = AsyncFieldHeader()
158 ):
159 return key, key2 1abcdefghijklmnopqr
161 start = monotonic_ns() 1abcdefghijklmnopqr
162 assert (await async_catch(headers={"key": "1", "key2": 1})) == (1.0, 1) 1abcdefghijklmnopqr
163 assert (monotonic_ns() - start) / 10**9 < 0.2 1abcdefghijklmnopqr
165 def test_async_header_sync(self): 1abcdefghijklmnopqrst
166 with pytest.raises(AssertionError): 1abcdefghijklmnopqr
168 @inject 1abcdefghijklmnopqr
169 def sync_catch(key: str = AsyncHeader()): # pragma: no cover # noqa: B008 1abcdefghijklmnopqr
170 return key
172 def test_header_annotated(self): 1abcdefghijklmnopqrst
173 @inject 1abcdefghijklmnopqr
174 def sync_catch(key: Annotated[int, Header()]): 1abcdefghijklmnopqr
175 return key 1abcdefghijklmnopqr
177 assert sync_catch(headers={"key": 1}) == 1 1abcdefghijklmnopqr
179 def test_header_required(self): 1abcdefghijklmnopqrst
180 @inject 1abcdefghijklmnopqr
181 def sync_catch(key=Header()): # pragma: no cover # noqa: B008 1abcdefghijklmnopqr
182 return key
184 with pytest.raises(ValidationError): 1abcdefghijklmnopqr
185 sync_catch() 1abcdefghijklmnopqr
187 def test_header_not_required(self): 1abcdefghijklmnopqrst
188 @inject 1abcdefghijklmnopqr
189 def sync_catch(key2=Header(required=False)): # noqa: B008 1abcdefghijklmnopqr
190 assert key2 is None 1abcdefghijklmnopqr
192 sync_catch() 1abcdefghijklmnopqr
194 def test_header_not_required_with_default(self): 1abcdefghijklmnopqrst
195 @inject 1abcdefghijklmnopqr
196 def sync_catch(key2: Annotated[str, Header(required=False)] = "1"): # noqa: B008 1abcdefghijklmnopqr
197 return key2 == "1" 1abcdefghijklmnopqr
199 assert sync_catch() 1abcdefghijklmnopqr
201 def test_depends(self): 1abcdefghijklmnopqrst
202 def dep(key: Annotated[int, Header()]): 1abcdefghijklmnopqr
203 return key 1abcdefghijklmnopqr
205 @inject 1abcdefghijklmnopqr
206 def sync_catch(k=Depends(dep)): 1abcdefghijklmnopqr
207 return k 1abcdefghijklmnopqr
209 assert sync_catch(headers={"key": 1}) == 1 1abcdefghijklmnopqr
211 def test_not_cast(self): 1abcdefghijklmnopqrst
212 @inject 1abcdefghijklmnopqr
213 def sync_catch(key: Annotated[float, Header(cast=False)]): 1abcdefghijklmnopqr
214 return key 1abcdefghijklmnopqr
216 assert sync_catch(headers={"key": 1}) == 1 1abcdefghijklmnopqr
218 @inject 1abcdefghijklmnopqr
219 def sync_catch(key: logging.Logger = Header(cast=False)): # noqa: B008 1abcdefghijklmnopqr
220 return key 1abcdefghijklmnopqr
222 assert sync_catch(headers={"key": 1}) == 1 1abcdefghijklmnopqr