From 6d37c8632b713af56c366c3ebeca55a89b30b57a Mon Sep 17 00:00:00 2001 From: John Kraal Date: Fri, 25 Apr 2014 10:00:31 +0200 Subject: added line numbers to generic log formatter --- pyramid/scaffolds/alchemy/production.ini_tmpl | 2 +- pyramid/scaffolds/starter/production.ini_tmpl | 2 +- pyramid/scaffolds/zodb/production.ini_tmpl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyramid/scaffolds/alchemy/production.ini_tmpl b/pyramid/scaffolds/alchemy/production.ini_tmpl index b316ec9ca..022bc0b7b 100644 --- a/pyramid/scaffolds/alchemy/production.ini_tmpl +++ b/pyramid/scaffolds/alchemy/production.ini_tmpl @@ -59,4 +59,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/pyramid/scaffolds/starter/production.ini_tmpl b/pyramid/scaffolds/starter/production.ini_tmpl index 6a123abf5..b2681c71d 100644 --- a/pyramid/scaffolds/starter/production.ini_tmpl +++ b/pyramid/scaffolds/starter/production.ini_tmpl @@ -51,4 +51,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/pyramid/scaffolds/zodb/production.ini_tmpl b/pyramid/scaffolds/zodb/production.ini_tmpl index c231e159d..522ff7651 100644 --- a/pyramid/scaffolds/zodb/production.ini_tmpl +++ b/pyramid/scaffolds/zodb/production.ini_tmpl @@ -57,4 +57,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s -- cgit v1.2.3 From 36d5a438308dbd2a89d4da82eafbd5b368389348 Mon Sep 17 00:00:00 2001 From: Harz-FEAR Date: Tue, 8 Jul 2014 04:40:52 +0200 Subject: RFC 6585 HTTP Exceptions --- pyramid/httpexceptions.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index ebee39ada..7cf802139 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -52,6 +52,9 @@ Exception * 422 - HTTPUnprocessableEntity * 423 - HTTPLocked * 424 - HTTPFailedDependency + * 428 - HTTPPreconditionRequired + * 429 - HTTPTooManyRequests + * 431 - HTTPRequestHeaderFieldsTooLarge HTTPServerError * 500 - HTTPInternalServerError * 501 - HTTPNotImplemented @@ -60,6 +63,7 @@ Exception * 504 - HTTPGatewayTimeout * 505 - HTTPVersionNotSupported * 507 - HTTPInsufficientStorage + * 511 - HTTPNetworkAuthenticationRequired HTTP exceptions are also :term:`response` objects, thus they accept most of the same parameters that can be passed to a regular @@ -907,6 +911,62 @@ class HTTPFailedDependency(HTTPClientError): 'The method could not be performed because the requested ' 'action dependended on another action and that action failed') +class HTTPPreconditionRequired(HTTPClientError): + """ + subclass of :class:`~HTTPClientError` + + This indicates that the origin server requires the + request to be conditional. + + Its typical use is to avoid the "lost update" problem, where a client + GETs a resource's state, modifies it, and PUTs it back to the server, + when meanwhile a third party has modified the state on the server, + leading to a conflict. By requiring requests to be conditional, the + server can assure that clients are working with the correct copies. + + RFC 6585.3 + + code: 428, title: Precondition Required + """ + code = 428 + title = 'Precondition Required' + explanation = ( + 'The origin server requires the request to be conditional.') + +class HTTPTooManyRequests(HTTPClientError): + """ + subclass of :class:`~HTTPClientError` + + This indicates that the user has sent too many + requests in a given amount of time ("rate limiting"). + + RFC 6585.4 + + code: 429, title: Too Many Requests + """ + code = 429 + title = 'Too Many Requests' + explanation = ( + 'The action could not be performed because there were too ' + 'many requests by the client.') + +class HTTPRequestHeaderFieldsTooLarge(HTTPClientError): + """ + subclass of :class:`~HTTPClientError` + + This indicates that the server is unwilling to process + the request because its header fields are too large. The request MAY + be resubmitted after reducing the size of the request header fields. + + RFC 6585.5 + + code: 431, title: Request Header Fields Too Large + """ + code = 431 + title = 'Request Header Fields Too Large' + explanation = ( + 'The requests header fields were too large.') + ############################################################ ## 5xx Server Error ############################################################ -- cgit v1.2.3 From 5ef159eb1a86046da4c53ca9530fff0eb3b3432f Mon Sep 17 00:00:00 2001 From: Harz-FEAR Date: Tue, 8 Jul 2014 04:44:32 +0200 Subject: did not include code 511 because it is for use by intercepting proxies --- pyramid/httpexceptions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index 7cf802139..ea40a99af 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -63,7 +63,6 @@ Exception * 504 - HTTPGatewayTimeout * 505 - HTTPVersionNotSupported * 507 - HTTPInsufficientStorage - * 511 - HTTPNetworkAuthenticationRequired HTTP exceptions are also :term:`response` objects, thus they accept most of the same parameters that can be passed to a regular -- cgit v1.2.3 From eae99acbf9eed71967ff12961e495f27708d1f39 Mon Sep 17 00:00:00 2001 From: dobesv Date: Wed, 9 Jul 2014 15:15:08 -0700 Subject: Allow the last callback called to add a callback This fixes a bug in the finished and response callbacks where if the last/only callback adds another callback, the newly added callback won't be called afterwards. This is because when it tries to add the callback, it is added to a new list instance because the callbacks list is empty at that time; the check for whether the callbacks list was created didn't previously distinguish between an empty list and not a list. However, if it is not the last callback in the list, the callbacks list will not be empty and the new callback will be added to the same list and the newly added callback *will* be called. Because the code as written appears to be trying to support callbacks adding callbacks, this push request modifies the code so that a callback may add another callback whether it is the last one or not. An alternative approach would be to modify the code so that callbacks cannot add new callbacks, which also would be reasonable. But I think it's a bug that the behavior depends currently on whether you are in the last/only callback when you try to add another one. --- pyramid/request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/request.py b/pyramid/request.py index 6318049ee..7e8b8c07d 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -72,7 +72,7 @@ class CallbackMethodsMixin(object): """ callbacks = self.response_callbacks - if not callbacks: + if callbacks == (): callbacks = [] callbacks.append(callback) self.response_callbacks = callbacks @@ -132,7 +132,7 @@ class CallbackMethodsMixin(object): """ callbacks = self.finished_callbacks - if not callbacks: + if callbacks == (): callbacks = [] callbacks.append(callback) self.finished_callbacks = callbacks -- cgit v1.2.3 From cd299ae7a21a95c1023c2b7c38624234ead1d464 Mon Sep 17 00:00:00 2001 From: Roy Hyunjin Han Date: Thu, 10 Jul 2014 14:08:47 -0700 Subject: Allow hyphens in project name Convert hyphens in project_name to underscores in pkg_name --- pyramid/scripts/pcreate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyramid/scripts/pcreate.py b/pyramid/scripts/pcreate.py index 4c1f432fb..edf2c39f7 100644 --- a/pyramid/scripts/pcreate.py +++ b/pyramid/scripts/pcreate.py @@ -81,7 +81,8 @@ class PCreateCommand(object): args = self.args output_dir = os.path.abspath(os.path.normpath(args[0])) project_name = os.path.basename(os.path.split(output_dir)[1]) - pkg_name = _bad_chars_re.sub('', project_name.lower()) + pkg_name = _bad_chars_re.sub( + '', project_name.lower().replace('-', '_')) safe_name = pkg_resources.safe_name(project_name) egg_name = pkg_resources.to_filename(safe_name) -- cgit v1.2.3 From 63e6e14a83a10331635bdced1ff6258e4c18989c Mon Sep 17 00:00:00 2001 From: Dobes Vandermeer Date: Wed, 23 Jul 2014 09:49:44 -0700 Subject: Add a test case for a response callback adding a response callback when it is the only callback. --- pyramid/tests/test_request.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index ed41b62ff..ec206dad3 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -144,6 +144,29 @@ class TestRequest(unittest.TestCase): self.assertEqual(response.called2, True) self.assertEqual(inst.response_callbacks, []) + def test__process_response_callback_adding_response_callback(self): + """ + When a response callback adds another callback, that new callback should still be called. + + See https://github.com/Pylons/pyramid/pull/1373 + """ + inst = self._makeOne() + def callback1(request, response): + request.called1 = True + response.called1 = True + request.add_response_callback(callback2) + def callback2(request, response): + request.called2 = True + response.called2 = True + inst.add_response_callback(callback1) + response = DummyResponse() + inst._process_response_callbacks(response) + self.assertEqual(inst.called1, True) + self.assertEqual(inst.called2, True) + self.assertEqual(response.called1, True) + self.assertEqual(response.called2, True) + self.assertEqual(inst.response_callbacks, []) + def test_add_finished_callback(self): inst = self._makeOne() self.assertEqual(inst.finished_callbacks, ()) -- cgit v1.2.3 From d47b360cde1d98a67874f88adea69c57ee708dad Mon Sep 17 00:00:00 2001 From: Roy Hyunjin Han Date: Mon, 28 Jul 2014 11:31:45 -0400 Subject: Add test_scaffold_with_hyphen_in_project_name --- pyramid/tests/test_scripts/test_pcreate.py | 87 +++++++++++++++--------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/pyramid/tests/test_scripts/test_pcreate.py b/pyramid/tests/test_scripts/test_pcreate.py index 2488e9595..d2eef2ae9 100644 --- a/pyramid/tests/test_scripts/test_pcreate.py +++ b/pyramid/tests/test_scripts/test_pcreate.py @@ -1,4 +1,6 @@ import unittest +from os import getcwd +from os.path import abspath, join, normpath class TestPCreateCommand(unittest.TestCase): def setUp(self): @@ -50,153 +52,150 @@ class TestPCreateCommand(unittest.TestCase): self.assertTrue(out.startswith('You must provide a project name')) def test_unknown_scaffold_name(self): - cmd = self._makeOne('-s', 'dummyXX', 'distro') + cmd = self._makeOne('-s', 'dummyXX', 'Distro') result = cmd.run() self.assertEqual(result, 2) out = self.out_.getvalue() self.assertTrue(out.startswith('Unavailable scaffolds')) def test_known_scaffold_single_rendered(self): - import os cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist("0.1") + cmd.pyramid_dist = DummyDist('0.1') result = cmd.run() self.assertEqual(result, 0) self.assertEqual( - scaffold.output_dir, - os.path.normpath(os.path.join(os.getcwd(), 'Distro')) - ) + scaffold.output_dir, normpath(join(getcwd(), 'Distro'))) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) + + def test_scaffold_with_hyphen_in_project_name(self): + cmd = self._makeOne('-s', 'dummy', 'Distro-') + scaffold = DummyScaffold('dummy') + cmd.scaffolds = [scaffold] + cmd.pyramid_dist = DummyDist('0.1') + result = cmd.run() + self.assertEqual(result, 0) + self.assertEqual( + scaffold.output_dir, normpath(join(getcwd(), 'Distro-'))) + self.assertEqual( + scaffold.vars, + {'project': 'Distro-', 'egg': 'Distro_', 'package': 'distro_', + 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) def test_known_scaffold_absolute_path(self): - import os - path = os.path.abspath('Distro') + path = abspath('Distro') cmd = self._makeOne('-s', 'dummy', path) - cmd.pyramid_dist = DummyDist("0.1") + cmd.pyramid_dist = DummyDist('0.1') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist("0.1") + cmd.pyramid_dist = DummyDist('0.1') result = cmd.run() self.assertEqual(result, 0) self.assertEqual( - scaffold.output_dir, - os.path.normpath(os.path.join(os.getcwd(), 'Distro')) - ) + scaffold.output_dir, normpath(join(getcwd(), 'Distro'))) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) def test_known_scaffold_multiple_rendered(self): - import os cmd = self._makeOne('-s', 'dummy1', '-s', 'dummy2', 'Distro') scaffold1 = DummyScaffold('dummy1') scaffold2 = DummyScaffold('dummy2') cmd.scaffolds = [scaffold1, scaffold2] - cmd.pyramid_dist = DummyDist("0.1") + cmd.pyramid_dist = DummyDist('0.1') result = cmd.run() self.assertEqual(result, 0) self.assertEqual( - scaffold1.output_dir, - os.path.normpath(os.path.join(os.getcwd(), 'Distro')) - ) + scaffold1.output_dir, normpath(join(getcwd(), 'Distro'))) self.assertEqual( scaffold1.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) self.assertEqual( - scaffold2.output_dir, - os.path.normpath(os.path.join(os.getcwd(), 'Distro')) - ) + scaffold2.output_dir, normpath(join(getcwd(), 'Distro'))) self.assertEqual( scaffold2.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) def test_known_scaffold_with_path_as_project_target_rendered(self): - import os cmd = self._makeOne('-s', 'dummy', '/tmp/foo/Distro/') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist("0.1") + cmd.pyramid_dist = DummyDist('0.1') result = cmd.run() self.assertEqual(result, 0) self.assertEqual( - scaffold.output_dir, - os.path.normpath(os.path.join(os.getcwd(), '/tmp/foo/Distro')) - ) + scaffold.output_dir, normpath(join(getcwd(), '/tmp/foo/Distro'))) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) - + 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) def test_scaffold_with_prod_pyramid_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist("0.2") + cmd.pyramid_dist = DummyDist('0.2') result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.2', 'pyramid_docs_branch':'0.2-branch'}) + 'pyramid_version': '0.2', 'pyramid_docs_branch': '0.2-branch'}) def test_scaffold_with_prod_pyramid_long_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist("0.2.1") + cmd.pyramid_dist = DummyDist('0.2.1') result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.2.1', 'pyramid_docs_branch':'0.2-branch'}) + 'pyramid_version': '0.2.1', 'pyramid_docs_branch': '0.2-branch'}) def test_scaffold_with_prod_pyramid_unparsable_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist("abc") + cmd.pyramid_dist = DummyDist('abc') result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': 'abc', 'pyramid_docs_branch':'latest'}) + 'pyramid_version': 'abc', 'pyramid_docs_branch': 'latest'}) def test_scaffold_with_dev_pyramid_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist("0.12dev") + cmd.pyramid_dist = DummyDist('0.12dev') result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.12dev', - 'pyramid_docs_branch': 'master'}) + 'pyramid_version': '0.12dev', 'pyramid_docs_branch': 'master'}) def test_scaffold_with_dev_pyramid_long_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist("0.10.1dev") + cmd.pyramid_dist = DummyDist('0.10.1dev') result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.10.1dev', - 'pyramid_docs_branch': 'master'}) + 'pyramid_version': '0.10.1dev', 'pyramid_docs_branch': 'master'}) class Test_main(unittest.TestCase): -- cgit v1.2.3 From 05ceab495c4390c0e3af5ae6458d4c808eb08e67 Mon Sep 17 00:00:00 2001 From: Dobes Vandermeer Date: Tue, 29 Jul 2014 17:54:23 -0700 Subject: Use None for the default value of the callbacks list. --- pyramid/request.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyramid/request.py b/pyramid/request.py index 7e8b8c07d..fa35dc5f4 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -32,8 +32,8 @@ class TemplateContext(object): pass class CallbackMethodsMixin(object): - response_callbacks = () - finished_callbacks = () + response_callbacks = None + finished_callbacks = None def add_response_callback(self, callback): """ Add a callback to the set of callbacks to be called by the @@ -72,7 +72,7 @@ class CallbackMethodsMixin(object): """ callbacks = self.response_callbacks - if callbacks == (): + if callbacks is None: callbacks = [] callbacks.append(callback) self.response_callbacks = callbacks @@ -132,7 +132,7 @@ class CallbackMethodsMixin(object): """ callbacks = self.finished_callbacks - if callbacks == (): + if callbacks is None: callbacks = [] callbacks.append(callback) self.finished_callbacks = callbacks -- cgit v1.2.3 From c73eb9be17a17c6e0b19ce6b08a579a6d524c584 Mon Sep 17 00:00:00 2001 From: Dobes Vandermeer Date: Tue, 29 Jul 2014 18:05:27 -0700 Subject: Use a deque for the request finished / response callbacks. --- pyramid/request.py | 9 +++++---- pyramid/tests/test_request.py | 25 ++++++++++++++----------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/pyramid/request.py b/pyramid/request.py index fa35dc5f4..bc2889310 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -1,3 +1,4 @@ +from collections import deque import json from zope.interface import implementer @@ -73,14 +74,14 @@ class CallbackMethodsMixin(object): callbacks = self.response_callbacks if callbacks is None: - callbacks = [] + callbacks = deque() callbacks.append(callback) self.response_callbacks = callbacks def _process_response_callbacks(self, response): callbacks = self.response_callbacks while callbacks: - callback = callbacks.pop(0) + callback = callbacks.popleft() callback(self, response) def add_finished_callback(self, callback): @@ -133,14 +134,14 @@ class CallbackMethodsMixin(object): callbacks = self.finished_callbacks if callbacks is None: - callbacks = [] + callbacks = deque() callbacks.append(callback) self.finished_callbacks = callbacks def _process_finished_callbacks(self): callbacks = self.finished_callbacks while callbacks: - callback = callbacks.pop(0) + callback = callbacks.popleft() callback(self) @implementer(IRequest) diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index ec206dad3..48af98f59 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -1,3 +1,4 @@ +from collections import deque import unittest from pyramid import testing @@ -119,13 +120,13 @@ class TestRequest(unittest.TestCase): def test_add_response_callback(self): inst = self._makeOne() - self.assertEqual(inst.response_callbacks, ()) + self.assertEqual(inst.response_callbacks, None) def callback(request, response): """ """ inst.add_response_callback(callback) - self.assertEqual(inst.response_callbacks, [callback]) + self.assertEqual(list(inst.response_callbacks), [callback]) inst.add_response_callback(callback) - self.assertEqual(inst.response_callbacks, [callback, callback]) + self.assertEqual(list(inst.response_callbacks), [callback, callback]) def test__process_response_callbacks(self): inst = self._makeOne() @@ -135,14 +136,15 @@ class TestRequest(unittest.TestCase): def callback2(request, response): request.called2 = True response.called2 = True - inst.response_callbacks = [callback1, callback2] + inst.add_response_callback(callback1) + inst.add_response_callback(callback2) response = DummyResponse() inst._process_response_callbacks(response) self.assertEqual(inst.called1, True) self.assertEqual(inst.called2, True) self.assertEqual(response.called1, True) self.assertEqual(response.called2, True) - self.assertEqual(inst.response_callbacks, []) + self.assertEqual(len(inst.response_callbacks), 0) def test__process_response_callback_adding_response_callback(self): """ @@ -165,17 +167,17 @@ class TestRequest(unittest.TestCase): self.assertEqual(inst.called2, True) self.assertEqual(response.called1, True) self.assertEqual(response.called2, True) - self.assertEqual(inst.response_callbacks, []) + self.assertEqual(len(inst.response_callbacks), 0) def test_add_finished_callback(self): inst = self._makeOne() - self.assertEqual(inst.finished_callbacks, ()) + self.assertEqual(inst.finished_callbacks, None) def callback(request): """ """ inst.add_finished_callback(callback) - self.assertEqual(inst.finished_callbacks, [callback]) + self.assertEqual(list(inst.finished_callbacks), [callback]) inst.add_finished_callback(callback) - self.assertEqual(inst.finished_callbacks, [callback, callback]) + self.assertEqual(list(inst.finished_callbacks), [callback, callback]) def test__process_finished_callbacks(self): inst = self._makeOne() @@ -183,11 +185,12 @@ class TestRequest(unittest.TestCase): request.called1 = True def callback2(request): request.called2 = True - inst.finished_callbacks = [callback1, callback2] + inst.add_finished_callback(callback1) + inst.add_finished_callback(callback2) inst._process_finished_callbacks() self.assertEqual(inst.called1, True) self.assertEqual(inst.called2, True) - self.assertEqual(inst.finished_callbacks, []) + self.assertEqual(len(inst.finished_callbacks), 0) def test_resource_url(self): self._registerResourceURL() -- cgit v1.2.3 From 39a03eef73b14bd05540af70e705d9f02d77f850 Mon Sep 17 00:00:00 2001 From: Dobes Vandermeer Date: Wed, 30 Jul 2014 09:42:28 -0700 Subject: Fix a few tests that assume response_callbacks was a list --- pyramid/tests/test_router.py | 6 +++--- pyramid/tests/test_session.py | 2 +- pyramid/tests/test_testing.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 838e52db0..c6c6eea1c 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -522,7 +522,7 @@ class TestRouter(unittest.TestCase): def view(context, request): def callback(request, response): response.called_back = True - request.response_callbacks = [callback] + request.add_response_callback(callback) return response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) @@ -545,7 +545,7 @@ class TestRouter(unittest.TestCase): def view(context, request): def callback(request): request.environ['called_back'] = True - request.finished_callbacks = [callback] + request.add_finished_callback(callback) return response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) @@ -567,7 +567,7 @@ class TestRouter(unittest.TestCase): def view(context, request): def callback(request): request.environ['called_back'] = True - request.finished_callbacks = [callback] + request.add_finished_callback(callback) raise NotImplementedError environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index 35c234e99..b013ffa66 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -521,7 +521,7 @@ class Test_manage_accessed(unittest.TestCase): result = wrapper(session, 'a') self.assertEqual(result, 1) callbacks = request.response_callbacks - self.assertEqual(len(callbacks), 0) + if callbacks is not None: self.assertEqual(len(callbacks), 0) class Test_manage_changed(unittest.TestCase): def _makeOne(self, wrapped): diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py index 2d0548b33..dfcad2a0c 100644 --- a/pyramid/tests/test_testing.py +++ b/pyramid/tests/test_testing.py @@ -217,7 +217,7 @@ class TestDummyRequest(unittest.TestCase): def test_add_response_callback(self): request = self._makeOne() request.add_response_callback(1) - self.assertEqual(request.response_callbacks, [1]) + self.assertEqual(list(request.response_callbacks), [1]) def test_registry_is_config_registry_when_setup_is_called_after_ctor(self): # see https://github.com/Pylons/pyramid/issues/165 -- cgit v1.2.3 From c1ef71cec60b0bfeac0445c3c30ae46a976f2b31 Mon Sep 17 00:00:00 2001 From: Roy Hyunjin Han Date: Thu, 31 Jul 2014 19:37:49 -0400 Subject: Minimize changes to increase merge likelihood --- pyramid/tests/test_scripts/test_pcreate.py | 82 ++++++++++++++++++------------ 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/pyramid/tests/test_scripts/test_pcreate.py b/pyramid/tests/test_scripts/test_pcreate.py index d2eef2ae9..020721ca7 100644 --- a/pyramid/tests/test_scripts/test_pcreate.py +++ b/pyramid/tests/test_scripts/test_pcreate.py @@ -1,6 +1,4 @@ import unittest -from os import getcwd -from os.path import abspath, join, normpath class TestPCreateCommand(unittest.TestCase): def setUp(self): @@ -52,150 +50,170 @@ class TestPCreateCommand(unittest.TestCase): self.assertTrue(out.startswith('You must provide a project name')) def test_unknown_scaffold_name(self): - cmd = self._makeOne('-s', 'dummyXX', 'Distro') + cmd = self._makeOne('-s', 'dummyXX', 'distro') result = cmd.run() self.assertEqual(result, 2) out = self.out_.getvalue() self.assertTrue(out.startswith('Unavailable scaffolds')) def test_known_scaffold_single_rendered(self): + import os cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist('0.1') + cmd.pyramid_dist = DummyDist("0.1") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( - scaffold.output_dir, normpath(join(getcwd(), 'Distro'))) + scaffold.output_dir, + os.path.normpath(os.path.join(os.getcwd(), 'Distro')) + ) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) def test_scaffold_with_hyphen_in_project_name(self): + import os cmd = self._makeOne('-s', 'dummy', 'Distro-') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist('0.1') + cmd.pyramid_dist = DummyDist("0.1") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( - scaffold.output_dir, normpath(join(getcwd(), 'Distro-'))) + scaffold.output_dir, + os.path.normpath(os.path.join(os.getcwd(), 'Distro-')) + ) self.assertEqual( scaffold.vars, {'project': 'Distro-', 'egg': 'Distro_', 'package': 'distro_', - 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) def test_known_scaffold_absolute_path(self): - path = abspath('Distro') + import os + path = os.path.abspath('Distro') cmd = self._makeOne('-s', 'dummy', path) - cmd.pyramid_dist = DummyDist('0.1') + cmd.pyramid_dist = DummyDist("0.1") scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist('0.1') + cmd.pyramid_dist = DummyDist("0.1") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( - scaffold.output_dir, normpath(join(getcwd(), 'Distro'))) + scaffold.output_dir, + os.path.normpath(os.path.join(os.getcwd(), 'Distro')) + ) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) def test_known_scaffold_multiple_rendered(self): + import os cmd = self._makeOne('-s', 'dummy1', '-s', 'dummy2', 'Distro') scaffold1 = DummyScaffold('dummy1') scaffold2 = DummyScaffold('dummy2') cmd.scaffolds = [scaffold1, scaffold2] - cmd.pyramid_dist = DummyDist('0.1') + cmd.pyramid_dist = DummyDist("0.1") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( - scaffold1.output_dir, normpath(join(getcwd(), 'Distro'))) + scaffold1.output_dir, + os.path.normpath(os.path.join(os.getcwd(), 'Distro')) + ) self.assertEqual( scaffold1.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) self.assertEqual( - scaffold2.output_dir, normpath(join(getcwd(), 'Distro'))) + scaffold2.output_dir, + os.path.normpath(os.path.join(os.getcwd(), 'Distro')) + ) self.assertEqual( scaffold2.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) def test_known_scaffold_with_path_as_project_target_rendered(self): + import os cmd = self._makeOne('-s', 'dummy', '/tmp/foo/Distro/') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist('0.1') + cmd.pyramid_dist = DummyDist("0.1") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( - scaffold.output_dir, normpath(join(getcwd(), '/tmp/foo/Distro'))) + scaffold.output_dir, + os.path.normpath(os.path.join(os.getcwd(), '/tmp/foo/Distro')) + ) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.1', 'pyramid_docs_branch': '0.1-branch'}) + 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'}) + def test_scaffold_with_prod_pyramid_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist('0.2') + cmd.pyramid_dist = DummyDist("0.2") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.2', 'pyramid_docs_branch': '0.2-branch'}) + 'pyramid_version': '0.2', 'pyramid_docs_branch':'0.2-branch'}) def test_scaffold_with_prod_pyramid_long_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist('0.2.1') + cmd.pyramid_dist = DummyDist("0.2.1") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.2.1', 'pyramid_docs_branch': '0.2-branch'}) + 'pyramid_version': '0.2.1', 'pyramid_docs_branch':'0.2-branch'}) def test_scaffold_with_prod_pyramid_unparsable_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist('abc') + cmd.pyramid_dist = DummyDist("abc") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': 'abc', 'pyramid_docs_branch': 'latest'}) + 'pyramid_version': 'abc', 'pyramid_docs_branch':'latest'}) def test_scaffold_with_dev_pyramid_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist('0.12dev') + cmd.pyramid_dist = DummyDist("0.12dev") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.12dev', 'pyramid_docs_branch': 'master'}) + 'pyramid_version': '0.12dev', + 'pyramid_docs_branch': 'master'}) def test_scaffold_with_dev_pyramid_long_version(self): cmd = self._makeOne('-s', 'dummy', 'Distro') scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] - cmd.pyramid_dist = DummyDist('0.10.1dev') + cmd.pyramid_dist = DummyDist("0.10.1dev") result = cmd.run() self.assertEqual(result, 0) self.assertEqual( scaffold.vars, {'project': 'Distro', 'egg': 'Distro', 'package': 'distro', - 'pyramid_version': '0.10.1dev', 'pyramid_docs_branch': 'master'}) + 'pyramid_version': '0.10.1dev', + 'pyramid_docs_branch': 'master'}) class Test_main(unittest.TestCase): -- cgit v1.2.3 From 1a768e3d45594d2458c379bcd55d6f1478ef3281 Mon Sep 17 00:00:00 2001 From: "Karl O. Pinc" Date: Fri, 8 Aug 2014 23:22:24 -0500 Subject: Link to .ini file description in configuration chapter. The Startup chapter describes a Pyramid application's .ini file. This is now a seealso in the Configuration chapter. --- docs/narr/configuration.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 52615533d..f7fa94daf 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -17,6 +17,10 @@ plugging application code that you've written into :app:`Pyramid` is also referred to within this documentation as "configuration"; you are configuring :app:`Pyramid` to call the code that makes up your application. +.. seealso:: + For information on ``.ini`` files for Pyramid applications see the + :ref:`startup_chapter` chapter. + There are two ways to configure a :app:`Pyramid` application: :term:`imperative configuration` and :term:`declarative configuration`. Both are described below. -- cgit v1.2.3 From ad76ef6ab78847ef86abf97868a32e9604aaab7e Mon Sep 17 00:00:00 2001 From: "Karl O. Pinc" Date: Fri, 8 Aug 2014 23:28:13 -0500 Subject: Link to logging configuration in the Startup chapter. The Startup chapter describes the application's .ini file. The Logging chapter describes how to configure logging with the .ini file. --- docs/narr/logging.rst | 9 +++++++++ docs/narr/startup.rst | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index 71029bb33..783fca932 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -294,6 +294,15 @@ use the :term:`pyramid_exclog` package. Details about its configuration are in its `documentation `_. +.. index:: + single: TransLogger + single: middleware; TransLogger + pair: configuration; middleware + single: settings; middleware + pair: .ini; middleware + +.. _request_logging_with_pastes_translogger: + Request Logging with Paste's TransLogger ---------------------------------------- diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index 7b4a7ea08..cd44e0ee3 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -139,6 +139,13 @@ Here's a high-level time-ordered overview of what happens when you press The server serves the application, and the application is running, waiting to receive requests. +.. seealso:: + Logging configuration is described in the :ref:`logging_chapter` + chapter. There, in :ref:`request_logging_with_pastes_translogger`, + you will also find an example of how to configure + :term:`middleware` to add pre-packaged functionality to your + application. + .. index:: pair: settings; deployment single: custom settings -- cgit v1.2.3 From 4a63f6ac8f19d21eebf23bd8c9f833f2b676287b Mon Sep 17 00:00:00 2001 From: "Karl O. Pinc" Date: Fri, 8 Aug 2014 23:30:04 -0500 Subject: Add index entries for .ini files vis settings. --- docs/narr/logging.rst | 5 +++++ docs/narr/startup.rst | 1 + 2 files changed, 6 insertions(+) diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index 783fca932..68da0813c 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -16,6 +16,11 @@ how to send log messages to loggers that you've configured. a third-party scaffold which does not create these files, the configuration information in this chapter may not be applicable. +.. index: + pair: settings; logging + pair: .ini; logging + pair: logging; configuration + .. _logging_config: Logging Configuration diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index cd44e0ee3..a1a23ed52 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -19,6 +19,7 @@ console. .. index:: single: startup process + pair: settings; .ini The Startup Process ------------------- -- cgit v1.2.3 From dcc6b4aceb140a5b6e03b1d3b0f32d925cbe879c Mon Sep 17 00:00:00 2001 From: "Karl O. Pinc" Date: Sun, 10 Aug 2014 21:31:01 -0500 Subject: Some improvements to the paste.translogger related docs. Synchronizes with Waitress docs. --- docs/narr/logging.rst | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index 71029bb33..8f6c74fd4 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -297,12 +297,14 @@ in its `documentation Request Logging with Paste's TransLogger ---------------------------------------- -Paste provides the `TransLogger -`_ :term:`middleware` for -logging requests using the `Apache Combined Log Format -`_. TransLogger combined -with a FileHandler can be used to create an ``access.log`` file similar to -Apache's. +The term:`WSGI` design is modular. Waitress logs error conditions, debugging +output, etc., but not web traffic. For web traffic logging Paste provides the +`TransLogger `_ +:term:`middleware`. TransLogger produces logs in the `Apache Combined Log +Format `_. But +TransLogger does not write to files, the Python logging system must be +configured to do this. The Python FileHandler_ logging handler can be used +alongside TransLogger to create an ``access.log`` file similar to Apache's. Like any standard :term:`middleware` with a Paste entry point, TransLogger can be configured to wrap your application using ``.ini`` file syntax. First, @@ -343,10 +345,12 @@ function of your project's ``__init__`` file: app = TransLogger(app, setup_console_handler=False) return app -TransLogger will automatically setup a logging handler to the console when -called with no arguments, so it 'just works' in environments that don't -configure logging. Since we've configured our own logging handlers, we need -to disable that option via ``setup_console_handler = False``. + +.. note:: + TransLogger will automatically setup a logging handler to the console when + called with no arguments, so it 'just works' in environments that don't + configure logging. Since our logging handlers are configured we disable + the automation via ``setup_console_handler = False``. With the filter in place, TransLogger's logger (named the ``wsgi`` logger) will propagate its log messages to the parent logger (the root logger), sending @@ -361,9 +365,9 @@ its output to the console when we request a page: "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6" -To direct TransLogger to an ``access.log`` FileHandler, we need to add that -FileHandler to the list of handlers (named ``accesslog``), and ensure that the -``wsgi`` logger is configured and uses this handler accordingly: +To direct TransLogger to an ``access.log`` FileHandler, we need the following +to add a FileHandler (named ``accesslog``) to the list of handlers, and ensure +that the ``wsgi`` logger is configured and uses this handler accordingly: .. code-block:: ini @@ -395,7 +399,7 @@ directs its records only to the ``accesslog`` handler. Finally, there's no need to use the ``generic`` formatter with TransLogger as TransLogger itself provides all the information we need. We'll use a formatter that passes-through the log messages as is. Add a new formatter -called ``accesslog`` by include the following in your configuration file: +called ``accesslog`` by including the following in your configuration file: .. code-block:: ini @@ -405,7 +409,9 @@ called ``accesslog`` by include the following in your configuration file: [formatter_accesslog] format = %(message)s -Then wire this new ``accesslog`` formatter into the FileHandler: + +Finally alter the existing configuration to wire this new +``accesslog`` formatter into the FileHandler: .. code-block:: ini -- cgit v1.2.3 From ddc745ff6497a5c08c44e2fc8f722ad0034948dc Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Thu, 14 Aug 2014 10:31:22 -0400 Subject: remove unnecessary use of `get_current_registry()` - We have a request object, so get the current registry properly through it. - make use of the built-in `aslist` function for parsing the result --- docs/narr/i18n.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 95f663584..3313f8dad 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -792,9 +792,11 @@ Then as a part of the code of a custom :term:`locale negotiator`: .. code-block:: python :linenos: - from pyramid.threadlocal import get_current_registry - settings = get_current_registry().settings - languages = settings['available_languages'].split() + from pyramid.settings import aslist + + def my_locale_negotiator(request): + languages = aslist(request.registry.settings['available_languages']) + # ... This is only a suggestion. You can create your own "available languages" configuration scheme as necessary. -- cgit v1.2.3 From e65d6eb43599fe45b8b3978eb78c34ebdf296b66 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Sun, 17 Aug 2014 08:11:14 -0700 Subject: Enable automated testing with PyPy 3 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ce27b5ec3..4ca998c42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ python: - 3.2 - 3.3 - 3.4 + - pypy3 install: python setup.py dev -- cgit v1.2.3 From 25d8b9790a613f5a0bbdf30b1c3f70b0d212e41a Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 17 Aug 2014 23:39:39 -0700 Subject: - add missing step for tox setup --- HACKING.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/HACKING.txt b/HACKING.txt index 1386be3af..e3afbf241 100644 --- a/HACKING.txt +++ b/HACKING.txt @@ -113,6 +113,7 @@ for this use case) and inside that a simple pyramid application named ``hacking`` that you can then fire up like so: cd env27/hacking + ../bin/python setup.py develop ../bin/pserve development.ini Adding Features -- cgit v1.2.3 From befc1b81d7713d5dab130388c285e83d641f7190 Mon Sep 17 00:00:00 2001 From: goodwillcoding Date: Fri, 5 Sep 2014 08:20:29 -0700 Subject: Remove unnecessary call to get_current_registry in NullRendererHelper --- pyramid/renderers.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 108255ee4..e647ebacf 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -248,7 +248,7 @@ class JSON(object): When you've done this, the JSON renderer will be able to serialize instances of the ``Foo`` class when they're encountered in your view results.""" - + self.components.registerAdapter(adapter, (type_or_iface,), IJSONAdapter) @@ -265,7 +265,7 @@ class JSON(object): response.content_type = 'application/json' default = self._make_default(request) return self.serializer(value, default=default, **self.kw) - + return _render def _make_default(self, request): @@ -286,7 +286,7 @@ json_renderer_factory = JSON() # bw compat class JSONP(JSON): """ `JSONP `_ renderer factory helper which implements a hybrid json/jsonp renderer. JSONP is useful for - making cross-domain AJAX requests. + making cross-domain AJAX requests. Configure a JSONP renderer using the :meth:`pyramid.config.Configurator.add_renderer` API at application @@ -309,7 +309,7 @@ class JSONP(JSON): config = Configurator() config.add_renderer('jsonp', JSONP(param_name='callback', indent=4)) - + .. versionchanged:: 1.4 The ability of this class to accept a ``**kw`` in its constructor. @@ -487,18 +487,18 @@ class NullRendererHelper(RendererHelper): @property def settings(self): - return get_current_registry().settings or {} + return {} def render_view(self, request, value, view, context): return value def render(self, value, system_values, request=None): return value - + def render_to_response(self, value, system_values, request=None): return value def clone(self, name=None, package=None, registry=None): return self - + null_renderer = NullRendererHelper() -- cgit v1.2.3 From c4c45446f79d6647aa6a90fc1f45def1319f7ac2 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Wed, 10 Sep 2014 11:15:21 -0600 Subject: Change helloworld to myapp Fix a typo in the documentation. Closes #1408 --- docs/narr/logging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index 71029bb33..74d9f260e 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -242,7 +242,7 @@ level is set to ``INFO``, whereas the application's log level is set to [logger_myapp] level = DEBUG handlers = - qualname = helloworld + qualname = myapp All of the child loggers of the ``myapp`` logger will inherit the ``DEBUG`` level unless they're explicitly set differently. Meaning the ``myapp.views``, -- cgit v1.2.3 From 9101510d3c08814e5a77683bf56f66a5b852e44e Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Wed, 17 Sep 2014 03:23:42 -0700 Subject: fix typo Re: https://github.com/Pylons/pyramid/pull/1411/files --- docs/quick_tutorial/ini.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst index 3402c50e8..132bc30be 100644 --- a/docs/quick_tutorial/ini.rst +++ b/docs/quick_tutorial/ini.rst @@ -16,7 +16,7 @@ This approach is optional, but its presence makes it distinct from other Python web frameworks. It taps into Python's ``setuptools`` library, which establishes conventions for how Python projects can be installed and provide "entry points". Pyramid uses an entry point to -let a Pyramid application it where to find the WSGI app. +let a Pyramid application know where to find the WSGI app. Objectives ========== -- cgit v1.2.3 From ba59b7b87796003138e7ebb01f5bc8a4a7b542a0 Mon Sep 17 00:00:00 2001 From: Omid Raha Date: Mon, 14 Apr 2014 12:38:24 +0430 Subject: Correct missing word. --- docs/quick_tour.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 4ab39bb11..41a0dc8c0 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -700,7 +700,7 @@ we might need to detect situations when other people use the site. We need *logging*. Fortunately Pyramid uses the normal Python approach to logging. The -scaffold generated in your ``development.ini`` a number of lines that +scaffold generated in your ``development.ini`` has a number of lines that configure the logging for you to some reasonable defaults. You then see messages sent by Pyramid (for example, when a new request comes in). -- cgit v1.2.3 From fea1750c7a70a666c7bcff40525206397c1080df Mon Sep 17 00:00:00 2001 From: Jay Martin Date: Sat, 20 Sep 2014 11:54:13 -0400 Subject: Update hello_world.rst --- docs/quick_tutorial/hello_world.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst index 1a9ba4c9d..4ae80ca87 100644 --- a/docs/quick_tutorial/hello_world.rst +++ b/docs/quick_tutorial/hello_world.rst @@ -77,7 +77,7 @@ explanation: #. *Lines 12-14*. Use Pyramid's :term:`configurator` to connect :term:`view` code to a particular URL :term:`route`. -#. *Lines 6-7*. Implement the view code that generates the +#. *Lines 6-8*. Implement the view code that generates the :term:`response`. #. *Lines 15-17*. Publish a :term:`WSGI` app using an HTTP -- cgit v1.2.3 From 5f0c50dc5f2d739a816f35992a024f616117b44c Mon Sep 17 00:00:00 2001 From: Jay Martin Date: Fri, 26 Sep 2014 06:38:55 -0400 Subject: Update ini.rst grammar tweak (former discussion:https://github.com/Pylons/pyramid/pull/1413#issuecomment-56891441) --- docs/quick_tutorial/ini.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst index 132bc30be..b8720711b 100644 --- a/docs/quick_tutorial/ini.rst +++ b/docs/quick_tutorial/ini.rst @@ -14,8 +14,8 @@ Pyramid has a first-class concept of :ref:`configuration ` distinct from code. This approach is optional, but its presence makes it distinct from other Python web frameworks. It taps into Python's ``setuptools`` -library, which establishes conventions for how Python projects can be -installed and provide "entry points". Pyramid uses an entry point to +library, which establishes conventions for installing and providing +"entry points" for Python projects. Pyramid uses an entry point to let a Pyramid application know where to find the WSGI app. Objectives -- cgit v1.2.3 From c92f49b63f005a63a0d9ac33973e7cec84b07c55 Mon Sep 17 00:00:00 2001 From: deisner Date: Mon, 6 Oct 2014 17:01:40 -0400 Subject: Update functional_testing.rst Make it clear that the tests.py file in the functional_testing directory is the one to edit. Also, warn about a potential "gotcha": if the tests.py file is executable, it will be silently ignored. (I ran into this problem while going through the tutorial. This can happen if the file is being edited on a network file share from a different OS, for example.). --- docs/quick_tutorial/functional_testing.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/quick_tutorial/functional_testing.rst b/docs/quick_tutorial/functional_testing.rst index 205ddf5cb..ddcf6b77d 100644 --- a/docs/quick_tutorial/functional_testing.rst +++ b/docs/quick_tutorial/functional_testing.rst @@ -37,12 +37,14 @@ Steps $ $VENV/bin/python setup.py develop $ $VENV/bin/easy_install webtest -#. Let's extend ``unit_testing/tutorial/tests.py`` to include a +#. Let's extend ``functional_testing/tutorial/tests.py`` to include a functional test: .. literalinclude:: functional_testing/tutorial/tests.py :linenos: + Be sure this file is not executable, or ``nosetests`` may not include your tests. + #. Now run the tests: .. code-block:: bash @@ -67,4 +69,4 @@ execution time of our tests. Extra Credit ============ -#. Why do our functional tests use ``b''``? \ No newline at end of file +#. Why do our functional tests use ``b''``? -- cgit v1.2.3 From 34e9381a628d4e5d061d8fe902579a9b9b9cb33c Mon Sep 17 00:00:00 2001 From: David Eisner Date: Mon, 6 Oct 2014 19:27:36 -0400 Subject: Update functional_testing.rst Wrap long line. --- docs/quick_tutorial/functional_testing.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/quick_tutorial/functional_testing.rst b/docs/quick_tutorial/functional_testing.rst index ddcf6b77d..09b05b0bc 100644 --- a/docs/quick_tutorial/functional_testing.rst +++ b/docs/quick_tutorial/functional_testing.rst @@ -43,7 +43,8 @@ Steps .. literalinclude:: functional_testing/tutorial/tests.py :linenos: - Be sure this file is not executable, or ``nosetests`` may not include your tests. + Be sure this file is not executable, or ``nosetests`` may not + include your tests. #. Now run the tests: -- cgit v1.2.3 From 9095de6ac110a7d31ea76a5dbb5b65408897c75e Mon Sep 17 00:00:00 2001 From: David Eisner Date: Wed, 8 Oct 2014 11:46:21 -0400 Subject: Minor edits to Templating With jinja2 * Fix grammar, order of operations in step 1. * Remove Analysis section reference to pyramid.includes in functional tests, which is no longer accurate. --- docs/quick_tutorial/jinja2.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/quick_tutorial/jinja2.rst b/docs/quick_tutorial/jinja2.rst index ad6da7a9e..613542349 100644 --- a/docs/quick_tutorial/jinja2.rst +++ b/docs/quick_tutorial/jinja2.rst @@ -20,8 +20,8 @@ Objectives Steps ===== -#. In this step let's start by installing the ``pyramid_jinja2`` - add-on, the copying the ``view_class`` step's directory: +#. In this step let's start by copying the ``view_class`` step's + directory, and then installing the ``pyramid_jinja2`` add-on. .. code-block:: bash @@ -72,9 +72,6 @@ Our view code stayed largely the same. We simply changed the file extension on the renderer. For the template, the syntax for Chameleon and Jinja2's basic variable insertion is very similar. -Our functional tests don't have ``development.ini`` so they needed the -``pyramid.includes`` to be setup in the test setup. - Extra Credit ============ -- cgit v1.2.3 From ee2313a6842d12ae0db91c14da3855e3f7dd0632 Mon Sep 17 00:00:00 2001 From: Jay Martin Date: Tue, 14 Oct 2014 09:09:36 -0400 Subject: typo fix --- docs/quick_tutorial/forms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/forms.rst b/docs/quick_tutorial/forms.rst index e8bc0c8b4..b08167edc 100644 --- a/docs/quick_tutorial/forms.rst +++ b/docs/quick_tutorial/forms.rst @@ -104,7 +104,7 @@ assets which need to be published. We don't have to know where on disk it is located. We point at the package, then the path inside the package. We just need to include a call to ``add_static_view`` to make that -directory available at a URL. For Pyramid-specific pages, +directory available at a URL. For Pyramid-specific packages, Pyramid provides a facility (``config.include()``) which even makes that unnecessary for consumers of a package. (Deform is not specific to Pyramid.) -- cgit v1.2.3 From 6aa96d5d40f8c420f020ae187d6842e01e6c668c Mon Sep 17 00:00:00 2001 From: Jay Martin Date: Tue, 14 Oct 2014 09:19:47 -0400 Subject: fix route name in comments --- docs/quick_tutorial/more_view_classes/tutorial/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quick_tutorial/more_view_classes/tutorial/views.py b/docs/quick_tutorial/more_view_classes/tutorial/views.py index 635de0520..fc0ccc37f 100644 --- a/docs/quick_tutorial/more_view_classes/tutorial/views.py +++ b/docs/quick_tutorial/more_view_classes/tutorial/views.py @@ -25,13 +25,13 @@ class TutorialViews: def hello(self): return {'page_title': 'Hello View'} - # Posting to /home via the "Edit" submit button + # Posting to /hello via the "Edit" submit button @view_config(request_method='POST', renderer='edit.pt') def edit(self): new_name = self.request.params['new_name'] return {'page_title': 'Edit View', 'new_name': new_name} - # Posting to /home via the "Delete" submit button + # Posting to /hello via the "Delete" submit button @view_config(request_method='POST', request_param='form.delete', renderer='delete.pt') def delete(self): -- cgit v1.2.3 From ac1bc3cb042fce04696bd799e095722be0d63fb9 Mon Sep 17 00:00:00 2001 From: Jay Martin Date: Tue, 14 Oct 2014 09:09:36 -0400 Subject: typo fix --- docs/quick_tutorial/forms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/forms.rst b/docs/quick_tutorial/forms.rst index e8bc0c8b4..b08167edc 100644 --- a/docs/quick_tutorial/forms.rst +++ b/docs/quick_tutorial/forms.rst @@ -104,7 +104,7 @@ assets which need to be published. We don't have to know where on disk it is located. We point at the package, then the path inside the package. We just need to include a call to ``add_static_view`` to make that -directory available at a URL. For Pyramid-specific pages, +directory available at a URL. For Pyramid-specific packages, Pyramid provides a facility (``config.include()``) which even makes that unnecessary for consumers of a package. (Deform is not specific to Pyramid.) -- cgit v1.2.3 From 2c3ed01554f0fdceeff425b44f7f80c55b9a3545 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 14 Oct 2014 10:03:28 -0500 Subject: fix comment on hello view --- docs/quick_tutorial/more_view_classes/tutorial/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quick_tutorial/more_view_classes/tutorial/views.py b/docs/quick_tutorial/more_view_classes/tutorial/views.py index fc0ccc37f..156e468a9 100644 --- a/docs/quick_tutorial/more_view_classes/tutorial/views.py +++ b/docs/quick_tutorial/more_view_classes/tutorial/views.py @@ -5,7 +5,7 @@ from pyramid.view import ( @view_defaults(route_name='hello') -class TutorialViews: +class TutorialViews(object): def __init__(self, request): self.request = request self.view_name = 'TutorialViews' @@ -25,13 +25,13 @@ class TutorialViews: def hello(self): return {'page_title': 'Hello View'} - # Posting to /hello via the "Edit" submit button + # Posting to /howdy/first/last via the "Edit" submit button @view_config(request_method='POST', renderer='edit.pt') def edit(self): new_name = self.request.params['new_name'] return {'page_title': 'Edit View', 'new_name': new_name} - # Posting to /hello via the "Delete" submit button + # Posting to /howdy/first/last via the "Delete" submit button @view_config(request_method='POST', request_param='form.delete', renderer='delete.pt') def delete(self): -- cgit v1.2.3 From ae6c883018560fe7b1ef02f21b1ceacf2e75528f Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 7 May 2014 22:54:40 -0500 Subject: add config.root_package attribute --- CHANGES.txt | 5 +++++ pyramid/config/__init__.py | 17 +++++++++++++++++ pyramid/tests/test_config/test_init.py | 12 ++++++++++++ 3 files changed, 34 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 63987d980..d66534752 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,11 @@ Features - Cache busting for static resources has been added and is available via a new argument to ``pyramid.config.Configurator.add_static_view``: ``cachebust``. +- Add ``pyramid.config.Configurator.root_package`` attribute and init + parameter to assist with includeable packages that wish to resolve + resources relative to the package in which the ``Configurator`` was created. + See https://github.com/Pylons/pyramid/pull/1337 + Bug Fixes --------- diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index ebaae38a9..cfa35ec6c 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -125,6 +125,14 @@ class Configurator( is passed (the default), the package is assumed to be the Python package in which the *caller* of the ``Configurator`` constructor lives. + If the ``root_package`` is passed, it will propagate through the + configuration hierarchy as a way for included packages to locate + resources relative to the package in which the main ``Configurator`` was + created. If ``None`` is passed (the default), the ``root_package`` will + be derived from the ``package`` argument. The ``package`` attribute is + always pointing at the package being included when using :meth:`.include`, + whereas the ``root_package`` does not change. + If the ``settings`` argument is passed, it should be a Python dictionary representing the :term:`deployment settings` for this application. These are later retrievable using the @@ -243,6 +251,9 @@ class Configurator( .. versionadded:: 1.3 The ``introspection`` argument. + + .. versionadded:: 1.6 + The ``root_package`` argument. """ manager = manager # for testing injection venusian = venusian # for testing injection @@ -272,13 +283,17 @@ class Configurator( exceptionresponse_view=default_exceptionresponse_view, route_prefix=None, introspection=True, + root_package=None, ): if package is None: package = caller_package() + if root_package is None: + root_package = package name_resolver = DottedNameResolver(package) self.name_resolver = name_resolver self.package_name = name_resolver.get_package_name() self.package = name_resolver.get_package() + self.root_package = root_package self.registry = registry self.autocommit = autocommit self.route_prefix = route_prefix @@ -747,6 +762,7 @@ class Configurator( configurator = self.__class__( registry=self.registry, package=package_of(module), + root_package=self.root_package, autocommit=self.autocommit, route_prefix=route_prefix, ) @@ -806,6 +822,7 @@ class Configurator( configurator = self.__class__( registry=self.registry, package=package, + root_package=self.root_package, autocommit=self.autocommit, route_prefix=self.route_prefix, introspection=self.introspection, diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index d6dba17f6..1e58e4d0f 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -736,6 +736,18 @@ pyramid.tests.test_config.dummy_include2""", else: # pragma: no cover raise AssertionError + def test_include_constant_root_package(self): + from pyramid import tests + from pyramid.tests import test_config + config = self._makeOne(root_package=tests) + results = {} + def include(config): + results['package'] = config.package + results['root_package'] = config.root_package + config.include(include) + self.assertEqual(results['root_package'], tests) + self.assertEqual(results['package'], test_config) + def test_action_branching_kw_is_None(self): config = self._makeOne(autocommit=True) self.assertEqual(config.action('discrim'), None) -- cgit v1.2.3 From ab2a77274584700fbcd508d294ff39cb81a99cfd Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 20 Oct 2014 18:06:53 -0500 Subject: reference more pull requests in the change log --- CHANGES.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 63987d980..38c348e67 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,22 +6,26 @@ Features - Cache busting for static resources has been added and is available via a new argument to ``pyramid.config.Configurator.add_static_view``: ``cachebust``. + See https://github.com/Pylons/pyramid/pull/1380 Bug Fixes --------- - ``pyramid.wsgi.wsgiapp`` and ``pyramid.wsgi.wsgiapp2`` now raise ``ValueError`` when accidentally passed ``None``. + See https://github.com/Pylons/pyramid/pull/1320 - Fix an issue whereby predicates would be resolved as maybe_dotted in the introspectable but not when passed for registration. This would mean that - add_route_predicate for example can not take a string and turn it into the - actual callable function. + ``add_route_predicate`` for example can not take a string and turn it into + the actual callable function. + See https://github.com/Pylons/pyramid/pull/1306 - Fix ``pyramid.testing.setUp`` to return a ``Configurator`` with a proper package. Previously it was not possible to do package-relative includes using the returned ``Configurator`` during testing. There is now a ``package`` argument that can override this behavior as well. + See https://github.com/Pylons/pyramid/pull/1322 - Fix an issue where a ``pyramid.response.FileResponse`` may apply a charset where it does not belong. See https://github.com/Pylons/pyramid/pull/1251 -- cgit v1.2.3 From ba0593aa9488a32193113048f14189b76d374102 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 4 Nov 2014 17:16:27 -0600 Subject: fix squashed docstring on set_request_property fixes #1436 --- pyramid/config/factories.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 1990c377a..5ce1081c6 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -1,4 +1,4 @@ -from zope.deprecation import deprecate +from zope.deprecation import deprecated from zope.interface import implementer from pyramid.interfaces import ( @@ -180,8 +180,6 @@ class FactoriesConfiguratorMixin(object): introspectables=(intr,)) @action_method - @deprecate('set_request_propery() is deprecated as of Pyramid 1.5; use ' - 'add_request_method() with the property=True argument instead') def set_request_property(self, callable, name=None, reify=False): """ Add a property to the request object. @@ -195,6 +193,11 @@ class FactoriesConfiguratorMixin(object): self.add_request_method( callable, name=name, property=not reify, reify=reify) + deprecated( + set_request_property, + 'set_request_propery() is deprecated as of Pyramid 1.5; use ' + 'add_request_method() with the property=True argument instead') + @implementer(IRequestExtensions) class _RequestExtensions(object): def __init__(self): -- cgit v1.2.3 From a3033c77c423ad2065c1d7ae65b9c61db8ce6f01 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Thu, 6 Nov 2014 23:36:41 -0700 Subject: Update documentation for exceptions --- pyramid/httpexceptions.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index ebee39ada..415dfbe7f 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -868,7 +868,12 @@ class HTTPUnprocessableEntity(HTTPClientError): subclass of :class:`~HTTPClientError` This indicates that the server is unable to process the contained - instructions. Only for WebDAV. + instructions. + + May be used to notify the client that their JSON/XML is well formed, but + not correct for the current request. + + See RFC4918 section 11 for more information. code: 422, title: Unprocessable Entity """ @@ -881,7 +886,7 @@ class HTTPLocked(HTTPClientError): """ subclass of :class:`~HTTPClientError` - This indicates that the resource is locked. Only for WebDAV + This indicates that the resource is locked. code: 423, title: Locked """ @@ -896,7 +901,6 @@ class HTTPFailedDependency(HTTPClientError): This indicates that the method could not be performed because the requested action depended on another action and that action failed. - Only for WebDAV. code: 424, title: Failed Dependency """ -- cgit v1.2.3 From 2a079b541b7c917d2be360ea0c4bd0d1ac6a4556 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 9 Nov 2014 11:56:54 -0800 Subject: - add Translation Context term to Glossary to allow Sphinx to build docs, in reference to a recent update in the docstrings to the package translationstring https://github.com/Pylons/translationstring/blame/master/translationstring/__init__.py#L50 --- docs/glossary.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index deb4c1c8b..ef7e9a9ae 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -749,9 +749,16 @@ Glossary made. For example the word "java" might be translated differently if the translation domain is "programming-languages" than would be if the translation domain was "coffee". A - translation domain is represnted by a collection of ``.mo`` files + translation domain is represented by a collection of ``.mo`` files within one or more :term:`translation directory` directories. + Translation Context + A string representing the "context" in which a translation was + made within a given :term:`translation domain`. See the gettext + documentation, `11.2.5 Using contexts for solving ambiguities + `_ + for more information. + Translator A callable which receives a :term:`translation string` and returns a translated Unicode object for the purposes of internationalization. A -- cgit v1.2.3 From 903f48c1e2a9a309b7386f3d69ae20433d0e7dfa Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 9 Nov 2014 20:44:10 -0800 Subject: - rewrap - add missing clause in last paragraph --- docs/quick_tutorial/debugtoolbar.rst | 52 +++++++++++++++++------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst index 90750c633..d138eb760 100644 --- a/docs/quick_tutorial/debugtoolbar.rst +++ b/docs/quick_tutorial/debugtoolbar.rst @@ -58,33 +58,31 @@ Steps Analysis ======== -``pyramid_debugtoolbar`` is a full-fledged Python package, -available on PyPI just like thousands of other Python packages. Thus we -start by installing the ``pyramid_debugtoolbar`` package into our -virtual environment using normal Python package installation commands. - -The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on, -which means we need to include its add-on configuration into our web -application. We could do this with imperative configuration in -``tutorial/__init__.py`` by using ``config.include``. Pyramid also -supports wiring in add-on configuration via our ``development.ini`` -using ``pyramid.includes``. We use this to load the configuration for -the debugtoolbar. - -You'll now see an attractive button on the right side of -your browser, which you may click to provide introspective access to debugging -information in a new browser tab. Even better, if your web application -generates an error, -you will see a nice traceback on the screen. When you want to disable -this toolbar, no need to change code: you can remove it from -``pyramid.includes`` in the relevant ``.ini`` configuration file (thus -showing why configuration files are handy.) - -Note injects a small amount of html/css into your app just before the closing -```` tag in order to display itself. If you -start to experience otherwise inexplicable client-side weirdness, you can shut -it off by commenting out the ``pyramid_debugtoolbar`` line in -``pyramid.includes`` temporarily. +``pyramid_debugtoolbar`` is a full-fledged Python package, available on PyPI +just like thousands of other Python packages. Thus we start by installing the +``pyramid_debugtoolbar`` package into our virtual environment using normal +Python package installation commands. + +The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on, which +means we need to include its add-on configuration into our web application. We +could do this with imperative configuration in ``tutorial/__init__.py`` by +using ``config.include``. Pyramid also supports wiring in add-on configuration +via our ``development.ini`` using ``pyramid.includes``. We use this to load +the configuration for the debugtoolbar. + +You'll now see an attractive button on the right side of your browser, which +you may click to provide introspective access to debugging information in a +new browser tab. Even better, if your web application generates an error, you +will see a nice traceback on the screen. When you want to disable this +toolbar, no need to change code: you can remove it from ``pyramid.includes`` +in the relevant ``.ini`` configuration file (thus showing why configuration +files are handy.) + +Note that the toolbar injects a small amount of html/css into your app just +before the closing ```` tag in order to display itself. If you start to +experience otherwise inexplicable client-side weirdness, you can shut it off +by commenting out the ``pyramid_debugtoolbar`` line in ``pyramid.includes`` +temporarily. .. seealso:: See also :ref:`pyramid_debugtoolbar `. -- cgit v1.2.3 From 95bf541f94513b5ec2585c5ecf9f9aa684853676 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 9 Nov 2014 20:45:37 -0800 Subject: - add missing "has" --- docs/quick_tutorial/logging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/logging.rst b/docs/quick_tutorial/logging.rst index 855ded59f..e07d23d6d 100644 --- a/docs/quick_tutorial/logging.rst +++ b/docs/quick_tutorial/logging.rst @@ -16,7 +16,7 @@ we might need to detect problems when other people use the site. We need *logging*. Fortunately Pyramid uses the normal Python approach to logging. The -scaffold generated, in your ``development.ini``, a number of lines that +scaffold generated, in your ``development.ini``, has a number of lines that configure the logging for you to some reasonable defaults. You then see messages sent by Pyramid (for example, when a new request comes in.) -- cgit v1.2.3 From 18566a29e5b3fa88c7ce4732ccb6beee2c8a05c6 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 9 Nov 2014 23:41:14 -0600 Subject: update changelog from #1376 --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index d5ad8e094..617e1497c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -40,6 +40,9 @@ Bug Fixes type, unlike any previous version of Python. See https://github.com/Pylons/pyramid/issues/1360 for more information. +- ``pcreate`` now normalizes the package name by converting hyphens to + underscores. See https://github.com/Pylons/pyramid/pull/1376 + Docs ---- -- cgit v1.2.3 From 909486ab9321bbac8018e202a42b290a096d2705 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 9 Nov 2014 23:50:48 -0600 Subject: update changelog for #1373 --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 617e1497c..0605ed308 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -43,6 +43,10 @@ Bug Fixes - ``pcreate`` now normalizes the package name by converting hyphens to underscores. See https://github.com/Pylons/pyramid/pull/1376 +- Fix an issue with the final response/finished callback being unable to + add another callback to the list. See + https://github.com/Pylons/pyramid/pull/1373 + Docs ---- -- cgit v1.2.3 From 45eb7db00ca472ccd8230f79a01e0eff5c3597ce Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 10 Nov 2014 00:13:32 -0600 Subject: continue #1326 by adding line numbers to development.ini --- pyramid/scaffolds/alchemy/development.ini_tmpl | 2 +- pyramid/scaffolds/starter/development.ini_tmpl | 2 +- pyramid/scaffolds/zodb/development.ini_tmpl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyramid/scaffolds/alchemy/development.ini_tmpl b/pyramid/scaffolds/alchemy/development.ini_tmpl index e54a8609c..448803c8f 100644 --- a/pyramid/scaffolds/alchemy/development.ini_tmpl +++ b/pyramid/scaffolds/alchemy/development.ini_tmpl @@ -68,4 +68,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/pyramid/scaffolds/starter/development.ini_tmpl b/pyramid/scaffolds/starter/development.ini_tmpl index 842cd61d9..c2a28e178 100644 --- a/pyramid/scaffolds/starter/development.ini_tmpl +++ b/pyramid/scaffolds/starter/development.ini_tmpl @@ -57,4 +57,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl index f57d559bf..199ddfab4 100644 --- a/pyramid/scaffolds/zodb/development.ini_tmpl +++ b/pyramid/scaffolds/zodb/development.ini_tmpl @@ -62,4 +62,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s -- cgit v1.2.3 From ba5444531cccf50e7d8b146f80cd22b1508f4bcf Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 10 Nov 2014 00:15:53 -0600 Subject: update changelog --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 0605ed308..927e7ce2a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,6 +13,9 @@ Features resources relative to the package in which the ``Configurator`` was created. See https://github.com/Pylons/pyramid/pull/1337 +- Added line numbers to the log formatters in the scaffolds to assist with + debugging. See https://github.com/Pylons/pyramid/pull/1326 + Bug Fixes --------- -- cgit v1.2.3 From 7dd39020afbd50f6b27e03bb81ace700ae280bef Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 10 Nov 2014 00:27:46 -0600 Subject: update changelog with new http exceptions from #1372 --- CHANGES.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 927e7ce2a..c1b729b3f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,11 @@ Features - Added line numbers to the log formatters in the scaffolds to assist with debugging. See https://github.com/Pylons/pyramid/pull/1326 +- Add new HTTP exception objects for status codes + ``428 Precondition Required``, ``429 Too Many Requests`` and + ``431 Request Header Fields Too Large`` in ``pyramid.httpexceptions``. + See https://github.com/Pylons/pyramid/pull/1372/files + Bug Fixes --------- -- cgit v1.2.3 From 73b1622d2610fd67ed2f8dd71dac2d9e22d76605 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 10 Nov 2014 01:49:03 -0600 Subject: moar changelogs! --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index c1b729b3f..cf2cced51 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,6 +11,9 @@ Features - Add ``pyramid.config.Configurator.root_package`` attribute and init parameter to assist with includeable packages that wish to resolve resources relative to the package in which the ``Configurator`` was created. + This is especially useful for addons that need to load asset specs from + settings, in which case it is natural for a user to define things relative + to their own packages. See https://github.com/Pylons/pyramid/pull/1337 - Added line numbers to the log formatters in the scaffolds to assist with -- cgit v1.2.3 From 187fd8ed07693017d743351cfd58f1327c1abb08 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Mon, 10 Nov 2014 01:05:39 -0700 Subject: Change autoclass to autoexception Fixes #1388 or part thereof --- docs/api/exceptions.rst | 12 +++--- docs/api/httpexceptions.rst | 94 ++++++++++++++++++++++----------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst index 0c630571f..faca0fbb6 100644 --- a/docs/api/exceptions.rst +++ b/docs/api/exceptions.rst @@ -5,14 +5,14 @@ .. automodule:: pyramid.exceptions - .. autoclass:: BadCSRFToken + .. autoexception:: BadCSRFToken - .. autoclass:: PredicateMismatch + .. autoexception:: PredicateMismatch - .. autoclass:: Forbidden + .. autoexception:: Forbidden - .. autoclass:: NotFound + .. autoexception:: NotFound - .. autoclass:: ConfigurationError + .. autoexception:: ConfigurationError - .. autoclass:: URLDecodeError + .. autoexception:: URLDecodeError diff --git a/docs/api/httpexceptions.rst b/docs/api/httpexceptions.rst index b50f10beb..d4cf97f1d 100644 --- a/docs/api/httpexceptions.rst +++ b/docs/api/httpexceptions.rst @@ -13,96 +13,96 @@ .. autofunction:: exception_response - .. autoclass:: HTTPException + .. autoexception:: HTTPException - .. autoclass:: HTTPOk + .. autoexception:: HTTPOk - .. autoclass:: HTTPRedirection + .. autoexception:: HTTPRedirection - .. autoclass:: HTTPError + .. autoexception:: HTTPError - .. autoclass:: HTTPClientError + .. autoexception:: HTTPClientError - .. autoclass:: HTTPServerError + .. autoexception:: HTTPServerError - .. autoclass:: HTTPCreated + .. autoexception:: HTTPCreated - .. autoclass:: HTTPAccepted + .. autoexception:: HTTPAccepted - .. autoclass:: HTTPNonAuthoritativeInformation + .. autoexception:: HTTPNonAuthoritativeInformation - .. autoclass:: HTTPNoContent + .. autoexception:: HTTPNoContent - .. autoclass:: HTTPResetContent + .. autoexception:: HTTPResetContent - .. autoclass:: HTTPPartialContent + .. autoexception:: HTTPPartialContent - .. autoclass:: HTTPMultipleChoices + .. autoexception:: HTTPMultipleChoices - .. autoclass:: HTTPMovedPermanently + .. autoexception:: HTTPMovedPermanently - .. autoclass:: HTTPFound + .. autoexception:: HTTPFound - .. autoclass:: HTTPSeeOther + .. autoexception:: HTTPSeeOther - .. autoclass:: HTTPNotModified + .. autoexception:: HTTPNotModified - .. autoclass:: HTTPUseProxy + .. autoexception:: HTTPUseProxy - .. autoclass:: HTTPTemporaryRedirect + .. autoexception:: HTTPTemporaryRedirect - .. autoclass:: HTTPBadRequest + .. autoexception:: HTTPBadRequest - .. autoclass:: HTTPUnauthorized + .. autoexception:: HTTPUnauthorized - .. autoclass:: HTTPPaymentRequired + .. autoexception:: HTTPPaymentRequired - .. autoclass:: HTTPForbidden + .. autoexception:: HTTPForbidden - .. autoclass:: HTTPNotFound + .. autoexception:: HTTPNotFound - .. autoclass:: HTTPMethodNotAllowed + .. autoexception:: HTTPMethodNotAllowed - .. autoclass:: HTTPNotAcceptable + .. autoexception:: HTTPNotAcceptable - .. autoclass:: HTTPProxyAuthenticationRequired + .. autoexception:: HTTPProxyAuthenticationRequired - .. autoclass:: HTTPRequestTimeout + .. autoexception:: HTTPRequestTimeout - .. autoclass:: HTTPConflict + .. autoexception:: HTTPConflict - .. autoclass:: HTTPGone + .. autoexception:: HTTPGone - .. autoclass:: HTTPLengthRequired + .. autoexception:: HTTPLengthRequired - .. autoclass:: HTTPPreconditionFailed + .. autoexception:: HTTPPreconditionFailed - .. autoclass:: HTTPRequestEntityTooLarge + .. autoexception:: HTTPRequestEntityTooLarge - .. autoclass:: HTTPRequestURITooLong + .. autoexception:: HTTPRequestURITooLong - .. autoclass:: HTTPUnsupportedMediaType + .. autoexception:: HTTPUnsupportedMediaType - .. autoclass:: HTTPRequestRangeNotSatisfiable + .. autoexception:: HTTPRequestRangeNotSatisfiable - .. autoclass:: HTTPExpectationFailed + .. autoexception:: HTTPExpectationFailed - .. autoclass:: HTTPUnprocessableEntity + .. autoexception:: HTTPUnprocessableEntity - .. autoclass:: HTTPLocked + .. autoexception:: HTTPLocked - .. autoclass:: HTTPFailedDependency + .. autoexception:: HTTPFailedDependency - .. autoclass:: HTTPInternalServerError + .. autoexception:: HTTPInternalServerError - .. autoclass:: HTTPNotImplemented + .. autoexception:: HTTPNotImplemented - .. autoclass:: HTTPBadGateway + .. autoexception:: HTTPBadGateway - .. autoclass:: HTTPServiceUnavailable + .. autoexception:: HTTPServiceUnavailable - .. autoclass:: HTTPGatewayTimeout + .. autoexception:: HTTPGatewayTimeout - .. autoclass:: HTTPVersionNotSupported + .. autoexception:: HTTPVersionNotSupported - .. autoclass:: HTTPInsufficientStorage + .. autoexception:: HTTPInsufficientStorage -- cgit v1.2.3 From 7a76cd0b183d5080ec863a7d494008e65469f683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Domen=20Ko=C5=BEar?= Date: Tue, 11 Nov 2014 08:02:09 +0100 Subject: fixes #1405 --- pyramid/tests/test_response.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py index a16eb8d33..84ec57757 100644 --- a/pyramid/tests/test_response.py +++ b/pyramid/tests/test_response.py @@ -1,4 +1,5 @@ import io +import mimetypes import os import unittest from pyramid import testing @@ -51,15 +52,11 @@ class TestFileResponse(unittest.TestCase): r.app_iter.close() def test_without_content_type(self): - for suffix, content_type in ( - ('txt', 'text/plain; charset=UTF-8'), - ('xml', 'application/xml; charset=UTF-8'), - ('pdf', 'application/pdf') - ): + for suffix in ('txt', 'xml', 'pdf'): path = self._getPath(suffix) r = self._makeOne(path) - self.assertEqual(r.content_type, content_type.split(';')[0]) - self.assertEqual(r.headers['content-type'], content_type) + self.assertEqual(r.headers['content-type'].split(';')[0], + mimetypes.guess_type(path, strict=False)[0]) r.app_iter.close() def test_python_277_bug_15207(self): -- cgit v1.2.3 From f10d1e8f9e7a8d65218b9fb09efe3b6fa9511bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Domen=20Ko=C5=BEar?= Date: Thu, 12 Dec 2013 21:14:10 +0100 Subject: if view argument is not passed to config.add_notfound_view, use default_exceptionresponse_view --- pyramid/config/views.py | 7 +++++++ pyramid/tests/test_config/test_views.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 5ca696069..fbe7fc712 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -53,6 +53,7 @@ from pyramid.exceptions import ( from pyramid.httpexceptions import ( HTTPForbidden, HTTPNotFound, + default_exceptionresponse_view, ) from pyramid.registry import ( @@ -1671,6 +1672,9 @@ class ViewsConfiguratorMixin(object): config.add_notfound_view(notfound) + If ``view`` argument is not provided, the view callable defaults to + :func:`~pyramid.httpexceptions.default_exceptionresponse_view`. + All arguments except ``append_slash`` have the same meaning as :meth:`pyramid.config.Configurator.add_view` and each predicate argument restricts the set of circumstances under which this notfound @@ -1697,6 +1701,9 @@ class ViewsConfiguratorMixin(object): % arg ) + if not view: + view = default_exceptionresponse_view + settings = dict( view=view, context=HTTPNotFound, diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index a0d9ee0c3..0fb7c734a 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -1860,6 +1860,21 @@ class TestViewsConfigurationMixin(unittest.TestCase): result = view(None, request) self.assertEqual(result, (None, request)) + def test_add_notfound_view_no_view_argument(self): + from zope.interface import implementedBy + from pyramid.interfaces import IRequest + from pyramid.httpexceptions import HTTPNotFound + config = self._makeOne(autocommit=True) + config.setup_registry() + config.add_notfound_view() + request = self._makeRequest(config) + view = self._getViewCallable(config, + ctx_iface=implementedBy(HTTPNotFound), + request_iface=IRequest) + context = HTTPNotFound() + result = view(context, request) + self.assertEqual(result, context) + def test_add_notfound_view_allows_other_predicates(self): from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) -- cgit v1.2.3 From 41ba4dfda4dd0e7fb5c32aaa34c164fe3ad43142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Domen=20Ko=C5=BEar?= Date: Tue, 11 Feb 2014 22:02:55 +0100 Subject: properly detect undefined view --- pyramid/config/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index fbe7fc712..6de5646f6 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1701,7 +1701,7 @@ class ViewsConfiguratorMixin(object): % arg ) - if not view: + if view is None: view = default_exceptionresponse_view settings = dict( -- cgit v1.2.3 From dfa449126a8cb87c58e6e7519df5aecf252d5127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Domen=20Ko=C5=BEar?= Date: Tue, 11 Nov 2014 07:50:58 +0100 Subject: if view argument is not passed to config.add_forbidden_view, use default_exceptionresponse_view --- pyramid/config/views.py | 8 +++++++- pyramid/tests/test_config/test_views.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 6de5646f6..e4171b0c5 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1592,9 +1592,12 @@ class ViewsConfiguratorMixin(object): config.add_forbidden_view(forbidden) + If ``view`` argument is not provided, the view callable defaults to + :func:`~pyramid.httpexceptions.default_exceptionresponse_view`. + All arguments have the same meaning as :meth:`pyramid.config.Configurator.add_view` and each predicate - argument restricts the set of circumstances under which this notfound + argument restricts the set of circumstances under which this forbidden view will be invoked. Unlike :meth:`pyramid.config.Configurator.add_view`, this method will raise an exception if passed ``name``, ``permission``, ``context``, @@ -1610,6 +1613,9 @@ class ViewsConfiguratorMixin(object): % arg ) + if view is None: + view = default_exceptionresponse_view + settings = dict( view=view, context=HTTPForbidden, diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 0fb7c734a..39b8ba70d 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -1783,6 +1783,21 @@ class TestViewsConfigurationMixin(unittest.TestCase): result = view(None, request) self.assertEqual(result, 'OK') + def test_add_forbidden_view_no_view_argument(self): + from zope.interface import implementedBy + from pyramid.interfaces import IRequest + from pyramid.httpexceptions import HTTPForbidden + config = self._makeOne(autocommit=True) + config.setup_registry() + config.add_forbidden_view() + request = self._makeRequest(config) + view = self._getViewCallable(config, + ctx_iface=implementedBy(HTTPForbidden), + request_iface=IRequest) + context = HTTPForbidden() + result = view(context, request) + self.assertEqual(result, context) + def test_add_forbidden_view_allows_other_predicates(self): from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) -- cgit v1.2.3 From 46a268b3e0c3f80974bc9f4471afdc819ba28763 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 11 Nov 2014 01:15:23 -0600 Subject: update changelog --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index cf2cced51..4d697de64 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -58,6 +58,9 @@ Bug Fixes add another callback to the list. See https://github.com/Pylons/pyramid/pull/1373 +- Fix a failing unittest caused by differing mimetypes across various OSs. + See https://github.com/Pylons/pyramid/issues/1405 + Docs ---- -- cgit v1.2.3 From 7b1d4223db73163f46600cf3d3badf4961dddafb Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 11 Nov 2014 01:19:36 -0600 Subject: update changelog --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 4d697de64..b5d08c8ff 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -24,6 +24,12 @@ Features ``431 Request Header Fields Too Large`` in ``pyramid.httpexceptions``. See https://github.com/Pylons/pyramid/pull/1372/files +- Make it simple to define notfound and forbidden views that wish to use + the default exception-response view but with altered predicates and other + configuration options. The ``view`` argument is now optional in + ``config.add_notfound_view`` and ``config.add_forbidden_view``.. + See https://github.com/Pylons/pyramid/issues/494 + Bug Fixes --------- -- cgit v1.2.3 From 940a7a3e3a254ba3b5db333f2a07ab43f5018d98 Mon Sep 17 00:00:00 2001 From: Randall Leeds Date: Thu, 10 Jul 2014 16:29:29 -0700 Subject: add failing test for package root spec static view --- pyramid/tests/test_config/test_views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 39b8ba70d..a82f7f257 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -3898,6 +3898,13 @@ class TestStaticURLInfo(unittest.TestCase): ('http://example.com/', 'anotherpackage:path/', None, None)] self._assertRegistrations(config, expected) + def test_add_package_root(self): + inst = self._makeOne() + config = self._makeConfig() + inst.add(config, 'http://example.com', 'package:') + expected = [('http://example.com/', 'package:', None)] + self._assertRegistrations(config, expected) + def test_add_url_withendslash(self): inst = self._makeOne() config = self._makeConfig() -- cgit v1.2.3 From e7745ac72ff5c5c499722a8cfcc589a77201fc9a Mon Sep 17 00:00:00 2001 From: Randall Leeds Date: Thu, 10 Jul 2014 16:31:31 -0700 Subject: Fix static views with package root spec patterns --- CHANGES.txt | 3 +++ pyramid/config/views.py | 2 +- pyramid/tests/test_config/test_views.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b5d08c8ff..5a0edc566 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -67,6 +67,9 @@ Bug Fixes - Fix a failing unittest caused by differing mimetypes across various OSs. See https://github.com/Pylons/pyramid/issues/1405 +- Fix route generation for static view asset specifications having no path. + See https://github.com/Pylons/pyramid/pull/1377 + Docs ---- diff --git a/pyramid/config/views.py b/pyramid/config/views.py index e4171b0c5..ba3981388 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1955,7 +1955,7 @@ class StaticURLInfo(object): sep = os.sep else: sep = '/' - if not spec.endswith(sep): + if not spec.endswith(sep) and not spec.endswith(':'): spec = spec + sep # we also make sure the name ends with a slash, purely as a diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index a82f7f257..b0d03fb72 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -3902,7 +3902,7 @@ class TestStaticURLInfo(unittest.TestCase): inst = self._makeOne() config = self._makeConfig() inst.add(config, 'http://example.com', 'package:') - expected = [('http://example.com/', 'package:', None)] + expected = [('http://example.com/', 'package:', None, None)] self._assertRegistrations(config, expected) def test_add_url_withendslash(self): -- cgit v1.2.3 From 0b0ea0a6fff1d238bcc419c7a4feb72ad4969175 Mon Sep 17 00:00:00 2001 From: Randall Leeds Date: Tue, 11 Nov 2014 00:42:43 -0800 Subject: Add myself to contributors --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index c77d3e92c..66f029cb7 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -232,3 +232,5 @@ Contributors - Amit Mane, 2014/01/23 - Fenton Travers, 2014/05/06 + +- Randall Leeds, 2014/11/11 -- cgit v1.2.3 From 555969e05458b2e19305fd0a4f15ac3d27d3a90c Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 11 Nov 2014 00:50:29 -0800 Subject: fix grammar --- docs/narr/i18n.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 3313f8dad..3c804a158 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -354,7 +354,7 @@ command from Gettext: $ mkdir -p es/LC_MESSAGES $ msginit -l es -o es/LC_MESSAGES/myapplication.po -This will create a new the message catalog ``.po`` file will in: +This will create a new message catalog ``.po`` file in: ``myapplication/locale/es/LC_MESSAGES/myapplication.po``. -- cgit v1.2.3 From e51dd09eb1ba4c873f7dec763a1e51c5779801b7 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Tue, 11 Nov 2014 08:17:02 -0800 Subject: Format proutes output and include module instead of repr of view --- pyramid/scripts/proutes.py | 66 ++++++++++++++++++++++++++---- pyramid/tests/test_scripts/test_proutes.py | 10 +++-- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 5784026bb..792030a74 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -4,11 +4,17 @@ import textwrap from pyramid.paster import bootstrap from pyramid.scripts.common import parse_vars +from pyramid.config.views import MultiView + + +PAD = 3 + def main(argv=sys.argv, quiet=False): command = PRoutesCommand(argv, quiet) return command.run() + class PRoutesCommand(object): description = """\ Print all URL dispatch routes used by a Pyramid application in the @@ -43,7 +49,7 @@ class PRoutesCommand(object): def out(self, msg): # pragma: no cover if not self.quiet: print(msg) - + def run(self, quiet=False): if not self.args: self.out('requires a config file argument') @@ -59,13 +65,22 @@ class PRoutesCommand(object): registry = env['registry'] mapper = self._get_mapper(registry) if mapper is not None: + mapped_routes = [('Name', 'Pattern', 'View')] + + max_name = len('Name') + max_pattern = len('Pattern') + max_view = len('View') + routes = mapper.get_routes() - fmt = '%-15s %-30s %-25s' if not routes: return 0 - self.out(fmt % ('Name', 'Pattern', 'View')) - self.out( - fmt % ('-'*len('Name'), '-'*len('Pattern'), '-'*len('View'))) + + mapped_routes.append(( + '-' * max_name, + '-' * max_pattern, + '-' * max_view, + )) + for route in routes: pattern = route.pattern if not pattern.startswith('/'): @@ -73,13 +88,50 @@ class PRoutesCommand(object): request_iface = registry.queryUtility(IRouteRequest, name=route.name) view_callable = None + if (request_iface is None) or (route.factory is not None): - self.out(fmt % (route.name, pattern, '')) + view_callable = '' else: view_callable = registry.adapters.lookup( (IViewClassifier, request_iface, Interface), IView, name='', default=None) - self.out(fmt % (route.name, pattern, view_callable)) + + if view_callable is not None: + if isinstance(view_callable, MultiView): + view_callables = [ + x[1] for x in view_callable.views + ] + else: + view_callables = [view_callable] + + for view_func in view_callables: + view_callable = '%s.%s' % ( + view_func.__module__, + view_func.__name__, + ) + else: + view_callable = str(None) + + if len(route.name) > max_name: + max_name = len(route.name) + + if len(pattern) > max_pattern: + max_pattern = len(pattern) + + if len(view_callable) > max_view: + max_view = len(view_callable) + + mapped_routes.append((route.name, pattern, view_callable)) + + fmt = '%-{0}s %-{1}s %-{2}s'.format( + max_name + PAD, + max_pattern + PAD, + max_view + PAD, + ) + + for route_data in mapped_routes: + self.out(fmt % route_data) + return 0 if __name__ == '__main__': # pragma: no cover diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 25a3cd2e3..45ab57d3a 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -123,8 +123,11 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split()[:3] - self.assertEqual(compare_to, ['a', '/a', ' Date: Mon, 4 Aug 2014 15:08:29 +0200 Subject: Remove duplicate code --- pyramid/config/views.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index e4171b0c5..db67d2582 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1186,10 +1186,6 @@ class ViewsConfiguratorMixin(object): predlist = self.get_predlist('view') def register(permission=permission, renderer=renderer): - # the discrim_func above is guaranteed to have been called already - order = view_intr['order'] - preds = view_intr['predicates'] - phash = view_intr['phash'] request_iface = IRequest if route_name is not None: request_iface = self.registry.queryUtility(IRouteRequest, -- cgit v1.2.3 From c617b7df97a326ca010ddb196978169e2a178c4a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 13 Nov 2014 15:47:21 -0600 Subject: update changelog for fixes from #1453 --- CHANGES.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b5d08c8ff..76f9bc84e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -12,8 +12,8 @@ Features parameter to assist with includeable packages that wish to resolve resources relative to the package in which the ``Configurator`` was created. This is especially useful for addons that need to load asset specs from - settings, in which case it is natural for a user to define things relative - to their own packages. + settings, in which case it is may be natural for a developer to define + imports or assets relative to the top-level package. See https://github.com/Pylons/pyramid/pull/1337 - Added line numbers to the log formatters in the scaffolds to assist with @@ -30,6 +30,9 @@ Features ``config.add_notfound_view`` and ``config.add_forbidden_view``.. See https://github.com/Pylons/pyramid/issues/494 +- Greatly improve the readability of the ``pcreate`` shell script output. + See https://github.com/Pylons/pyramid/pull/1453 + Bug Fixes --------- -- cgit v1.2.3 From 716a20fc79c98e250c90a3d3e9f2218bec181a8d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 16 Nov 2014 23:11:15 -0600 Subject: use hmac.compare_digest if available --- CHANGES.txt | 5 +++++ pyramid/tests/test_util.py | 43 +++++++++++++++++++++++++++++++++++++++++++ pyramid/util.py | 32 ++++++++++++++++++++++++-------- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a893ebae4..bbaa6739e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -33,6 +33,11 @@ Features - Greatly improve the readability of the ``pcreate`` shell script output. See https://github.com/Pylons/pyramid/pull/1453 +- Improve robustness to timing attacks in the ``AuthTktCookieHelper`` and + the ``SignedCookieSessionFactory`` classes by using the stdlib's + ``hmac.compare_digest`` if it is available (such as Python 2.7.7+ and 3.3+). + See https://github.com/Pylons/pyramid/pull/1457 + Bug Fixes --------- diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 2ca4c4a66..a18fa8d16 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -217,6 +217,49 @@ class Test_WeakOrderedSet(unittest.TestCase): self.assertEqual(list(wos), []) self.assertEqual(wos.last, None) +class Test_strings_differ(unittest.TestCase): + def _callFUT(self, *args, **kw): + from pyramid.util import strings_differ + return strings_differ(*args, **kw) + + def test_it(self): + self.assertFalse(self._callFUT(b'foo', b'foo')) + self.assertTrue(self._callFUT(b'123', b'345')) + self.assertTrue(self._callFUT(b'1234', b'123')) + self.assertTrue(self._callFUT(b'123', b'1234')) + + def test_it_with_internal_comparator(self): + result = self._callFUT(b'foo', b'foo', compare_digest=None) + self.assertFalse(result) + + result = self._callFUT(b'123', b'abc', compare_digest=None) + self.assertTrue(result) + + def test_it_with_external_comparator(self): + class DummyComparator(object): + called = False + def __init__(self, ret_val): + self.ret_val = ret_val + + def __call__(self, a, b): + self.called = True + return self.ret_val + + dummy_compare = DummyComparator(True) + result = self._callFUT(b'foo', b'foo', compare_digest=dummy_compare) + self.assertTrue(dummy_compare.called) + self.assertFalse(result) + + dummy_compare = DummyComparator(False) + result = self._callFUT(b'123', b'345', compare_digest=dummy_compare) + self.assertTrue(dummy_compare.called) + self.assertTrue(result) + + dummy_compare = DummyComparator(False) + result = self._callFUT(b'abc', b'abc', compare_digest=dummy_compare) + self.assertTrue(dummy_compare.called) + self.assertTrue(result) + class Test_object_description(unittest.TestCase): def _callFUT(self, object): from pyramid.util import object_description diff --git a/pyramid/util.py b/pyramid/util.py index 6b92f17fc..6de53d559 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -1,4 +1,9 @@ import functools +try: + # py2.7.7+ and py3.3+ have native comparison support + from hmac import compare_digest +except ImportError: # pragma: nocover + compare_digest = None import inspect import traceback import weakref @@ -227,7 +232,7 @@ class WeakOrderedSet(object): oid = self._order[-1] return self._items[oid]() -def strings_differ(string1, string2): +def strings_differ(string1, string2, compare_digest=compare_digest): """Check whether two strings differ while avoiding timing attacks. This function returns True if the given strings differ and False @@ -237,14 +242,25 @@ def strings_differ(string1, string2): http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf - """ - if len(string1) != len(string2): - return True - - invalid_bits = 0 - for a, b in zip(string1, string2): - invalid_bits += a != b + .. versionchanged:: 1.6 + Support :func:`hmac.compare_digest` if it is available (Python 2.7.7+ + and Python 3.3+). + """ + len_eq = len(string1) == len(string2) + if len_eq: + invalid_bits = 0 + left = string1 + else: + invalid_bits = 1 + left = string2 + right = string2 + + if compare_digest is not None: + invalid_bits += not compare_digest(left, right) + else: + for a, b in zip(left, right): + invalid_bits += a != b return invalid_bits != 0 def object_description(object): -- cgit v1.2.3 From e0c09c151ffb9bce0fdc71fb351745e3c282bb18 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 16 Nov 2014 22:15:15 -0800 Subject: Make sure tox fails the cover build if it isn't at 100% --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2bf213ca4..9a9c5a983 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ basepython = python2.6 commands = python setup.py dev - python setup.py nosetests --with-xunit --with-xcoverage + python setup.py nosetests --with-xunit --with-xcoverage --cover-min-percentage=100 deps = nosexcover -- cgit v1.2.3 From 36046388d5cbe99b8d972853efba03b2fb5aa203 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 16 Nov 2014 22:58:22 -0800 Subject: Added coverage for MultiView and long names in proutes --- pyramid/scripts/proutes.py | 6 ++- pyramid/tests/test_scripts/test_proutes.py | 83 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 792030a74..d0c1aa13e 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -4,7 +4,6 @@ import textwrap from pyramid.paster import bootstrap from pyramid.scripts.common import parse_vars -from pyramid.config.views import MultiView PAD = 3 @@ -58,6 +57,8 @@ class PRoutesCommand(object): from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView + from pyramid.interfaces import IMultiView + from zope.interface import Interface config_uri = self.args[0] @@ -72,6 +73,7 @@ class PRoutesCommand(object): max_view = len('View') routes = mapper.get_routes() + if not routes: return 0 @@ -97,7 +99,7 @@ class PRoutesCommand(object): IView, name='', default=None) if view_callable is not None: - if isinstance(view_callable, MultiView): + if IMultiView.providedBy(view_callable): view_callables = [ x[1] for x in view_callable.views ] diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 45ab57d3a..32202af4b 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -128,6 +128,48 @@ class TestPRoutesCommand(unittest.TestCase): ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view'] ) + def test_one_route_with_long_name_one_view_registered(self): + from zope.interface import Interface + from pyramid.registry import Registry + from pyramid.interfaces import IRouteRequest + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IView + registry = Registry() + def view():pass + + class IMyRoute(Interface): + pass + + registry.registerAdapter( + view, + (IViewClassifier, IMyRoute, Interface), + IView, '' + ) + + registry.registerUtility(IMyRoute, IRouteRequest, + name='very_long_name_123') + + command = self._makeOne() + route = dummy.DummyRoute( + 'very_long_name_123', + '/and_very_long_pattern_as_well' + ) + mapper = dummy.DummyMapper(route) + command._get_mapper = lambda *arg: mapper + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split()[:3] + self.assertEqual( + compare_to, + ['very_long_name_123', + '/and_very_long_pattern_as_well', + 'pyramid.tests.test_scripts.test_proutes.view'] + ) + def test_single_route_one_view_registered_with_factory(self): from zope.interface import Interface from pyramid.registry import Registry @@ -157,6 +199,47 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(len(L), 3) self.assertEqual(L[-1].split()[:3], ['a', '/a', '']) + def test_single_route_multiview_registered(self): + from zope.interface import Interface + from pyramid.registry import Registry + from pyramid.interfaces import IRouteRequest + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IMultiView + + registry = Registry() + + def view(): pass + + class IMyRoute(Interface): + pass + + multiview1 = dummy.DummyMultiView( + view, context='context', + view_name='a1' + ) + + registry.registerAdapter( + multiview1, + (IViewClassifier, IMyRoute, Interface), + IMultiView, '' + ) + registry.registerUtility(IMyRoute, IRouteRequest, name='a') + command = self._makeOne() + route = dummy.DummyRoute('a', '/a') + mapper = dummy.DummyMapper(route) + command._get_mapper = lambda *arg: mapper + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split()[:3] + self.assertEqual( + compare_to, + ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view'] + ) + def test__get_mapper(self): from pyramid.registry import Registry from pyramid.urldispatch import RoutesMapper -- cgit v1.2.3 From 6bdda352153a277fb2812746dce5522f441a49f2 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 16 Nov 2014 23:08:25 -0800 Subject: Switch to using tox for travis so coverage is ran --- .travis.yml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4ca998c42..2d54a2b36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,20 @@ # Wire up travis language: python -python: - - 2.6 - - 2.7 - - pypy - - 3.2 - - 3.3 - - 3.4 - - pypy3 +env: + - TOX_ENV=py26 + - TOX_ENV=py27 + - TOX_ENV=py32 + - TOX_ENV=py33 + - TOX_ENV=py34 + - TOX_ENV=pypy + - TOX_ENV=cover -install: python setup.py dev +install: + - pip install tox -script: python setup.py test -q +script: + - tox -e $TOX_ENV notifications: email: -- cgit v1.2.3 From d965c4fa42aa04888e5a829d9975ffec26037c9b Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 16 Nov 2014 23:21:37 -0800 Subject: Use travis_retry in case of timeouts, remove -e $OTX_ENV --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2d54a2b36..dddeb1df7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,10 @@ env: - TOX_ENV=cover install: - - pip install tox + - travis_retry pip install tox script: - - tox -e $TOX_ENV + - travis_retry tox notifications: email: -- cgit v1.2.3 From 1d298deae192918a994423c3fc4ee9cd4bf7e7ca Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 17 Nov 2014 01:50:09 -0600 Subject: fix travis.yml to use the correct TOXENV var --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index dddeb1df7..4ff4939d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,13 @@ language: python env: - - TOX_ENV=py26 - - TOX_ENV=py27 - - TOX_ENV=py32 - - TOX_ENV=py33 - - TOX_ENV=py34 - - TOX_ENV=pypy - - TOX_ENV=cover + - TOXENV=py26 + - TOXENV=py27 + - TOXENV=py32 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=pypy + - TOXENV=cover install: - travis_retry pip install tox -- cgit v1.2.3