fresco 3.3.1__py3-none-any.whl → 3.3.3__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.

@@ -0,0 +1,448 @@
1
+ # encoding=UTF-8
2
+ # Copyright 2015 Oliver Cope
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ from io import BytesIO
17
+ import datetime
18
+
19
+ import pytest
20
+
21
+ from fresco import FrescoApp, exceptions
22
+ from fresco.tests import fixtures
23
+
24
+ context = FrescoApp().requestcontext
25
+
26
+
27
+ class TestRequestProperties(object):
28
+ def test_url_script_name_only(self):
29
+ with context(SCRIPT_NAME="/foo/bar", PATH_INFO="") as c:
30
+ assert c.request.url == "http://localhost/foo/bar"
31
+
32
+ def test_url_script_name_path_info(self):
33
+ with context(SCRIPT_NAME="/foo/bar", PATH_INFO="/baz") as c:
34
+ assert c.request.url == "http://localhost/foo/bar/baz"
35
+
36
+ def test_url_normalizes_host_port(self):
37
+ with context(HTTP_HOST="localhost:80") as c:
38
+ assert c.request.url == "http://localhost/"
39
+ with context(HTTP_HOST="localhost:81") as c:
40
+ assert c.request.url == "http://localhost:81/"
41
+
42
+ def test_url_normalizes_host_ssl_port(self):
43
+ with context(
44
+ environ={"wsgi.url_scheme": "https"}, HTTP_HOST="localhost:443"
45
+ ) as c:
46
+ assert c.request.url == "https://localhost/"
47
+
48
+ def test_url_ignores_server_port_if_host_header_present(self):
49
+ with context(
50
+ environ={"wsgi.url_scheme": "https", "SERVER_PORT": "81"},
51
+ HTTP_HOST="localhost",
52
+ ) as c:
53
+ assert c.request.url == "https://localhost/"
54
+
55
+ def test_as_string(self):
56
+ with context("http://example.org/foo?bar=baz") as c:
57
+ assert str(c.request) == "<Request GET http://example.org/foo?bar=baz>"
58
+
59
+ def test_url_returns_full_url(self):
60
+ with context("http://example.org/foo?bar=baz") as c:
61
+ assert c.request.url == "http://example.org/foo?bar=baz"
62
+
63
+ def test_path_returns_correct_path_when_script_name_empty(self):
64
+ with context(SCRIPT_NAME="", PATH_INFO="/foo/bar") as c:
65
+ assert c.request.path == "/foo/bar"
66
+
67
+ def test_path_returns_correct_path(self):
68
+ with context(SCRIPT_NAME="/foo", PATH_INFO="/bar") as c:
69
+ assert c.request.path == "/foo/bar"
70
+
71
+ def test_query_decodes_unicode(self):
72
+ with context("/?q=%C3%A0") as c:
73
+ assert c.request.query["q"] == b"\xc3\xa0".decode("utf8")
74
+
75
+ def test_form_decodes_unicode(self):
76
+ with context("/?q=%C3%A0") as c:
77
+ assert c.request.form["q"] == b"\xc3\xa0".decode("utf8")
78
+
79
+ def test_body_decodes_unicode(self):
80
+ with context(
81
+ wsgi_input=b"\xc3\xa0",
82
+ CONTENT_LENGTH="2",
83
+ CONTENT_TYPE="text/plain; charset=UTF-8",
84
+ ) as c:
85
+ assert c.request.body == b"\xc3\xa0".decode("UTF-8")
86
+
87
+ def test_body_raises_bad_request_for_invalid_encoding(self):
88
+ with context(
89
+ wsgi_input=b"\xc3a",
90
+ CONTENT_LENGTH="2",
91
+ CONTENT_TYPE="text/plain; charset=UTF-8",
92
+ ) as c:
93
+ with pytest.raises(exceptions.BadRequest):
94
+ c.request.body
95
+
96
+ def test_body_bytes_does_not_decode(self):
97
+ with context(
98
+ wsgi_input=b"\xc3a",
99
+ CONTENT_LENGTH="2",
100
+ CONTENT_TYPE="text/plain; charset=UTF-8",
101
+ ) as c:
102
+ c.request.body_bytes == b"\xc3a"
103
+
104
+ def test_get_json_decodes_json(self):
105
+ with context(
106
+ wsgi_input=b'{"foo": "bar"}',
107
+ CONTENT_LENGTH="14",
108
+ CONTENT_TYPE="application/json",
109
+ ) as c:
110
+ c.request.get_json() == {"foo": "bar"}
111
+
112
+ def test_get_json_ignores_mime_type(self):
113
+ with context(
114
+ wsgi_input=b'{"foo": "bar"}',
115
+ CONTENT_LENGTH="14",
116
+ CONTENT_TYPE="application/broken",
117
+ ) as c:
118
+ c.request.get_json() == {"foo": "bar"}
119
+
120
+ def test_get_json_passes_args_to_decoder(self):
121
+ with context(
122
+ wsgi_input=b'{"foo": 1}',
123
+ CONTENT_LENGTH="10",
124
+ CONTENT_TYPE="application/broken",
125
+ ) as c:
126
+ c.request.get_json(parse_int=lambda s: s + "!") == {"foo": "1!"}
127
+
128
+ def test_get_does_type_conversion(self):
129
+ with context(
130
+ QUERY_STRING="x=10",
131
+ CONTENT_LENGTH="10",
132
+ CONTENT_TYPE="application/broken",
133
+ ) as c:
134
+ assert c.request.get("x") == "10"
135
+ assert c.request.get("x", type=int) == 10
136
+ with pytest.raises(exceptions.BadRequest):
137
+ c.request.get("y", type=int)
138
+ assert c.request.get("y", None, type=int) is None
139
+
140
+ def test_is_secure_returns_correct_value(self):
141
+ with context("https://example.org/") as c:
142
+ assert c.request.is_secure is True
143
+ with context("http://example.org/") as c:
144
+ assert c.request.is_secure is False
145
+
146
+ def test_getint_returns_int(self):
147
+ with context("http://example.org/?a=4") as c:
148
+ assert c.request.getint("a") == 4
149
+
150
+ def test_getint_raises_badrequest(self):
151
+ with pytest.raises(exceptions.BadRequest):
152
+ with context("http://example.org/") as c:
153
+ c.request.getint("a")
154
+
155
+ with pytest.raises(exceptions.BadRequest):
156
+ with context("http://example.org/?a=four") as c:
157
+ c.request.getint("a")
158
+
159
+ with pytest.raises(exceptions.BadRequest):
160
+ with context("http://example.org/?a=four") as c:
161
+ assert c.request.getint("a", 0) == 0
162
+
163
+ def test_getint_returns_default(self):
164
+ with context("http://example.org/") as c:
165
+ assert c.request.getint("a", 0) == 0
166
+
167
+ def test_now(self):
168
+ with context("http://example.org/") as c:
169
+ now = c.request.now
170
+ assert now.tzinfo is datetime.timezone.utc
171
+ assert now is c.request.now
172
+
173
+
174
+ class TestPathEncoding(object):
175
+ def test_url_is_quoted(self):
176
+ with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
177
+ assert c.request.url == "http://localhost" + fixtures.quoted_unicode_path
178
+
179
+ def test_application_url_is_quoted(self):
180
+ with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
181
+ assert (
182
+ c.request.application_url
183
+ == "http://localhost" + fixtures.quoted_unicode_path
184
+ )
185
+
186
+ def test_parsed_url_is_quoted(self):
187
+ with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
188
+ assert c.request.parsed_url.path == fixtures.quoted_unicode_path
189
+
190
+ def test_path_is_unquoted(self):
191
+ with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
192
+ assert c.request.path == fixtures.unquoted_unicode_path
193
+
194
+ def test_script_name_is_unquoted(self):
195
+ with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
196
+ assert c.request.script_name == fixtures.unquoted_unicode_path
197
+
198
+ def test_path_info_is_unquoted(self):
199
+ with context(PATH_INFO=fixtures.wsgi_unicode_path) as c:
200
+ assert c.request.path_info == fixtures.unquoted_unicode_path
201
+
202
+ def test_invalid_path_info_encoding_raises_bad_request(self):
203
+ with context(PATH_INFO=fixtures.misquoted_wsgi_unicode_path) as c:
204
+ with pytest.raises(exceptions.BadRequest):
205
+ c.request.path_info
206
+
207
+ def test_invalid_script_name_encoding_raises_bad_request(self):
208
+ with context(SCRIPT_NAME=fixtures.misquoted_wsgi_unicode_path) as c:
209
+ with pytest.raises(exceptions.BadRequest):
210
+ c.request.script_name
211
+
212
+
213
+ class TestMakeURL(object):
214
+ def assert_equal_with_query(self, url1, url2):
215
+ if "?" not in url1:
216
+ assert url1 == url2
217
+ return
218
+
219
+ base1, _, q1 = url1.partition("?")
220
+ base2, _, q2 = url2.partition("?")
221
+ assert base1 == base2
222
+ assert sorted(q1.split(";")) == sorted(q2.split(";"))
223
+
224
+ def test_it_returns_request_url(self):
225
+ with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
226
+ assert c.request.make_url() == "http://localhost/script/pathinfo"
227
+
228
+ def test_it_doesnt_double_quote_request_url(self):
229
+ with context(SCRIPT_NAME="/script name", PATH_INFO="/path info") as c:
230
+ assert c.request.make_url() == "http://localhost/script%20name/path%20info"
231
+
232
+ def test_it_doesnt_double_quote_supplied_path_info(self):
233
+ with context() as c:
234
+ assert c.request.make_url(PATH_INFO="/x y") == "http://localhost/x%20y"
235
+
236
+ def test_can_replace_path(self):
237
+ with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
238
+ assert c.request.make_url(path="/foo bar") == "http://localhost/foo%20bar"
239
+
240
+ def test_it_joins_path(self):
241
+ with context(SCRIPT_NAME="/script name", PATH_INFO="/path info/") as c:
242
+ assert (
243
+ c.request.make_url(path="a/b c")
244
+ == "http://localhost/script%20name/path%20info/a/b%20c"
245
+ )
246
+
247
+ def test_query_not_included_by_default(self):
248
+ with context(QUERY_STRING="query=foo") as c:
249
+ assert c.request.make_url() == "http://localhost/"
250
+
251
+ def test_query_dict(self):
252
+ with context() as c:
253
+ self.assert_equal_with_query(
254
+ c.request.make_url(query={"a": 1, "b": "2 3"}),
255
+ "http://localhost/?a=1&b=2+3",
256
+ )
257
+
258
+ def test_query_drops_items_with_None_value(self):
259
+ with context() as c:
260
+ self.assert_equal_with_query(
261
+ c.request.make_url(query={"a": 1, "b": None}),
262
+ "http://localhost/?a=1",
263
+ )
264
+ self.assert_equal_with_query(
265
+ c.request.make_url(query=[("a", 1), ("b", None)]),
266
+ "http://localhost/?a=1",
267
+ )
268
+
269
+ def test_query_kwargs(self):
270
+ with context() as c:
271
+ self.assert_equal_with_query(
272
+ c.request.make_url(query={"a": 1}, b=2),
273
+ "http://localhost/?a=1&b=2",
274
+ )
275
+
276
+ def test_unicode_path(self):
277
+ e = b"\xc3\xa9".decode("utf8") # e-acute
278
+ with context() as c:
279
+ assert c.request.make_url(path=e) == "http://localhost/%C3%A9"
280
+
281
+ def test_unicode_path_info(self):
282
+ e = b"\xc3\xa9".decode("utf8") # e-acute
283
+ with context() as c:
284
+ assert c.request.make_url(PATH_INFO=e) == "http://localhost/%C3%A9"
285
+
286
+ def test_unicode_query(self):
287
+ e = b"\xc3\xa9".decode("utf8") # e-acute
288
+ with context() as c:
289
+ assert c.request.make_url(query={"e": e}) == "http://localhost/?e=%C3%A9"
290
+
291
+ def test_it_quotes_paths(self):
292
+ with context() as c:
293
+ assert c.request.make_url(path="/a b") == "http://localhost/a%20b"
294
+
295
+ def test_replace_query_replaces_existing(self):
296
+ with context(QUERY_STRING="a=1") as c:
297
+ assert (
298
+ c.request.make_url(query_replace={"a": "2"}) == "http://localhost/?a=2"
299
+ )
300
+
301
+ def test_replace_query_adds_new_value(self):
302
+ with context(QUERY_STRING="a=1") as c:
303
+ self.assert_equal_with_query(
304
+ c.request.make_url(query_replace={"b": "2"}),
305
+ "http://localhost/?a=1&b=2",
306
+ )
307
+
308
+ def test_add_query_doesnt_replace_existing(self):
309
+ with context(QUERY_STRING="a=1") as c:
310
+ self.assert_equal_with_query(
311
+ c.request.make_url(query_add={"a": "2"}),
312
+ "http://localhost/?a=1&a=2",
313
+ )
314
+
315
+ def test_add_query_works_on_specified_query(self):
316
+ with context(QUERY_STRING="a=1") as c:
317
+ assert (
318
+ c.request.make_url(query="", query_add={"a": "2"})
319
+ == "http://localhost/?a=2"
320
+ )
321
+
322
+ def test_add_query_does_not_mutate_request_query(self):
323
+ with context(QUERY_STRING="a=1") as c:
324
+ c.request.make_url(query_add={"a": "2"})
325
+ assert list(c.request.query.allitems()) == [("a", "1")]
326
+
327
+
328
+ class TestResolveURL(object):
329
+ def test_relative_path(self):
330
+ with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
331
+ assert c.request.resolve_url("foo") == "http://localhost/script/foo"
332
+
333
+ def test_absolute_path_unspecified_relative(self):
334
+ with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
335
+ assert (
336
+ c.request.resolve_url("/foo", relative="app")
337
+ == "http://localhost/script/foo"
338
+ )
339
+
340
+ def test_absolute_path_app_relative(self):
341
+ with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
342
+ assert (
343
+ c.request.resolve_url("/foo", relative="app")
344
+ == "http://localhost/script/foo"
345
+ )
346
+
347
+ def test_absolute_path_server_relative(self):
348
+ with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
349
+ assert (
350
+ c.request.resolve_url("/foo", relative="server")
351
+ == "http://localhost/foo"
352
+ )
353
+
354
+ def test_ignores_server_port_if_host_header_present(self):
355
+ with context(
356
+ environ={"wsgi.url_scheme": "https", "SERVER_PORT": "81"},
357
+ HTTP_HOST="localhost",
358
+ ) as c:
359
+ assert c.request.resolve_url("/foo") == "https://localhost/foo"
360
+
361
+
362
+ class TestCurrentRequest(object):
363
+ def test_currentrequest_returns_current_request(self):
364
+ from fresco import currentrequest
365
+
366
+ with context() as c:
367
+ assert currentrequest() is c.request
368
+
369
+ def test_currentrequest_returns_None(self):
370
+ from fresco import currentrequest
371
+
372
+ assert currentrequest() is None
373
+
374
+
375
+ class TestMultipart(object):
376
+ filename = "test.txt"
377
+ filedata = "123456\n"
378
+ boundary = "---------------------------1234"
379
+
380
+ post_data = (
381
+ (
382
+ "--{boundary}\r\n"
383
+ "Content-Disposition: form-data; "
384
+ 'name="uploaded_file"; filename="{filename}"\r\n'
385
+ "Content-Type: text/plain\r\n"
386
+ "\r\n"
387
+ "{filedata}\r\n"
388
+ "--{boundary}"
389
+ "--\r\n"
390
+ )
391
+ .format(boundary=boundary, filedata=filedata, filename=filename)
392
+ .encode("latin1")
393
+ )
394
+
395
+ request_args = {
396
+ "wsgi_input": post_data,
397
+ "REQUEST_METHOD": "POST",
398
+ "CONTENT_TYPE": "multipart/form-data; boundary=" + boundary,
399
+ "CONTENT_LENGTH": str(len(post_data)),
400
+ }
401
+
402
+ def test_files_populated(self):
403
+ with FrescoApp().requestcontext(**self.request_args) as c:
404
+ request = c.request
405
+
406
+ assert len(request.files) == 1
407
+ assert "uploaded_file" in request.files
408
+
409
+ def test_file_content_available(self):
410
+ with FrescoApp().requestcontext(**self.request_args) as c:
411
+ request = c.request
412
+
413
+ b = BytesIO()
414
+ request.files["uploaded_file"].save(b)
415
+ assert b.getvalue() == self.filedata.encode("latin1")
416
+
417
+ def test_headers_available(self):
418
+ with FrescoApp().requestcontext(**self.request_args) as c:
419
+ request = c.request
420
+
421
+ assert (
422
+ request.files["uploaded_file"].headers["content-type"] == "text/plain"
423
+ )
424
+ assert request.files["uploaded_file"].filename == self.filename
425
+
426
+ def test_quotes_in_input_names_are_decoded(self):
427
+ """
428
+ Field names in multipart form data must be decoded as RFC822
429
+ quoted-strings
430
+ """
431
+ data = (
432
+ b"----BOUNDARY\r\n"
433
+ b'Content-Disposition: form-data; name="\\"qtext\\"";\r\n'
434
+ b"\r\n"
435
+ b"foo\r\n"
436
+ b"----BOUNDARY"
437
+ b"--\r\n"
438
+ )
439
+
440
+ env = {
441
+ "wsgi_input": data,
442
+ "REQUEST_METHOD": "POST",
443
+ "CONTENT_TYPE": "multipart/form-data; boundary=--BOUNDARY",
444
+ "CONTENT_LENGTH": str(len(data)),
445
+ }
446
+
447
+ with FrescoApp().requestcontext(**env) as c:
448
+ assert list(c.request.form.items()) == [('"qtext"', "foo")]
@@ -0,0 +1,107 @@
1
+ # Copyright 2015 Oliver Cope
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ import pytest
16
+
17
+ from fresco import FrescoApp
18
+ from fresco.response import Response
19
+ from fresco.routing import GET
20
+ from fresco.requestcontext import RequestContext
21
+
22
+
23
+ class TestRequestContext(object):
24
+ def test_instantiation(self):
25
+ """
26
+ Can we cleanly instantiate a RequestContext object?
27
+ """
28
+ RequestContext()
29
+
30
+ def test_app_populates_request_object(self):
31
+ def view():
32
+ from fresco.core import context
33
+
34
+ assert context.request is not None
35
+ return Response([""])
36
+
37
+ app = FrescoApp()
38
+ app.route("/", GET, view)
39
+
40
+ with app.requestcontext("/"):
41
+ app.view()
42
+
43
+ def test_context_returns_correct_request_for_each_app(self):
44
+ from time import sleep
45
+ from threading import Thread, current_thread
46
+
47
+ threadcount = 3
48
+ itercount = 200
49
+ calls = []
50
+
51
+ def view():
52
+ from fresco.core import context
53
+
54
+ request_id = id(context.request)
55
+
56
+ def generate_response():
57
+ for i in range(itercount):
58
+ assert id(context.request) == request_id
59
+ calls.append(current_thread().ident)
60
+ sleep(0.0001)
61
+ yield str(request_id).encode("ascii")
62
+
63
+ return Response(list(generate_response()))
64
+
65
+ app = FrescoApp()
66
+ app.route("/", GET, view)
67
+
68
+ def do_request():
69
+ with app.requestcontext("/"):
70
+ app.view()
71
+
72
+ threads = [Thread(target=do_request) for i in range(threadcount)]
73
+
74
+ for t in threads:
75
+ t.start()
76
+ for t in threads:
77
+ t.join()
78
+
79
+ assert len(calls) == threadcount * itercount
80
+
81
+ # Check that threaded requests were genuinely interleaved
82
+ for i1, i2 in zip(calls[: itercount - 1], calls[1:itercount]):
83
+ if i1 != i2:
84
+ break
85
+ else:
86
+ raise AssertionError("Output does not appear interleaved")
87
+
88
+ def test_context_does_not_inherit_from_parent(self):
89
+ c = RequestContext()
90
+ c.push(foo=1)
91
+ c.push(bar=2)
92
+ with pytest.raises(AttributeError):
93
+ c.foo
94
+
95
+ def test_child_context_overrides_parent(self):
96
+ c = RequestContext()
97
+ c.push(foo=1)
98
+ c.push(foo=2)
99
+ assert c.foo == 2
100
+
101
+ def test_pop_context_removes_keys(self):
102
+ c = RequestContext()
103
+ c.push(foo=1)
104
+ c.push(foo=2)
105
+ assert c.foo == 2
106
+ c.pop()
107
+ assert c.foo == 1