@@ -28,8 +28,9 @@ def test_default_decoder_behaves_like_decodeutils(self) -> None:
2828 # The adapter may wrap the default, so compare behavior rather than identity.
2929 opts = DecodeOptions ()
3030 s = "a+b%2E"
31- out_key = opts .decoder (s , Charset .UTF8 , kind = DecodeKind .KEY )
32- out_val = opts .decoder (s , Charset .UTF8 , kind = DecodeKind .VALUE )
31+ decoder = require_decoder (opts )
32+ out_key = decoder (s , Charset .UTF8 , kind = DecodeKind .KEY )
33+ out_val = decoder (s , Charset .UTF8 , kind = DecodeKind .VALUE )
3334 assert out_key == DecodeUtils .decode (s , charset = Charset .UTF8 , kind = DecodeKind .KEY )
3435 assert out_val == DecodeUtils .decode (s , charset = Charset .UTF8 , kind = DecodeKind .VALUE )
3536
@@ -43,7 +44,8 @@ def dec(s: t.Optional[str]) -> t.Optional[str]:
4344 return None if s is None else s .upper ()
4445
4546 opts = DecodeOptions (decoder = dec )
46- assert opts .decoder ("x" , Charset .UTF8 , kind = DecodeKind .KEY ) == "X"
47+ decoder = require_decoder (opts )
48+ assert decoder ("x" , Charset .UTF8 , kind = DecodeKind .KEY ) == "X"
4749 assert calls == [("x" ,)]
4850
4951 def test_two_args_s_charset (self ) -> None :
@@ -55,7 +57,8 @@ def dec(s: t.Optional[str], charset: t.Optional[Charset]) -> t.Optional[str]:
5557 return None if s is None else f"{ s } |{ charset .name if charset else 'NONE' } "
5658
5759 opts = DecodeOptions (decoder = dec )
58- assert opts .decoder ("hi" , Charset .LATIN1 , kind = DecodeKind .VALUE ) == "hi|LATIN1"
60+ decoder = require_decoder (opts )
61+ assert decoder ("hi" , Charset .LATIN1 , kind = DecodeKind .VALUE ) == "hi|LATIN1"
5962 assert seen == [("hi" , Charset .LATIN1 )]
6063
6164 def test_three_args_kind_enum_annotation (self ) -> None :
@@ -67,7 +70,8 @@ def dec(s: t.Optional[str], charset: t.Optional[Charset], kind: DecodeKind) -> t
6770 return None if s is None else f"K:{ 'E' if isinstance (kind , DecodeKind ) else type (kind ).__name__ } "
6871
6972 opts = DecodeOptions (decoder = dec )
70- assert opts .decoder ("z" , Charset .UTF8 , kind = DecodeKind .KEY ) == "K:E"
73+ decoder = require_decoder (opts )
74+ assert decoder ("z" , Charset .UTF8 , kind = DecodeKind .KEY ) == "K:E"
7175 assert seen and isinstance (seen [0 ], DecodeKind ) and seen [0 ] is DecodeKind .KEY
7276
7377 def test_three_args_kind_str_annotation (self ) -> None :
@@ -78,7 +82,8 @@ def dec(s: t.Optional[str], charset: t.Optional[Charset], kind: str) -> t.Option
7882 return None if s is None else kind # echo back
7983
8084 opts = DecodeOptions (decoder = dec )
81- assert opts .decoder ("z" , Charset .UTF8 , kind = DecodeKind .KEY ) == "key"
85+ decoder = require_decoder (opts )
86+ assert decoder ("z" , Charset .UTF8 , kind = DecodeKind .KEY ) == "key"
8287 assert seen == ["key" ]
8388
8489 def test_kwonly_kind_str (self ) -> None :
@@ -89,7 +94,8 @@ def dec(s: t.Optional[str], charset: t.Optional[Charset], *, kind: str) -> t.Opt
8994 return None if s is None else kind
9095
9196 opts = DecodeOptions (decoder = dec )
92- assert opts .decoder ("z" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "value"
97+ decoder = require_decoder (opts )
98+ assert decoder ("z" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "value"
9399 assert seen == ["value" ]
94100
95101 def test_varargs_kwargs_receives_kind_string (self ) -> None :
@@ -100,16 +106,18 @@ def dec(s: t.Optional[str], *args, **kwargs) -> t.Optional[str]: # type: ignore
100106 return s
101107
102108 opts = DecodeOptions (decoder = dec )
103- assert opts .decoder ("ok" , Charset .UTF8 , kind = DecodeKind .KEY ) == "ok"
109+ decoder = require_decoder (opts )
110+ assert decoder ("ok" , Charset .UTF8 , kind = DecodeKind .KEY ) == "ok"
104111 assert seen == ["key" ]
105112
106113 def test_user_decoder_typeerror_is_not_swallowed (self ) -> None :
107114 def dec (s : t .Optional [str ]) -> t .Optional [str ]:
108115 raise TypeError ("boom" )
109116
110117 opts = DecodeOptions (decoder = dec )
118+ decoder = require_decoder (opts )
111119 with pytest .raises (TypeError ):
112- _ = opts . decoder ("oops" , Charset .UTF8 , kind = DecodeKind .KEY )
120+ _ = decoder ("oops" , Charset .UTF8 , kind = DecodeKind .KEY )
113121
114122 def test_kwonly_charset_receives_keyword_argument (self ) -> None :
115123 calls : t .List [t .Dict [str , t .Any ]] = []
@@ -119,7 +127,8 @@ def dec(s: t.Optional[str], *, charset: t.Optional[Charset], kind: str) -> t.Opt
119127 return s
120128
121129 opts = DecodeOptions (decoder = dec )
122- assert opts .decoder ("x" , Charset .LATIN1 , kind = DecodeKind .KEY ) == "x"
130+ decoder = require_decoder (opts )
131+ assert decoder ("x" , Charset .LATIN1 , kind = DecodeKind .KEY ) == "x"
123132 assert calls == [{"charset" : Charset .LATIN1 , "kind" : "key" }]
124133
125134 def test_positional_only_kind_receives_string (self ) -> None :
@@ -136,7 +145,8 @@ def dec(
136145 return s
137146
138147 opts = DecodeOptions (decoder = dec )
139- assert opts .decoder ("value" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "value"
148+ decoder = require_decoder (opts )
149+ assert decoder ("value" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "value"
140150 assert seen == [("value" , Charset .UTF8 )]
141151
142152 def test_unannotated_kind_parameter_receives_string (self ) -> None :
@@ -147,7 +157,8 @@ def dec(s: t.Optional[str], charset: t.Optional[Charset], kind) -> t.Optional[st
147157 return s
148158
149159 opts = DecodeOptions (decoder = dec )
150- assert opts .decoder ("q" , Charset .UTF8 , kind = DecodeKind .KEY ) == "q"
160+ decoder = require_decoder (opts )
161+ assert decoder ("q" , Charset .UTF8 , kind = DecodeKind .KEY ) == "q"
151162 assert seen == ["key" ]
152163
153164 def test_literal_kind_annotation_prefers_string (self ) -> None :
@@ -162,7 +173,8 @@ def dec(
162173 return s
163174
164175 opts = DecodeOptions (decoder = dec )
165- assert opts .decoder ("ok" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "ok"
176+ decoder = require_decoder (opts )
177+ assert decoder ("ok" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "ok"
166178 assert seen == ["value" ]
167179
168180 def test_builtin_signature_unavailable_single_arg_fallback (self ) -> None :
@@ -173,7 +185,8 @@ def __call__(self, s: t.Optional[str]) -> t.Optional[str]:
173185 return None if s is None else f"{ s } -ok"
174186
175187 opts = DecodeOptions (decoder = BadSignature ())
176- assert opts .decoder ("x" , Charset .UTF8 , kind = DecodeKind .KEY ) == "x-ok"
188+ decoder = require_decoder (opts )
189+ assert decoder ("x" , Charset .UTF8 , kind = DecodeKind .KEY ) == "x-ok"
177190
178191 def test_builtin_signature_unavailable_two_arg_fallback (self ) -> None :
179192 class BadSignature :
@@ -183,7 +196,8 @@ def __call__(self, s: t.Optional[str], charset: t.Optional[Charset]) -> t.Option
183196 return None if s is None else f"{ s } |{ charset .name if charset else 'NONE' } "
184197
185198 opts = DecodeOptions (decoder = BadSignature ())
186- assert opts .decoder ("x" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "x|UTF8"
199+ decoder = require_decoder (opts )
200+ assert decoder ("x" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "x|UTF8"
187201
188202 def test_builtin_signature_unavailable_raises_original_typeerror (self ) -> None :
189203 class BadSignature :
@@ -193,8 +207,9 @@ def __call__(self) -> t.Optional[str]:
193207 return "nope"
194208
195209 opts = DecodeOptions (decoder = BadSignature ())
210+ decoder = require_decoder (opts )
196211 with pytest .raises (TypeError ) as exc_info :
197- _ = opts . decoder ("x" , Charset .UTF8 , kind = DecodeKind .KEY )
212+ _ = decoder ("x" , Charset .UTF8 , kind = DecodeKind .KEY )
198213 assert exc_info .value .__cause__ is not None
199214
200215 def test_builtin_without_signature_raises_original_typeerror (self ) -> None :
@@ -257,43 +272,51 @@ class TestDefaultDecodeKeyEncodedDots:
257272 def test_key_maps_2e_inside_brackets_allowdots_true (self ) -> None :
258273 for cs in (Charset .UTF8 , Charset .LATIN1 ):
259274 opts = DecodeOptions (allow_dots = True , charset = cs )
260- assert opts .decoder ("a[%2E]" , cs , kind = DecodeKind .KEY ) == "a[.]"
261- assert opts .decoder ("a[%2e]" , cs , kind = DecodeKind .KEY ) == "a[.]"
275+ decoder = require_decoder (opts )
276+ assert decoder ("a[%2E]" , cs , kind = DecodeKind .KEY ) == "a[.]"
277+ assert decoder ("a[%2e]" , cs , kind = DecodeKind .KEY ) == "a[.]"
262278
263279 def test_key_maps_2e_outside_brackets_allowdots_true_independent_of_decodeopt (self ) -> None :
264280 for cs in (Charset .UTF8 , Charset .LATIN1 ):
265281 opts1 = DecodeOptions (allow_dots = True , decode_dot_in_keys = False , charset = cs )
266282 opts2 = DecodeOptions (allow_dots = True , decode_dot_in_keys = True , charset = cs )
267- assert opts1 .decoder ("a%2Eb" , cs , kind = DecodeKind .KEY ) == "a.b"
268- assert opts2 .decoder ("a%2Eb" , cs , kind = DecodeKind .KEY ) == "a.b"
283+ decoder1 = require_decoder (opts1 )
284+ decoder2 = require_decoder (opts2 )
285+ assert decoder1 ("a%2Eb" , cs , kind = DecodeKind .KEY ) == "a.b"
286+ assert decoder2 ("a%2Eb" , cs , kind = DecodeKind .KEY ) == "a.b"
269287
270288 def test_non_key_decodes_2e_to_dot_control (self ) -> None :
271289 for cs in (Charset .UTF8 , Charset .LATIN1 ):
272290 opts = DecodeOptions (allow_dots = True , charset = cs )
273- assert opts .decoder ("a%2Eb" , cs , kind = DecodeKind .VALUE ) == "a.b"
291+ decoder = require_decoder (opts )
292+ assert decoder ("a%2Eb" , cs , kind = DecodeKind .VALUE ) == "a.b"
274293
275294 def test_key_maps_2e_inside_brackets_allowdots_false (self ) -> None :
276295 for cs in (Charset .UTF8 , Charset .LATIN1 ):
277296 opts = DecodeOptions (allow_dots = False , charset = cs )
278- assert opts .decoder ("a[%2E]" , cs , kind = DecodeKind .KEY ) == "a[.]"
279- assert opts .decoder ("a[%2e]" , cs , kind = DecodeKind .KEY ) == "a[.]"
297+ decoder = require_decoder (opts )
298+ assert decoder ("a[%2E]" , cs , kind = DecodeKind .KEY ) == "a[.]"
299+ assert decoder ("a[%2e]" , cs , kind = DecodeKind .KEY ) == "a[.]"
280300
281301 def test_key_outside_2e_decodes_to_dot_allowdots_false (self ) -> None :
282302 for cs in (Charset .UTF8 , Charset .LATIN1 ):
283303 opts = DecodeOptions (allow_dots = False , charset = cs )
284- assert opts .decoder ("a%2Eb" , cs , kind = DecodeKind .KEY ) == "a.b"
285- assert opts .decoder ("a%2eb" , cs , kind = DecodeKind .KEY ) == "a.b"
304+ decoder = require_decoder (opts )
305+ assert decoder ("a%2Eb" , cs , kind = DecodeKind .KEY ) == "a.b"
306+ assert decoder ("a%2eb" , cs , kind = DecodeKind .KEY ) == "a.b"
286307
287308
288309class TestCustomDecoderBehavior :
289310 def test_decode_key_decodes_percent_sequences_like_values_when_decode_dot_in_keys_false (self ) -> None :
290311 opts = DecodeOptions (allow_dots = True , decode_dot_in_keys = False )
291- assert opts .decoder ("a%2Eb" , Charset .UTF8 , kind = DecodeKind .KEY ) == "a.b"
292- assert opts .decoder ("a%2eb" , Charset .UTF8 , kind = DecodeKind .KEY ) == "a.b"
312+ decoder = require_decoder (opts )
313+ assert decoder ("a%2Eb" , Charset .UTF8 , kind = DecodeKind .KEY ) == "a.b"
314+ assert decoder ("a%2eb" , Charset .UTF8 , kind = DecodeKind .KEY ) == "a.b"
293315
294316 def test_decode_value_decodes_percent_sequences_normally (self ) -> None :
295317 opts = DecodeOptions ()
296- assert opts .decoder ("%2E" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "."
318+ decoder = require_decoder (opts )
319+ assert decoder ("%2E" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "."
297320
298321 def test_decoder_is_used_for_key_and_value (self ) -> None :
299322 calls : t .List [t .Tuple [t .Optional [str ], DecodeKind ]] = []
@@ -303,8 +326,9 @@ def dec(s: t.Optional[str], charset: t.Optional[Charset], kind: DecodeKind) -> t
303326 return s
304327
305328 opts = DecodeOptions (decoder = dec )
306- assert opts .decoder ("x" , Charset .UTF8 , kind = DecodeKind .KEY ) == "x"
307- assert opts .decoder ("y" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "y"
329+ decoder = require_decoder (opts )
330+ assert decoder ("x" , Charset .UTF8 , kind = DecodeKind .KEY ) == "x"
331+ assert decoder ("y" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "y"
308332
309333 assert len (calls ) == 2
310334 assert calls [0 ][1 ] is DecodeKind .KEY and calls [0 ][0 ] == "x"
@@ -315,17 +339,19 @@ def dec(s: t.Optional[str], charset: t.Optional[Charset], kind: DecodeKind) -> t
315339 return None
316340
317341 opts = DecodeOptions (decoder = dec )
318- assert opts .decoder ("foo" , Charset .UTF8 , kind = DecodeKind .VALUE ) is None
319- assert opts .decoder ("bar" , Charset .UTF8 , kind = DecodeKind .KEY ) is None
342+ decoder = require_decoder (opts )
343+ assert decoder ("foo" , Charset .UTF8 , kind = DecodeKind .VALUE ) is None
344+ assert decoder ("bar" , Charset .UTF8 , kind = DecodeKind .KEY ) is None
320345
321346 def test_single_decoder_acts_like_legacy_when_ignoring_kind (self ) -> None :
322347 def dec (s : t .Optional [str ], * args , ** kwargs ): # type: ignore[no-untyped-def]
323348 return None if s is None else s .upper ()
324349
325350 opts = DecodeOptions (decoder = dec )
326- assert opts .decoder ("abc" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "ABC"
351+ decoder = require_decoder (opts )
352+ assert decoder ("abc" , Charset .UTF8 , kind = DecodeKind .VALUE ) == "ABC"
327353 # For keys, custom decoder gets the raw token; no default percent-decoding happens first.
328- assert opts . decoder ("a%2Eb" , Charset .UTF8 , kind = DecodeKind .KEY ) == "A%2EB"
354+ assert decoder ("a%2Eb" , Charset .UTF8 , kind = DecodeKind .KEY ) == "A%2EB"
329355
330356 def test_decoder_wins_over_legacy_decoder_when_both_provided (self ) -> None :
331357 # decoder must take precedence over legacy_decoder (parity with Kotlin/C#)
@@ -356,3 +382,11 @@ def dec(
356382
357383 opts = DecodeOptions (decoder = dec )
358384 assert opts .decode_key ("anything" ) == "42"
385+
386+
387+ DecoderCallable = t .Callable [..., t .Optional [t .Any ]]
388+
389+
390+ def require_decoder (opts : DecodeOptions ) -> DecoderCallable :
391+ assert opts .decoder is not None
392+ return opts .decoder
0 commit comments