fresco 3.4.0__py3-none-any.whl → 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fresco might be problematic. Click here for more details.
- fresco/__init__.py +55 -56
- fresco/core.py +36 -26
- fresco/decorators.py +6 -3
- fresco/defaults.py +1 -0
- fresco/middleware.py +45 -24
- fresco/multidict.py +35 -51
- fresco/options.py +182 -70
- fresco/py.typed +0 -0
- fresco/request.py +155 -34
- fresco/requestcontext.py +3 -0
- fresco/response.py +12 -9
- fresco/routeargs.py +23 -9
- fresco/routing.py +133 -71
- fresco/static.py +1 -1
- fresco/subrequests.py +3 -5
- fresco/tests/test_core.py +4 -4
- fresco/tests/test_multidict.py +2 -2
- fresco/tests/test_options.py +59 -15
- fresco/tests/test_request.py +21 -10
- fresco/tests/test_routing.py +86 -33
- fresco/tests/util/test_http.py +1 -3
- fresco/types.py +28 -2
- fresco/util/cache.py +2 -1
- fresco/util/http.py +66 -46
- fresco/util/urls.py +13 -11
- fresco/util/wsgi.py +15 -14
- {fresco-3.4.0.dist-info → fresco-3.6.0.dist-info}/METADATA +3 -2
- fresco-3.6.0.dist-info/RECORD +58 -0
- {fresco-3.4.0.dist-info → fresco-3.6.0.dist-info}/WHEEL +1 -1
- fresco/typing.py +0 -11
- fresco-3.4.0.dist-info/RECORD +0 -57
- {fresco-3.4.0.dist-info → fresco-3.6.0.dist-info/licenses}/LICENSE.txt +0 -0
- {fresco-3.4.0.dist-info → fresco-3.6.0.dist-info}/top_level.txt +0 -0
fresco/tests/test_options.py
CHANGED
|
@@ -24,6 +24,7 @@ import sys
|
|
|
24
24
|
import pytest
|
|
25
25
|
|
|
26
26
|
from fresco.options import Options
|
|
27
|
+
from fresco.options import override_options
|
|
27
28
|
from fresco.options import parse_key_value_pairs
|
|
28
29
|
from fresco.options import dict_from_options
|
|
29
30
|
|
|
@@ -104,6 +105,24 @@ class TestOptions(object):
|
|
|
104
105
|
assert isinstance(Options().copy(), Options)
|
|
105
106
|
|
|
106
107
|
|
|
108
|
+
class TestOverrideOptions:
|
|
109
|
+
def test_override_options_with_object(self):
|
|
110
|
+
options = Options(foo=1)
|
|
111
|
+
with override_options(options, {"foo": 2, "bar": "a"}):
|
|
112
|
+
assert options["foo"] == 2
|
|
113
|
+
assert options["bar"] == "a"
|
|
114
|
+
assert options["foo"] == 1
|
|
115
|
+
assert "bar" not in options
|
|
116
|
+
|
|
117
|
+
def test_override_options_with_kwargs(self):
|
|
118
|
+
options = Options(foo=1)
|
|
119
|
+
with override_options(options, foo=2, bar="a"):
|
|
120
|
+
assert options["foo"] == 2
|
|
121
|
+
assert options["bar"] == "a"
|
|
122
|
+
assert options["foo"] == 1
|
|
123
|
+
assert "bar" not in options
|
|
124
|
+
|
|
125
|
+
|
|
107
126
|
class TestLoadKeyValuePairs:
|
|
108
127
|
def test_it_loads_strings(self):
|
|
109
128
|
assert parse_key_value_pairs({}, ["a=b"]) == {"a": "b"}
|
|
@@ -143,7 +162,6 @@ class TestLoadKeyValuePairs:
|
|
|
143
162
|
|
|
144
163
|
|
|
145
164
|
class TestLoadOptions:
|
|
146
|
-
|
|
147
165
|
def check_loadoptions(self, tmpdir, files, sources="*", tags=[], expected={}):
|
|
148
166
|
"""
|
|
149
167
|
Write the files indicated in ``sources`` to the given temporary directory,
|
|
@@ -192,6 +210,11 @@ class TestLoadOptions:
|
|
|
192
210
|
def test_it_loads_py_files(self, tmpdir):
|
|
193
211
|
self.check_loadoptions(tmpdir, {"a.py": "x = 2 * 2"}, expected={"x": 4})
|
|
194
212
|
|
|
213
|
+
def test_py_files_have_options_in_namespace(self, tmpdir):
|
|
214
|
+
self.check_loadoptions(
|
|
215
|
+
tmpdir, {"a.py": "options['foo'] = 'bar'"}, expected={"foo": "bar"}
|
|
216
|
+
)
|
|
217
|
+
|
|
195
218
|
def test_it_selects_by_tag(self, tmpdir):
|
|
196
219
|
with self.check_loadoptions(
|
|
197
220
|
tmpdir,
|
|
@@ -217,14 +240,38 @@ class TestLoadOptions:
|
|
|
217
240
|
with self.check_loadoptions(
|
|
218
241
|
tmpdir,
|
|
219
242
|
{
|
|
220
|
-
"a": "a =
|
|
221
|
-
"a.dev.txt": "a = ${a}
|
|
222
|
-
"a.local.txt": "a = ${a}
|
|
223
|
-
"b.dev.txt": "a = ${a}
|
|
243
|
+
"a": "a = 'a'",
|
|
244
|
+
"a.0-dev.txt": "a = ${a} a.dev",
|
|
245
|
+
"a.local.txt": "a = ${a} a.local",
|
|
246
|
+
"b.dev.txt": "a = ${a} b.dev",
|
|
247
|
+
},
|
|
248
|
+
) as loadopts:
|
|
249
|
+
assert loadopts("*", ["dev", "local"]) == {"a": "a a.dev b.dev a.local"}
|
|
250
|
+
assert loadopts("*", ["local", "dev"]) == {"a": "a a.local a.dev b.dev"}
|
|
251
|
+
|
|
252
|
+
def test_it_loads_in_priority_order(self, tmpdir):
|
|
253
|
+
with self.check_loadoptions(
|
|
254
|
+
tmpdir,
|
|
255
|
+
{
|
|
256
|
+
"a": "a = 'a'",
|
|
257
|
+
"a.100-dev.txt": "a = ${a} a.100-dev",
|
|
258
|
+
"a.local.txt": "a = ${a} a.local",
|
|
259
|
+
"b.dev.txt": "a = ${a} b.dev",
|
|
224
260
|
},
|
|
225
261
|
) as loadopts:
|
|
226
|
-
assert loadopts("*", ["dev", "local"]) == {"a": "
|
|
227
|
-
assert loadopts("*", ["local", "dev"]) == {"a": "
|
|
262
|
+
assert loadopts("*", ["dev", "local"]) == {"a": "a b.dev a.local a.100-dev"}
|
|
263
|
+
assert loadopts("*", ["local", "dev"]) == {"a": "a a.local b.dev a.100-dev"}
|
|
264
|
+
|
|
265
|
+
def test_it_loads_in_priority_order_without_tags(self, tmpdir):
|
|
266
|
+
with self.check_loadoptions(
|
|
267
|
+
tmpdir,
|
|
268
|
+
{
|
|
269
|
+
"a": "a = a",
|
|
270
|
+
"b.100": "a = ${a} b",
|
|
271
|
+
"a.200.txt": "a = ${a} 100",
|
|
272
|
+
},
|
|
273
|
+
) as loadopts:
|
|
274
|
+
assert loadopts("*") == {"a": "a b 100"}
|
|
228
275
|
|
|
229
276
|
def test_it_loads_from_os_environ(self, tmpdir):
|
|
230
277
|
with setenv(a="2"):
|
|
@@ -252,7 +299,7 @@ class TestLoadOptions:
|
|
|
252
299
|
tmpdir,
|
|
253
300
|
{"a.txt": "a=1", "b.txt": "b=1"},
|
|
254
301
|
sources=["a.*", "b.*"],
|
|
255
|
-
expected={"a": 1, "b": 1}
|
|
302
|
+
expected={"a": 1, "b": 1},
|
|
256
303
|
)
|
|
257
304
|
|
|
258
305
|
def test_it_substitutes_from_environment_variables(self, tmpdir):
|
|
@@ -261,7 +308,7 @@ class TestLoadOptions:
|
|
|
261
308
|
tmpdir,
|
|
262
309
|
{"a.txt": "a=1", "a.bar.txt": "a=2"},
|
|
263
310
|
tags=["{FOO}"],
|
|
264
|
-
expected={"a": 2}
|
|
311
|
+
expected={"a": 2},
|
|
265
312
|
)
|
|
266
313
|
|
|
267
314
|
with setenv(FOO="baz"):
|
|
@@ -269,7 +316,7 @@ class TestLoadOptions:
|
|
|
269
316
|
tmpdir,
|
|
270
317
|
{"a.txt": "a=1", "a.bar.txt": "a=2"},
|
|
271
318
|
tags=["{FOO}"],
|
|
272
|
-
expected={"a": 1}
|
|
319
|
+
expected={"a": 1},
|
|
273
320
|
)
|
|
274
321
|
|
|
275
322
|
def test_it_allows_missing_environment_variables(self, tmpdir):
|
|
@@ -278,19 +325,16 @@ class TestLoadOptions:
|
|
|
278
325
|
tmpdir,
|
|
279
326
|
{"a.txt": "a=1", "a.bar.txt": "a=2"},
|
|
280
327
|
tags=["{FOO}"],
|
|
281
|
-
expected={"a": 1}
|
|
328
|
+
expected={"a": 1},
|
|
282
329
|
)
|
|
283
330
|
|
|
284
331
|
|
|
285
332
|
class TestDictFromOptions:
|
|
286
|
-
|
|
287
333
|
def test_it_splits_on_prefix(self):
|
|
288
|
-
|
|
289
334
|
options = Options(FOO_BAR=1, FOO_BAZ=2, FOO_BAR_BAZ=3, BAR=4)
|
|
290
335
|
assert dict_from_options("FOO_", options) == {"BAR": 1, "BAZ": 2, "BAR_BAZ": 3}
|
|
291
336
|
|
|
292
337
|
def test_it_splits_recursively(self):
|
|
293
|
-
|
|
294
338
|
options = Options(
|
|
295
339
|
A_A=1,
|
|
296
340
|
A_B_C_D=2,
|
|
@@ -303,7 +347,7 @@ class TestDictFromOptions:
|
|
|
303
347
|
"A": 1,
|
|
304
348
|
"B": {"C": {"D": 2}, "E": 3},
|
|
305
349
|
"F": {"G": {"H": 4}},
|
|
306
|
-
"I": 5
|
|
350
|
+
"I": 5,
|
|
307
351
|
}
|
|
308
352
|
|
|
309
353
|
|
fresco/tests/test_request.py
CHANGED
|
@@ -99,7 +99,7 @@ class TestRequestProperties(object):
|
|
|
99
99
|
CONTENT_LENGTH="2",
|
|
100
100
|
CONTENT_TYPE="text/plain; charset=UTF-8",
|
|
101
101
|
) as c:
|
|
102
|
-
c.request.body_bytes == b"\xc3a"
|
|
102
|
+
assert c.request.body_bytes == b"\xc3a"
|
|
103
103
|
|
|
104
104
|
def test_get_json_decodes_json(self):
|
|
105
105
|
with context(
|
|
@@ -107,7 +107,7 @@ class TestRequestProperties(object):
|
|
|
107
107
|
CONTENT_LENGTH="14",
|
|
108
108
|
CONTENT_TYPE="application/json",
|
|
109
109
|
) as c:
|
|
110
|
-
c.request.get_json() == {"foo": "bar"}
|
|
110
|
+
assert c.request.get_json() == {"foo": "bar"}
|
|
111
111
|
|
|
112
112
|
def test_get_json_ignores_mime_type(self):
|
|
113
113
|
with context(
|
|
@@ -115,7 +115,7 @@ class TestRequestProperties(object):
|
|
|
115
115
|
CONTENT_LENGTH="14",
|
|
116
116
|
CONTENT_TYPE="application/broken",
|
|
117
117
|
) as c:
|
|
118
|
-
c.request.get_json() == {"foo": "bar"}
|
|
118
|
+
assert c.request.get_json() == {"foo": "bar"}
|
|
119
119
|
|
|
120
120
|
def test_get_json_passes_args_to_decoder(self):
|
|
121
121
|
with context(
|
|
@@ -123,19 +123,30 @@ class TestRequestProperties(object):
|
|
|
123
123
|
CONTENT_LENGTH="10",
|
|
124
124
|
CONTENT_TYPE="application/broken",
|
|
125
125
|
) as c:
|
|
126
|
-
c.request.get_json(parse_int=lambda s: s + "!") == {"foo": "1!"}
|
|
126
|
+
assert c.request.get_json(parse_int=lambda s: s + "!") == {"foo": "1!"}
|
|
127
127
|
|
|
128
|
-
def
|
|
128
|
+
def test_get_required_raises_badrequest(self):
|
|
129
129
|
with context(
|
|
130
130
|
QUERY_STRING="x=10",
|
|
131
131
|
CONTENT_LENGTH="10",
|
|
132
132
|
CONTENT_TYPE="application/broken",
|
|
133
133
|
) as c:
|
|
134
|
-
|
|
135
|
-
assert
|
|
134
|
+
req = c.request
|
|
135
|
+
assert req.get("x", required=True) == "10"
|
|
136
136
|
with pytest.raises(exceptions.BadRequest):
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
req.get("y", required=True)
|
|
138
|
+
|
|
139
|
+
def test_get_does_type_conversion(self):
|
|
140
|
+
with context(
|
|
141
|
+
QUERY_STRING="x=10",
|
|
142
|
+
CONTENT_LENGTH="10",
|
|
143
|
+
CONTENT_TYPE="application/broken",
|
|
144
|
+
) as c:
|
|
145
|
+
req = c.request
|
|
146
|
+
assert req.get("x") == "10"
|
|
147
|
+
assert req.get("x", type=int) == 10
|
|
148
|
+
assert req.get("y", type=int) is None
|
|
149
|
+
assert req.get("y", "20", type=int) == "20"
|
|
139
150
|
|
|
140
151
|
def test_is_secure_returns_correct_value(self):
|
|
141
152
|
with context("https://example.org/") as c:
|
|
@@ -150,7 +161,7 @@ class TestRequestProperties(object):
|
|
|
150
161
|
def test_getint_raises_badrequest(self):
|
|
151
162
|
with pytest.raises(exceptions.BadRequest):
|
|
152
163
|
with context("http://example.org/") as c:
|
|
153
|
-
c.request.getint("a")
|
|
164
|
+
c.request.getint("a", required=True)
|
|
154
165
|
|
|
155
166
|
with pytest.raises(exceptions.BadRequest):
|
|
156
167
|
with context("http://example.org/?a=four") as c:
|
fresco/tests/test_routing.py
CHANGED
|
@@ -24,6 +24,7 @@ import tms
|
|
|
24
24
|
|
|
25
25
|
from fresco import FrescoApp
|
|
26
26
|
from fresco.core import urlfor
|
|
27
|
+
from fresco.request import Request
|
|
27
28
|
from fresco.exceptions import NotFound
|
|
28
29
|
from fresco.response import Response
|
|
29
30
|
from fresco.routing import ALL_METHODS
|
|
@@ -31,6 +32,7 @@ from fresco.routing import ALL
|
|
|
31
32
|
from fresco.routing import GET
|
|
32
33
|
from fresco.routing import OPTIONS
|
|
33
34
|
from fresco.routing import POST
|
|
35
|
+
from fresco.routing import _has_request_parameter
|
|
34
36
|
from fresco.routing import (
|
|
35
37
|
Route,
|
|
36
38
|
DelegateRoute,
|
|
@@ -45,6 +47,10 @@ from fresco.routing import (
|
|
|
45
47
|
from . import fixtures
|
|
46
48
|
|
|
47
49
|
|
|
50
|
+
def dummyview() -> Response:
|
|
51
|
+
return Response()
|
|
52
|
+
|
|
53
|
+
|
|
48
54
|
def assert_method_bound_to(method, ob):
|
|
49
55
|
try:
|
|
50
56
|
assert method.__self__ is ob
|
|
@@ -347,36 +353,36 @@ class TestPredicates(object):
|
|
|
347
353
|
|
|
348
354
|
class TestRouteNames(object):
|
|
349
355
|
def test_name_present_in_route_keys(self):
|
|
350
|
-
r = Route("/", GET, None, name="foo")
|
|
356
|
+
r = Route("/", GET, lambda: None, name="foo")
|
|
351
357
|
assert "foo" in list(r.route_keys())
|
|
352
358
|
|
|
353
359
|
def test_name_with_other_kwargs(self):
|
|
354
|
-
r = Route("/", GET, None, name="foo", x="bar")
|
|
360
|
+
r = Route("/", GET, lambda: None, name="foo", x="bar")
|
|
355
361
|
assert "foo" in list(r.route_keys())
|
|
356
362
|
|
|
357
363
|
def test_name_cannot_contain_colon(self):
|
|
358
364
|
with pytest.raises(ValueError):
|
|
359
|
-
Route("/", GET, None, name="foo:bar")
|
|
365
|
+
Route("/", GET, lambda: None, name="foo:bar")
|
|
360
366
|
|
|
361
367
|
|
|
362
368
|
class TestRouteCollection(object):
|
|
363
369
|
def test_it_adds_routes_from_constructor(self):
|
|
364
|
-
r1 = Route("/1", GET,
|
|
365
|
-
r2 = Route("/2", POST,
|
|
370
|
+
r1 = Route("/1", GET, dummyview, name="1")
|
|
371
|
+
r2 = Route("/2", POST, dummyview, name="2")
|
|
366
372
|
rc = RouteCollection([r1, r2])
|
|
367
373
|
assert [r.name for r in rc] == ["1", "2"]
|
|
368
374
|
|
|
369
375
|
def test_it_adds_routecollections_from_constructor(self):
|
|
370
|
-
r1 = Route("/", GET,
|
|
371
|
-
r2 = Route("/", POST,
|
|
372
|
-
r3 = Route("/", POST,
|
|
376
|
+
r1 = Route("/", GET, dummyview, name="1")
|
|
377
|
+
r2 = Route("/", POST, dummyview, name="2")
|
|
378
|
+
r3 = Route("/", POST, dummyview, name="3")
|
|
373
379
|
rc = RouteCollection([r1, RouteCollection([r2, r3])])
|
|
374
380
|
assert [r.name for r in rc] == ["1", "2", "3"]
|
|
375
381
|
|
|
376
382
|
def test_it_adds_dunderroutes_from_constructor(self):
|
|
377
|
-
r1 = Route("/", GET,
|
|
378
|
-
r2 = Route("/", POST,
|
|
379
|
-
r3 = Route("/", POST,
|
|
383
|
+
r1 = Route("/", GET, dummyview, name="1")
|
|
384
|
+
r2 = Route("/", POST, dummyview, name="2")
|
|
385
|
+
r3 = Route("/", POST, dummyview, name="3")
|
|
380
386
|
|
|
381
387
|
class A:
|
|
382
388
|
__routes__ = [r2, r3]
|
|
@@ -385,8 +391,8 @@ class TestRouteCollection(object):
|
|
|
385
391
|
assert [r.name for r in rc] == ["1", "2", "3"]
|
|
386
392
|
|
|
387
393
|
def test_get_routes_matches_on_method(self):
|
|
388
|
-
r_get = Route("/", GET,
|
|
389
|
-
r_post = Route("/", POST,
|
|
394
|
+
r_get = Route("/", GET, dummyview)
|
|
395
|
+
r_post = Route("/", POST, dummyview)
|
|
390
396
|
|
|
391
397
|
rc = RouteCollection([r_post, r_get])
|
|
392
398
|
|
|
@@ -394,8 +400,8 @@ class TestRouteCollection(object):
|
|
|
394
400
|
assert [r.route for r in rc.get_route_traversals("/", POST)] == [r_post]
|
|
395
401
|
|
|
396
402
|
def test_get_routes_matches_on_path(self):
|
|
397
|
-
r1 = Route("/1", GET,
|
|
398
|
-
r2 = Route("/2", GET,
|
|
403
|
+
r1 = Route("/1", GET, dummyview)
|
|
404
|
+
r2 = Route("/2", GET, dummyview)
|
|
399
405
|
|
|
400
406
|
rc = RouteCollection([r1, r2])
|
|
401
407
|
|
|
@@ -403,8 +409,8 @@ class TestRouteCollection(object):
|
|
|
403
409
|
assert [r.route for r in rc.get_route_traversals("/2", GET)] == [r2]
|
|
404
410
|
|
|
405
411
|
def test_get_routes_can_match_all_methods(self):
|
|
406
|
-
r1 = Route("/1", GET,
|
|
407
|
-
r2 = Route("/1", POST,
|
|
412
|
+
r1 = Route("/1", GET, dummyview)
|
|
413
|
+
r2 = Route("/1", POST, dummyview)
|
|
408
414
|
|
|
409
415
|
rc = RouteCollection([r1, r2])
|
|
410
416
|
assert [r.route for r in rc.get_route_traversals("/1", None)] == [
|
|
@@ -416,8 +422,8 @@ class TestRouteCollection(object):
|
|
|
416
422
|
a = RouteCollection()
|
|
417
423
|
b = RouteCollection()
|
|
418
424
|
|
|
419
|
-
a_route = Route("/harvey", GET,
|
|
420
|
-
b_route = Route("/harvey", GET,
|
|
425
|
+
a_route = Route("/harvey", GET, dummyview)
|
|
426
|
+
b_route = Route("/harvey", GET, dummyview)
|
|
421
427
|
|
|
422
428
|
a.add_route(a_route)
|
|
423
429
|
b.add_route(b_route)
|
|
@@ -428,7 +434,7 @@ class TestRouteCollection(object):
|
|
|
428
434
|
a.add_route(a_delegate_route)
|
|
429
435
|
b.add_route(b_delegate_route)
|
|
430
436
|
|
|
431
|
-
r = next(a.get_route_traversals("/rabbit/hole/rabbit/harvey",
|
|
437
|
+
r = next(a.get_route_traversals("/rabbit/hole/rabbit/harvey", "GET"))
|
|
432
438
|
|
|
433
439
|
assert r.collections_traversed == [
|
|
434
440
|
(a, "", a_delegate_route, (), {}, (), {}),
|
|
@@ -525,7 +531,9 @@ class TestRouteCollection(object):
|
|
|
525
531
|
]
|
|
526
532
|
|
|
527
533
|
def test_add_prefix_returns_prefixed_collection(self):
|
|
528
|
-
rc = RouteCollection(
|
|
534
|
+
rc = RouteCollection(
|
|
535
|
+
[Route("/fish", GET, dummyview), Route("/beans", GET, dummyview)]
|
|
536
|
+
)
|
|
529
537
|
prefixed = rc.add_prefix("/jelly")
|
|
530
538
|
assert [str(r.pattern) for r in prefixed] == [
|
|
531
539
|
"/jelly/fish",
|
|
@@ -628,15 +636,14 @@ class TestRouteCollection(object):
|
|
|
628
636
|
assert list(response.content_iterator) == [b"ok"]
|
|
629
637
|
|
|
630
638
|
def test_it_isolates_request_for_wsgi_app(self):
|
|
631
|
-
|
|
632
639
|
params: t.Dict[str, t.Any] = {}
|
|
633
640
|
outerapp = FrescoApp()
|
|
634
641
|
innerapp = FrescoApp()
|
|
635
642
|
innerapp.route("/y", GET=Response)
|
|
636
643
|
outerapp.route_wsgi("/x", innerapp)
|
|
637
644
|
|
|
638
|
-
innerapp.process_request(partial(params.__setitem__,
|
|
639
|
-
outerapp.process_request(partial(params.__setitem__,
|
|
645
|
+
innerapp.process_request(partial(params.__setitem__, "inner_request"))
|
|
646
|
+
outerapp.process_request(partial(params.__setitem__, "outer_request"))
|
|
640
647
|
|
|
641
648
|
with outerapp.requestcontext("/x/y"):
|
|
642
649
|
outerapp.view()
|
|
@@ -911,25 +918,25 @@ class TestRRoute:
|
|
|
911
918
|
|
|
912
919
|
class TestViewArgs(object):
|
|
913
920
|
def test_it_uses_args(self):
|
|
914
|
-
routes = RouteCollection([Route("/", GET,
|
|
921
|
+
routes = RouteCollection([Route("/", GET, dummyview, args=(1, 2))])
|
|
915
922
|
assert list(routes.get_route_traversals("/", GET)) == [
|
|
916
923
|
tms.InstanceOf(RouteTraversal, args=(1, 2))
|
|
917
924
|
]
|
|
918
925
|
|
|
919
926
|
def test_it_uses_view_args(self):
|
|
920
|
-
routes = RouteCollection([Route("/", GET,
|
|
927
|
+
routes = RouteCollection([Route("/", GET, dummyview, view_args=(1, 2))])
|
|
921
928
|
assert list(routes.get_route_traversals("/", GET)) == [
|
|
922
929
|
tms.InstanceOf(RouteTraversal, args=(1, 2))
|
|
923
930
|
]
|
|
924
931
|
|
|
925
932
|
def test_it_appends_args_extracted_from_path(self):
|
|
926
|
-
routes = RouteCollection([Route("/<:int>", GET,
|
|
933
|
+
routes = RouteCollection([Route("/<:int>", GET, dummyview, view_args=(1, 2))])
|
|
927
934
|
assert list(routes.get_route_traversals("/3", GET)) == [
|
|
928
935
|
tms.InstanceOf(RouteTraversal, args=(1, 2, 3))
|
|
929
936
|
]
|
|
930
937
|
|
|
931
938
|
def test_it_keeps_traversal_args_separate(self):
|
|
932
|
-
routes = RouteCollection([Route("/<:int>", GET,
|
|
939
|
+
routes = RouteCollection([Route("/<:int>", GET, dummyview, view_args=(1,))])
|
|
933
940
|
assert list(routes.get_route_traversals("/2", GET)) == [
|
|
934
941
|
tms.InstanceOf(
|
|
935
942
|
RouteTraversal,
|
|
@@ -945,25 +952,27 @@ class TestViewArgs(object):
|
|
|
945
952
|
|
|
946
953
|
class TestViewKwargs(object):
|
|
947
954
|
def test_it_reads_from_route_kwargs(self):
|
|
948
|
-
routes = RouteCollection([Route("/", GET,
|
|
955
|
+
routes = RouteCollection([Route("/", GET, dummyview, x=1)])
|
|
949
956
|
assert list(routes.get_route_traversals("/", GET)) == [
|
|
950
957
|
tms.InstanceOf(RouteTraversal, kwargs={"x": 1})
|
|
951
958
|
]
|
|
952
959
|
|
|
953
960
|
def test_it_reads_from_kwargs(self):
|
|
954
|
-
routes = RouteCollection([Route("/", GET,
|
|
961
|
+
routes = RouteCollection([Route("/", GET, dummyview, kwargs={"x": 1})])
|
|
955
962
|
assert list(routes.get_route_traversals("/", GET)) == [
|
|
956
963
|
tms.InstanceOf(RouteTraversal, kwargs={"x": 1})
|
|
957
964
|
]
|
|
958
965
|
|
|
959
966
|
def test_it_reads_from_view_kwargs(self):
|
|
960
|
-
routes = RouteCollection([Route("/", GET,
|
|
967
|
+
routes = RouteCollection([Route("/", GET, dummyview, view_kwargs={"x": 1})])
|
|
961
968
|
assert list(routes.get_route_traversals("/", GET)) == [
|
|
962
969
|
tms.InstanceOf(RouteTraversal, kwargs={"x": 1})
|
|
963
970
|
]
|
|
964
971
|
|
|
965
972
|
def test_it_keeps_traversal_kwargs_separate(self):
|
|
966
|
-
routes = RouteCollection(
|
|
973
|
+
routes = RouteCollection(
|
|
974
|
+
[Route("/<x:int>", GET, dummyview, view_kwargs={"y": 1})]
|
|
975
|
+
)
|
|
967
976
|
assert list(routes.get_route_traversals("/2", GET)) == [
|
|
968
977
|
tms.InstanceOf(
|
|
969
978
|
RouteTraversal,
|
|
@@ -1130,7 +1139,6 @@ class TestRouteCache(object):
|
|
|
1130
1139
|
|
|
1131
1140
|
|
|
1132
1141
|
class TestRouteAll:
|
|
1133
|
-
|
|
1134
1142
|
def test_route_all_matches_on_separator(self):
|
|
1135
1143
|
def view():
|
|
1136
1144
|
return Response()
|
|
@@ -1141,3 +1149,48 @@ class TestRouteAll:
|
|
|
1141
1149
|
assert len(list(app.get_route_traversals("/x", GET))) == 1
|
|
1142
1150
|
assert len(list(app.get_route_traversals("/x/y", GET))) == 1
|
|
1143
1151
|
assert len(list(app.get_route_traversals("/xy", GET))) == 0
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
class TestRequestParameter:
|
|
1155
|
+
def test_has_request_parameter(self):
|
|
1156
|
+
def a(request: Request):
|
|
1157
|
+
pass
|
|
1158
|
+
|
|
1159
|
+
def b(request: t.Optional[Request]):
|
|
1160
|
+
pass
|
|
1161
|
+
|
|
1162
|
+
def c(request: t.Union[Request, None]):
|
|
1163
|
+
pass
|
|
1164
|
+
|
|
1165
|
+
def d(request: t.Union[None, Request]):
|
|
1166
|
+
pass
|
|
1167
|
+
|
|
1168
|
+
def e(request: int):
|
|
1169
|
+
pass
|
|
1170
|
+
|
|
1171
|
+
def f(request):
|
|
1172
|
+
pass
|
|
1173
|
+
|
|
1174
|
+
assert _has_request_parameter(a) is True
|
|
1175
|
+
assert _has_request_parameter(b) is True
|
|
1176
|
+
assert _has_request_parameter(c) is True
|
|
1177
|
+
assert _has_request_parameter(d) is True
|
|
1178
|
+
assert _has_request_parameter(e) is False
|
|
1179
|
+
assert _has_request_parameter(f) is False
|
|
1180
|
+
|
|
1181
|
+
def test_request_is_provided_automatically(self):
|
|
1182
|
+
def a(request: Request):
|
|
1183
|
+
return Response("a")
|
|
1184
|
+
|
|
1185
|
+
def b():
|
|
1186
|
+
return Response("b")
|
|
1187
|
+
|
|
1188
|
+
app = FrescoApp()
|
|
1189
|
+
app.route("/a", GET=a)
|
|
1190
|
+
app.route("/b", GET=b)
|
|
1191
|
+
|
|
1192
|
+
with app.requestcontext("/a"):
|
|
1193
|
+
assert app.view().content == "a"
|
|
1194
|
+
|
|
1195
|
+
with app.requestcontext("/b"):
|
|
1196
|
+
assert app.view().content == "b"
|
fresco/tests/util/test_http.py
CHANGED
|
@@ -42,7 +42,7 @@ class TestParseQueryString(object):
|
|
|
42
42
|
return list(parse_querystring(value))
|
|
43
43
|
|
|
44
44
|
def test_empty(self):
|
|
45
|
-
self.p("") == []
|
|
45
|
+
assert self.p("") == []
|
|
46
46
|
|
|
47
47
|
def test_simple_key_value(self):
|
|
48
48
|
assert self.p("a=b") == [("a", "b")]
|
|
@@ -276,12 +276,10 @@ class TestParseFormEncodedData(object):
|
|
|
276
276
|
class TestEncodeMultipart(object):
|
|
277
277
|
def test_it_encodes_a_data_dict(self):
|
|
278
278
|
data, headers = encode_multipart([("foo", "bar baf")])
|
|
279
|
-
data = data.getvalue()
|
|
280
279
|
assert b'Content-Disposition: form-data; name="foo"\r\n\r\nbar baf' in data
|
|
281
280
|
|
|
282
281
|
def test_it_encodes_a_file_tuple(self):
|
|
283
282
|
data, headers = encode_multipart(files=[("foo", "foo.txt", "ascii", "bar")])
|
|
284
|
-
data = data.getvalue()
|
|
285
283
|
expected = (
|
|
286
284
|
b"Content-Disposition: form-data; "
|
|
287
285
|
b'name="foo"; filename="foo.txt"\r\n'
|
fresco/types.py
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from types import TracebackType
|
|
5
|
+
from typing import Any
|
|
6
|
+
from typing import Optional
|
|
1
7
|
import typing as t
|
|
2
8
|
|
|
9
|
+
import fresco.response # noqa
|
|
10
|
+
|
|
11
|
+
|
|
3
12
|
QuerySpec = t.Union[
|
|
4
|
-
|
|
5
|
-
|
|
13
|
+
Mapping[str, Any],
|
|
14
|
+
Iterable[tuple[str, Any]]
|
|
15
|
+
]
|
|
16
|
+
ViewCallable = Callable[..., "fresco.response.Response"]
|
|
17
|
+
ExcInfo = tuple[type[BaseException], BaseException, TracebackType]
|
|
18
|
+
OptionalExcInfo = Optional[ExcInfo]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
HeaderList = list[tuple[str, str]]
|
|
22
|
+
HeadersList = HeaderList
|
|
23
|
+
WSGIEnviron = dict[str, Any]
|
|
24
|
+
WriteCallable = Callable[[bytes], object]
|
|
25
|
+
StartResponse = Callable[[str, HeaderList, OptionalExcInfo], WriteCallable]
|
|
26
|
+
WSGIApplication = Callable[
|
|
27
|
+
[
|
|
28
|
+
WSGIEnviron,
|
|
29
|
+
StartResponse,
|
|
30
|
+
],
|
|
31
|
+
Iterable[bytes]
|
|
6
32
|
]
|
fresco/util/cache.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from typing import Any
|
|
2
|
+
from typing import Callable
|
|
2
3
|
from typing import Dict
|
|
3
4
|
from typing import List
|
|
4
5
|
import sys
|
|
@@ -46,7 +47,7 @@ def cache_generator(original_function, maxsize):
|
|
|
46
47
|
link[NEXT] = root
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
def make_cache(original_function, maxsize=100):
|
|
50
|
+
def make_cache(original_function, maxsize=100) -> Callable[[Any], Any]:
|
|
50
51
|
"Create a cache around a function that takes a single argument"
|
|
51
52
|
c = cache_generator(original_function, maxsize)
|
|
52
53
|
next(c)
|