From 0a2ea908f35f245b1c4750d8d97ffe645ce29a57 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Wed, 13 Nov 2013 17:36:35 -0800 Subject: tox.ini: Add py34 Useful for testing with the new Python 3.4 alpha versions. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b50e56544..2bf213ca4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py26,py27,py32,py33,pypy,cover + py26,py27,py32,py33,py34,pypy,cover [testenv] commands = -- cgit v1.2.3 From 69b613db258d71caa925f0165030b9974a1610ca Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 21 Feb 2014 21:51:53 -0600 Subject: test cases to reproduce #1246 --- pyramid/tests/test_session.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index 1ad0729b3..6bce764ca 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -519,7 +519,7 @@ def serialize(data, secret): from pyramid.compat import native_ from pyramid.compat import pickle pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) - sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest() + sig = hmac.new(bytes_(secret, 'utf-8'), pickled, sha1).hexdigest() return sig + native_(base64.b64encode(pickled)) class Test_signed_serialize(unittest.TestCase): @@ -531,6 +531,12 @@ class Test_signed_serialize(unittest.TestCase): expected = serialize('123', 'secret') result = self._callFUT('123', 'secret') self.assertEqual(result, expected) + + def test_it_with_highorder_secret(self): + secret = b'La Pe\xc3\xb1a'.decode('utf-8') + expected = serialize('123', secret) + result = self._callFUT('123', secret) + self.assertEqual(result, expected) class Test_signed_deserialize(unittest.TestCase): def _callFUT(self, serialized, secret, hmac=None): @@ -562,6 +568,12 @@ class Test_signed_deserialize(unittest.TestCase): serialized = 'bad' + serialize('123', 'secret') self.assertRaises(ValueError, self._callFUT, serialized, 'secret') + def test_it_with_highorder_secret(self): + secret = b'La Pe\xc3\xb1a'.decode('utf-8') + serialized = serialize('123', secret) + result = self._callFUT(serialized, secret) + self.assertEqual(result, '123') + class Test_check_csrf_token(unittest.TestCase): def _callFUT(self, *args, **kwargs): from ..session import check_csrf_token -- cgit v1.2.3 From adcacf48dbf6eb84a1c1661918f3fb093a929bc2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 21 Feb 2014 21:52:14 -0600 Subject: support high-order characters in UnencryptedCookieSessionFactoryConfig secrets --- CHANGES.txt | 3 +++ pyramid/session.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 84d0694e3..6372c904d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,9 @@ Unreleased - Avoid crash in ``pserve --reload`` under Py3k, when iterating over posiibly mutated ``sys.modules``. +- ``UnencryptedCookieSessionFactoryConfig`` failed if the secret contained + higher order characters. See https://github.com/Pylons/pyramid/issues/1246 + 1.5b1 (2014-02-08) ================== diff --git a/pyramid/session.py b/pyramid/session.py index 3a045b91b..d1964c43e 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -57,7 +57,7 @@ def signed_serialize(data, secret): response.set_cookie('signed_cookie', cookieval) """ pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) - sig = hmac.new(bytes_(secret), pickled, hashlib.sha1).hexdigest() + sig = hmac.new(bytes_(secret, 'utf-8'), pickled, hashlib.sha1).hexdigest() return sig + native_(base64.b64encode(pickled)) def signed_deserialize(serialized, secret, hmac=hmac): @@ -81,7 +81,9 @@ def signed_deserialize(serialized, secret, hmac=hmac): # Badly formed data can make base64 die raise ValueError('Badly formed base64 data: %s' % e) - sig = bytes_(hmac.new(bytes_(secret), pickled, hashlib.sha1).hexdigest()) + sig = bytes_(hmac.new( + bytes_(secret, 'utf-8'), pickled, hashlib.sha1, + ).hexdigest()) # Avoid timing attacks (see # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf) -- cgit v1.2.3 From cf026e2cd8704a679dd83760907d8847deabb18e Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 21 Feb 2014 22:33:33 -0600 Subject: fix regression with code expecting secrets to be encoded with latin-1 --- pyramid/session.py | 16 ++++++++++++---- pyramid/tests/test_session.py | 17 +++++++++++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/pyramid/session.py b/pyramid/session.py index d1964c43e..4dc7bda74 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -57,7 +57,12 @@ def signed_serialize(data, secret): response.set_cookie('signed_cookie', cookieval) """ pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) - sig = hmac.new(bytes_(secret, 'utf-8'), pickled, hashlib.sha1).hexdigest() + try: + # bw-compat with pyramid <= 1.5b1 where latin1 is the default + secret = bytes_(secret) + except UnicodeEncodeError: + secret = bytes_(secret, 'utf-8') + sig = hmac.new(secret, pickled, hashlib.sha1).hexdigest() return sig + native_(base64.b64encode(pickled)) def signed_deserialize(serialized, secret, hmac=hmac): @@ -81,9 +86,12 @@ def signed_deserialize(serialized, secret, hmac=hmac): # Badly formed data can make base64 die raise ValueError('Badly formed base64 data: %s' % e) - sig = bytes_(hmac.new( - bytes_(secret, 'utf-8'), pickled, hashlib.sha1, - ).hexdigest()) + try: + # bw-compat with pyramid <= 1.5b1 where latin1 is the default + secret = bytes_(secret) + except UnicodeEncodeError: + secret = bytes_(secret, 'utf-8') + sig = bytes_(hmac.new(secret, pickled, hashlib.sha1).hexdigest()) # Avoid timing attacks (see # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf) diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index 6bce764ca..f1b1e2296 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -533,10 +533,16 @@ class Test_signed_serialize(unittest.TestCase): self.assertEqual(result, expected) def test_it_with_highorder_secret(self): - secret = b'La Pe\xc3\xb1a'.decode('utf-8') + secret = b'\xce\xb1\xce\xb2\xce\xb3\xce\xb4'.decode('utf-8') expected = serialize('123', secret) result = self._callFUT('123', secret) self.assertEqual(result, expected) + + def test_it_with_latin1_secret(self): + secret = b'La Pe\xc3\xb1a' + expected = serialize('123', secret) + result = self._callFUT('123', secret.decode('latin-1')) + self.assertEqual(result, expected) class Test_signed_deserialize(unittest.TestCase): def _callFUT(self, serialized, secret, hmac=None): @@ -569,11 +575,18 @@ class Test_signed_deserialize(unittest.TestCase): self.assertRaises(ValueError, self._callFUT, serialized, 'secret') def test_it_with_highorder_secret(self): - secret = b'La Pe\xc3\xb1a'.decode('utf-8') + secret = b'\xce\xb1\xce\xb2\xce\xb3\xce\xb4'.decode('utf-8') serialized = serialize('123', secret) result = self._callFUT(serialized, secret) self.assertEqual(result, '123') + # bwcompat with pyramid <= 1.5b1 where latin1 is the default + def test_it_with_latin1_secret(self): + secret = b'La Pe\xc3\xb1a' + serialized = serialize('123', secret) + result = self._callFUT(serialized, secret.decode('latin-1')) + self.assertEqual(result, '123') + class Test_check_csrf_token(unittest.TestCase): def _callFUT(self, *args, **kwargs): from ..session import check_csrf_token -- cgit v1.2.3 From 76144dfae72a0a6d2bd33a414deb296937e90e49 Mon Sep 17 00:00:00 2001 From: Robert Buchholz Date: Mon, 3 Mar 2014 16:24:31 +0100 Subject: Hand RepozeWho1AuthenticationPolicy.remember kwargs to repoze.who #1249 Documentation for pyramid.security.remember supports keyword arguments to hand over to the authentication policy. However, when using RepozeWho1AuthenticationPolicy, all of the kw were dropped in remember. It is my understanding that with repoze.who, additional configuration parameters shall be stored in the identity dictionary. In our case, setting the max_age parameter to the authtkt identifier, would be done using an identity {'repoze.who.userid':principal, 'max_age': 23}. It seems sensible just to hand over kw through the identity dictionary and all users to specify max_age or other parameters such as userdata. --- pyramid/authentication.py | 11 +++++++++-- pyramid/tests/test_authentication.py | 8 ++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pyramid/authentication.py b/pyramid/authentication.py index ba7b864f9..b84981bbc 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -336,12 +336,19 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): return effective_principals def remember(self, request, principal, **kw): - """ Store the ``principal`` as ``repoze.who.userid``.""" + """ Store the ``principal`` as ``repoze.who.userid``. + + The identity to authenticated to :mod:`repoze.who` + will contain the given principal as ``userid``, and + provide all keyword arguments as additional identity + keys. Useful keys could be ``max_age`` or ``userdata``. + """ identifier = self._get_identifier(request) if identifier is None: return [] environ = request.environ - identity = {'repoze.who.userid':principal} + identity = kw + identity['repoze.who.userid'] = principal return identifier.remember(environ, identity) def forget(self, request): diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 79d2a5923..e25e9faa1 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -350,6 +350,14 @@ class TestRepozeWho1AuthenticationPolicy(unittest.TestCase): self.assertEqual(result[0], request.environ) self.assertEqual(result[1], {'repoze.who.userid':'fred'}) + def test_remember_kwargs(self): + authtkt = DummyWhoPlugin() + request = DummyRequest( + {'repoze.who.plugins':{'auth_tkt':authtkt}}) + policy = self._makeOne() + result = policy.remember(request, 'fred', max_age=23) + self.assertEqual(result[1], {'repoze.who.userid':'fred', 'max_age': 23}) + def test_forget_no_plugins(self): request = DummyRequest({}) policy = self._makeOne() -- cgit v1.2.3 From 1bd3157143c7ae6e0a35fa07e4f0888ddd347ab5 Mon Sep 17 00:00:00 2001 From: Robert Buchholz Date: Mon, 3 Mar 2014 16:24:31 +0100 Subject: Hand RepozeWho1AuthenticationPolicy.remember kwargs to repoze.who #1249 Documentation for pyramid.security.remember supports keyword arguments to hand over to the authentication policy. However, when using RepozeWho1AuthenticationPolicy, all of the kw were dropped in remember. It is my understanding that with repoze.who, additional configuration parameters shall be stored in the identity dictionary. In our case, setting the max_age parameter to the authtkt identifier, would be done using an identity {'repoze.who.userid':principal, 'max_age': 23}. It seems sensible just to hand over kw through the identity dictionary and all users to specify max_age or other parameters such as userdata. --- pyramid/authentication.py | 11 +++++++++-- pyramid/tests/test_authentication.py | 8 ++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pyramid/authentication.py b/pyramid/authentication.py index ba7b864f9..b84981bbc 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -336,12 +336,19 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): return effective_principals def remember(self, request, principal, **kw): - """ Store the ``principal`` as ``repoze.who.userid``.""" + """ Store the ``principal`` as ``repoze.who.userid``. + + The identity to authenticated to :mod:`repoze.who` + will contain the given principal as ``userid``, and + provide all keyword arguments as additional identity + keys. Useful keys could be ``max_age`` or ``userdata``. + """ identifier = self._get_identifier(request) if identifier is None: return [] environ = request.environ - identity = {'repoze.who.userid':principal} + identity = kw + identity['repoze.who.userid'] = principal return identifier.remember(environ, identity) def forget(self, request): diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 79d2a5923..e25e9faa1 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -350,6 +350,14 @@ class TestRepozeWho1AuthenticationPolicy(unittest.TestCase): self.assertEqual(result[0], request.environ) self.assertEqual(result[1], {'repoze.who.userid':'fred'}) + def test_remember_kwargs(self): + authtkt = DummyWhoPlugin() + request = DummyRequest( + {'repoze.who.plugins':{'auth_tkt':authtkt}}) + policy = self._makeOne() + result = policy.remember(request, 'fred', max_age=23) + self.assertEqual(result[1], {'repoze.who.userid':'fred', 'max_age': 23}) + def test_forget_no_plugins(self): request = DummyRequest({}) policy = self._makeOne() -- cgit v1.2.3 From 82ec99eebc6a6dfe2903bc0383f31a9b146b3d80 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 3 Mar 2014 16:36:04 -0800 Subject: update inconsistent effective_principals docs fixes #1196 --- pyramid/security.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyramid/security.py b/pyramid/security.py index aa4aece69..c98d4e6cc 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -340,10 +340,9 @@ class AuthenticationAPIMixin(object): @property def effective_principals(self): """ Return the list of 'effective' :term:`principal` identifiers - for the ``request``. This will include the userid of the - currently authenticated user if a user is currently - authenticated. If no :term:`authentication policy` is in effect, - this will return an empty sequence. + for the ``request``. If no :term:`authentication policy` is in effect, + this will return a one-element list containing the + :data:`pyramid.security.Everyone` principal. .. versionadded:: 1.5 """ -- cgit v1.2.3 From b4e59b7168273b774acd975457906dffa176caba Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 5 Mar 2014 18:47:46 -0500 Subject: 'request_method' is *not* limited to the named set. --- pyramid/config/views.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 1ef4efdb3..2f6c758ab 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -879,13 +879,13 @@ class ViewsConfiguratorMixin(object): request_method - This value can be one of the strings ``GET``, ``POST``, ``PUT``, - ``DELETE``, or ``HEAD`` representing an HTTP ``REQUEST_METHOD``, or - a tuple containing one or more of these strings. A view - declaration with this argument ensures that the view will only be - called when the request's ``method`` attribute (aka the - ``REQUEST_METHOD`` of the WSGI environment) string matches a - supplied value. Note that use of ``GET`` also implies that the + This value can be either a strings (such as ``GET``, ``POST``, + ``PUT``, ``DELETE``, or ``HEAD``) representing an HTTP + ``REQUEST_METHOD``, or a tuple containing one or more of these + strings. A view declaration with this argument ensures that the + view will only be called when the ``method`` attribute of the + request (aka the ``REQUEST_METHOD`` of the WSGI environment) matches + a supplied value. Note that use of ``GET`` also implies that the view will respond to ``HEAD`` as of Pyramid 1.4. .. versionchanged:: 1.2 -- cgit v1.2.3 From 58febc5917a82ef1ae25af92b62843ad3c6d5e0e Mon Sep 17 00:00:00 2001 From: Paul Winkler Date: Fri, 7 Mar 2014 11:19:29 -0500 Subject: Minor punctuation and grammar changes to Quick Intro --- docs/quick_tour.rst | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 2d4e679f8..4ab39bb11 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -73,14 +73,14 @@ This simple example is easy to run. Save this as ``app.py`` and run it: Next, open `http://localhost:6543/ `_ in a browser and you will see the ``Hello World!`` message. -New to Python web programming? If so, some lines in module merit +New to Python web programming? If so, some lines in the module merit explanation: #. *Line 10*. The ``if __name__ == '__main__':`` is Python's way of saying "Start here when running from the command line". #. *Lines 11-13*. Use Pyramid's :term:`configurator` to connect - :term:`view` code to particular URL :term:`route`. + :term:`view` code to a particular URL :term:`route`. #. *Lines 6-7*. Implement the view code that generates the :term:`response`. @@ -148,15 +148,15 @@ So far our examples place everything in one file: - the WSGI application launcher Let's move the views out to their own ``views.py`` module and change -the ``app.py`` to scan that module, looking for decorators that setup +the ``app.py`` to scan that module, looking for decorators that set up the views. First, our revised ``app.py``: .. literalinclude:: quick_tour/views/app.py :linenos: We added some more routes, but we also removed the view code. -Our views, and their registrations (via decorators) are now in a module -``views.py`` which is scanned via ``config.scan('views')``. +Our views and their registrations (via decorators) are now in a module +``views.py``, which is scanned via ``config.scan('views')``. We now have a ``views.py`` module that is focused on handling requests and responses: @@ -167,7 +167,7 @@ and responses: We have 4 views, each leading to the other. If you start at ``http://localhost:6543/``, you get a response with a link to the next view. The ``hello_view`` (available at the URL ``/howdy``) has a link -to the ``redirect_view``, which shows issuing a redirect to the final +to the ``redirect_view``, which issues a redirect to the final view. Earlier we saw ``config.add_view`` as one way to configure a view. This @@ -267,7 +267,7 @@ Now lets change our views.py file: Ahh, that looks better. We have a view that is focused on Python code. Our ``@view_config`` decorator specifies a :term:`renderer` that points -our template file. Our view then simply returns data which is then +to our template file. Our view then simply returns data which is then supplied to our template: .. literalinclude:: quick_tour/templating/hello_world.pt @@ -303,7 +303,7 @@ our configuration: config.include('pyramid_jinja2') -The only change in our view...point the renderer at the ``.jinja2`` file: +The only change in our view is to point the renderer at the ``.jinja2`` file: .. literalinclude:: quick_tour/jinja2/views.py :start-after: Start View 1 @@ -356,8 +356,8 @@ template: This link presumes that our CSS is at a URL starting with ``/static/``. What if the site is later moved under ``/somesite/static/``? Or perhaps -web developer changes the arrangement on disk? Pyramid gives a helper -that provides flexibility on URL generation: +a web developer changes the arrangement on disk? Pyramid provides a helper +to allow flexibility on URL generation: .. literalinclude:: quick_tour/static_assets/hello_world.pt :language: html @@ -454,7 +454,7 @@ have much more to offer: Quick Project Startup with Scaffolds ==================================== -So far we have done all of our *Quick Glance* as a single Python file. +So far we have done all of our *Quick Tour* as a single Python file. No Python packages, no structure. Most Pyramid projects, though, aren't developed this way. @@ -479,7 +479,7 @@ let's use that scaffold to make our project: $ pcreate --scaffold pyramid_jinja2_starter hello_world -We next use the normal Python development to setup our package for +We next use the normal Python command to set up our package for development: .. code-block:: bash @@ -534,7 +534,7 @@ take a look at this configuration file. Configuration with ``.ini`` Files ================================= -Earlier in *Quick Glance* we first met Pyramid's configuration system. +Earlier in *Quick Tour* we first met Pyramid's configuration system. At that point we did all configuration in Python code. For example, the port number chosen for our HTTP server was right there in Python code. Our scaffold has moved this decision, and more, into the @@ -556,8 +556,8 @@ into sections: We have a few decisions made for us in this configuration: -#. *Choice of web server*. The ``use = egg:pyramid#wsgiref`` tell - ``pserve`` to the ``wsgiref`` server that is wrapped in the Pyramid +#. *Choice of web server*. The ``use = egg:pyramid#wsgiref`` tells + ``pserve`` to use the ``wsgiref`` server that is wrapped in the Pyramid package. #. *Port number*. ``port = 6543`` tells ``wsgiref`` to listen on port @@ -574,7 +574,7 @@ We have a few decisions made for us in this configuration: Additionally, the ``development.ini`` generated by this scaffold wired up Python's standard logging. We'll now see in the console, for example, -a log on every request that comes in, as well traceback information. +a log on every request that comes in, as well as traceback information. .. seealso:: See also: :ref:`Quick Tutorial Application Configuration `, @@ -585,7 +585,7 @@ a log on every request that comes in, as well traceback information. Easier Development with ``debugtoolbar`` ======================================== -As we introduce the basics we also want to show how to be productive in +As we introduce the basics, we also want to show how to be productive in development and debugging. For example, we just discussed template reloading and earlier we showed ``--reload`` for application reloading. @@ -700,12 +700,12 @@ 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`` 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.) +messages sent by Pyramid (for example, when a new request comes in). Maybe you would like to log messages in your code? In your Python -module, import and setup the logging: +module, import and set up the logging: .. literalinclude:: quick_tour/package/hello_world/views.py :start-after: Start Logging 1 @@ -726,7 +726,7 @@ controls that? These sections in the configuration file: :start-after: Start Sphinx Include :end-before: End Sphinx Include -Our application, a package named ``hello_world``, is setup as a logger +Our application, a package named ``hello_world``, is set up as a logger and configured to log messages at a ``DEBUG`` or higher level. When you visit ``http://localhost:6543`` your console will now show:: @@ -789,7 +789,7 @@ Databases ========= Web applications mean data. Data means databases. Frequently SQL -databases. SQL Databases frequently mean an "ORM" +databases. SQL databases frequently mean an "ORM" (object-relational mapper.) In Python, ORM usually leads to the mega-quality *SQLAlchemy*, a Python package that greatly eases working with databases. -- cgit v1.2.3 From 5c5edadd089f3c3f13751bde3f691ad19339b551 Mon Sep 17 00:00:00 2001 From: sbivol Date: Mon, 10 Mar 2014 16:28:56 +0200 Subject: Remove extra concatenation from request.static_url() fixes issue #1266 --- docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt index d1fea0d7f..3292dfd90 100644 --- a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt +++ b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt @@ -4,10 +4,10 @@ WikiPage: Add/Edit + href="${request.static_url(reqt)}"/> - -- cgit v1.2.3 From 49bcc8e86ded6785c3bddd6972f870b2d2d858a8 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Mon, 10 Mar 2014 09:15:08 -0700 Subject: Add integration tests for Unicode in URL --- pyramid/tests/test_integration.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 9d3a9e004..bc22c2e54 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -640,6 +640,44 @@ class RendererScanAppTest(IntegrationBase, unittest.TestCase): res = testapp.get('/two', status=200) self.assertTrue(b'Two!' in res.body) +class UnicodeInURLTest(unittest.TestCase): + def _makeConfig(self): + from pyramid.config import Configurator + config = Configurator() + return config + + def _makeTestApp(self, config): + from webtest import TestApp + app = config.make_wsgi_app() + return TestApp(app) + + def test_unicode_in_url_404(self): + request_path = b'/avalia%C3%A7%C3%A3o_participante/' + request_path_unicode = u'/avalia\xe7\xe3o_participante/' + + config = self._makeConfig() + testapp = self._makeTestApp(config) + + res = testapp.get(request_path, status=404) + self.assertTrue(request_path_unicode in res.text) + + def test_unicode_in_url_200(self): + request_path = b'/avalia%C3%A7%C3%A3o_participante' + request_path_unicode = u'/avalia\xe7\xe3o_participante' + + def myview(request): + return 'XXX' + + config = self._makeConfig() + config.add_route('myroute', request_path_unicode) + config.add_view(myview, route_name='myroute', renderer='json') + testapp = self._makeTestApp(config) + + res = testapp.get(request_path, status=200) + + self.assertEqual(res.text, '"XXX"') + + class AcceptContentTypeTest(unittest.TestCase): def setUp(self): def hello_view(request): -- cgit v1.2.3 From 0c425da09d966bafd2f4043fa8919f3da4d8abc4 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 11 Mar 2014 08:27:56 -0700 Subject: Remove 'b'; WebTest or WebOb doesn't like Python 3 bytes for URLs --- pyramid/tests/test_integration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index bc22c2e54..48199c419 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -652,7 +652,7 @@ class UnicodeInURLTest(unittest.TestCase): return TestApp(app) def test_unicode_in_url_404(self): - request_path = b'/avalia%C3%A7%C3%A3o_participante/' + request_path = '/avalia%C3%A7%C3%A3o_participante/' request_path_unicode = u'/avalia\xe7\xe3o_participante/' config = self._makeConfig() @@ -662,7 +662,7 @@ class UnicodeInURLTest(unittest.TestCase): self.assertTrue(request_path_unicode in res.text) def test_unicode_in_url_200(self): - request_path = b'/avalia%C3%A7%C3%A3o_participante' + request_path = '/avalia%C3%A7%C3%A3o_participante' request_path_unicode = u'/avalia\xe7\xe3o_participante' def myview(request): -- cgit v1.2.3 From bc87edd3d8df7c5bd4c8d267c626f4fd22ef7443 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 11 Mar 2014 10:10:10 -0700 Subject: Convert from u prefix to byte string and decode because the u prefix is not supported in Python 3.2 --- pyramid/tests/test_integration.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 48199c419..0ab18de7a 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -652,8 +652,8 @@ class UnicodeInURLTest(unittest.TestCase): return TestApp(app) def test_unicode_in_url_404(self): - request_path = '/avalia%C3%A7%C3%A3o_participante/' - request_path_unicode = u'/avalia\xe7\xe3o_participante/' + request_path = '/avalia%C3%A7%C3%A3o_participante' + request_path_unicode = b'/avalia\xc3\xa7\xc3\xa3o_participante'.decode('utf-8') config = self._makeConfig() testapp = self._makeTestApp(config) @@ -663,7 +663,7 @@ class UnicodeInURLTest(unittest.TestCase): def test_unicode_in_url_200(self): request_path = '/avalia%C3%A7%C3%A3o_participante' - request_path_unicode = u'/avalia\xe7\xe3o_participante' + request_path_unicode = b'/avalia\xc3\xa7\xc3\xa3o_participante'.decode('utf-8') def myview(request): return 'XXX' -- cgit v1.2.3 From a99abf808a15fbc02da4c27ab7d46d03668b62ed Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 11 Mar 2014 11:12:13 -0700 Subject: test_unicode_in_url_404: Add comment to clarify why request_path_unicode is expected to be in res.txt --- pyramid/tests/test_integration.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 0ab18de7a..35648ed38 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -659,6 +659,10 @@ class UnicodeInURLTest(unittest.TestCase): testapp = self._makeTestApp(config) res = testapp.get(request_path, status=404) + + # Pyramid default 404 handler outputs: + # u'404 Not Found\n\nThe resource could not be found.\n\n\n' + # u'/avalia\xe7\xe3o_participante\n\n' self.assertTrue(request_path_unicode in res.text) def test_unicode_in_url_200(self): -- cgit v1.2.3 From ba85e591d563ed654f492f7cab5ca492a32a86d7 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 13 Mar 2014 18:17:43 -0400 Subject: Strip redundant logging config from tutorial INI files. Leave it for the 'logging' and 'scaffolds' sections, where it is germane. --- docs/quick_tutorial/authentication/development.ini | 31 ------------------- docs/quick_tutorial/authorization/development.ini | 31 ------------------- docs/quick_tutorial/databases/development.ini | 36 ---------------------- docs/quick_tutorial/debugtoolbar/development.ini | 31 ------------------- docs/quick_tutorial/forms/development.ini | 31 ------------------- .../functional_testing/development.ini | 31 ------------------- docs/quick_tutorial/ini/development.ini | 31 ------------------- docs/quick_tutorial/jinja2/development.ini | 31 ------------------- docs/quick_tutorial/json/development.ini | 31 ------------------- .../more_view_classes/development.ini | 31 ------------------- .../request_response/development.ini | 31 ------------------- docs/quick_tutorial/retail_forms/development.ini | 31 ------------------- docs/quick_tutorial/routing/development.ini | 31 ------------------- docs/quick_tutorial/sessions/development.ini | 31 ------------------- docs/quick_tutorial/static_assets/development.ini | 31 ------------------- docs/quick_tutorial/templating/development.ini | 31 ------------------- docs/quick_tutorial/unit_testing/development.ini | 31 ------------------- docs/quick_tutorial/view_classes/development.ini | 31 ------------------- docs/quick_tutorial/views/development.ini | 31 ------------------- 19 files changed, 594 deletions(-) diff --git a/docs/quick_tutorial/authentication/development.ini b/docs/quick_tutorial/authentication/development.ini index 5d4580ff5..8a39b2fe7 100644 --- a/docs/quick_tutorial/authentication/development.ini +++ b/docs/quick_tutorial/authentication/development.ini @@ -9,34 +9,3 @@ tutorial.secret = 98zd use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/authorization/development.ini b/docs/quick_tutorial/authorization/development.ini index 5d4580ff5..8a39b2fe7 100644 --- a/docs/quick_tutorial/authorization/development.ini +++ b/docs/quick_tutorial/authorization/development.ini @@ -9,34 +9,3 @@ tutorial.secret = 98zd use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/databases/development.ini b/docs/quick_tutorial/databases/development.ini index 270da960f..04c249a62 100644 --- a/docs/quick_tutorial/databases/development.ini +++ b/docs/quick_tutorial/databases/development.ini @@ -11,39 +11,3 @@ sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial, sqlalchemy - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[logger_sqlalchemy] -level = INFO -handlers = -qualname = sqlalchemy.engine - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/debugtoolbar/development.ini b/docs/quick_tutorial/debugtoolbar/development.ini index 470d92c57..52b2a3a41 100644 --- a/docs/quick_tutorial/debugtoolbar/development.ini +++ b/docs/quick_tutorial/debugtoolbar/development.ini @@ -7,34 +7,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/forms/development.ini b/docs/quick_tutorial/forms/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/forms/development.ini +++ b/docs/quick_tutorial/forms/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/functional_testing/development.ini b/docs/quick_tutorial/functional_testing/development.ini index 470d92c57..52b2a3a41 100644 --- a/docs/quick_tutorial/functional_testing/development.ini +++ b/docs/quick_tutorial/functional_testing/development.ini @@ -7,34 +7,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/ini/development.ini b/docs/quick_tutorial/ini/development.ini index ca7d9bf81..8853e2c2b 100644 --- a/docs/quick_tutorial/ini/development.ini +++ b/docs/quick_tutorial/ini/development.ini @@ -5,34 +5,3 @@ use = egg:tutorial use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/jinja2/development.ini b/docs/quick_tutorial/jinja2/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/jinja2/development.ini +++ b/docs/quick_tutorial/jinja2/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/json/development.ini b/docs/quick_tutorial/json/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/json/development.ini +++ b/docs/quick_tutorial/json/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/more_view_classes/development.ini b/docs/quick_tutorial/more_view_classes/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/more_view_classes/development.ini +++ b/docs/quick_tutorial/more_view_classes/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/request_response/development.ini b/docs/quick_tutorial/request_response/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/request_response/development.ini +++ b/docs/quick_tutorial/request_response/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/retail_forms/development.ini b/docs/quick_tutorial/retail_forms/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/retail_forms/development.ini +++ b/docs/quick_tutorial/retail_forms/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/routing/development.ini b/docs/quick_tutorial/routing/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/routing/development.ini +++ b/docs/quick_tutorial/routing/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/sessions/development.ini b/docs/quick_tutorial/sessions/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/sessions/development.ini +++ b/docs/quick_tutorial/sessions/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/static_assets/development.ini b/docs/quick_tutorial/static_assets/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/static_assets/development.ini +++ b/docs/quick_tutorial/static_assets/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/templating/development.ini b/docs/quick_tutorial/templating/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/templating/development.ini +++ b/docs/quick_tutorial/templating/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/unit_testing/development.ini b/docs/quick_tutorial/unit_testing/development.ini index 470d92c57..52b2a3a41 100644 --- a/docs/quick_tutorial/unit_testing/development.ini +++ b/docs/quick_tutorial/unit_testing/development.ini @@ -7,34 +7,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/view_classes/development.ini b/docs/quick_tutorial/view_classes/development.ini index 62e0c5123..4d47e54a5 100644 --- a/docs/quick_tutorial/view_classes/development.ini +++ b/docs/quick_tutorial/view_classes/development.ini @@ -8,34 +8,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/quick_tutorial/views/development.ini b/docs/quick_tutorial/views/development.ini index 470d92c57..52b2a3a41 100644 --- a/docs/quick_tutorial/views/development.ini +++ b/docs/quick_tutorial/views/development.ini @@ -7,34 +7,3 @@ pyramid.includes = use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 - -# Begin logging configuration - -[loggers] -keys = root, tutorial - -[logger_tutorial] -level = DEBUG -handlers = -qualname = tutorial - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = INFO -handlers = console - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration -- cgit v1.2.3 From ee824a80bab0b30f7f8f466a9c93765b35c1c677 Mon Sep 17 00:00:00 2001 From: thapar Date: Mon, 17 Mar 2014 20:16:44 -0400 Subject: Added note to place login/logout route definitions before `/{pagename}` route Resolves https://github.com/Pylons/pyramid/issues/1274 --- docs/tutorials/wiki2/authorization.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index 1e5d0dcbf..1417dbdd8 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -207,6 +207,19 @@ routes: :linenos: :language: python +.. note:: These lines must be added `before` this ``view_page`` route + definition: + .. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 32 + :linenos: + :language: python + This is because ``view_page``'s route definition uses a catch-all + "replacement marker" ``/{pagename}`` (see :ref:_route_pattern_syntax ) + which will catch any route that was not already caught by any + route listed above it in ``__init__.py``. Hence, for ``login`` and + ``logout`` views to have the opportunity of being matched + (or "caught"), they must be above ``/{pagename}``. + Add Login and Logout Views ~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From b6d8cf9c2eb8f7dd03fa3487b8e401940c314fb4 Mon Sep 17 00:00:00 2001 From: thapar Date: Mon, 17 Mar 2014 21:26:01 -0400 Subject: Corrected inline markup to use * for italics --- docs/tutorials/wiki2/authorization.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index 1417dbdd8..e3811b338 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -207,7 +207,7 @@ routes: :linenos: :language: python -.. note:: These lines must be added `before` this ``view_page`` route +.. note:: These lines must be added *before* this ``view_page`` route definition: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 32 -- cgit v1.2.3 From cf4023ad25637720ed5ccd1e7cdffd24da4c47cb Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 21 Mar 2014 06:19:27 -0700 Subject: - correct bad .rst syntax --- docs/tutorials/wiki2/authorization.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index e3811b338..2e35574fd 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -207,14 +207,16 @@ routes: :linenos: :language: python -.. note:: These lines must be added *before* this ``view_page`` route - definition: +.. note:: The preceding lines must be added *before* the following + ``view_page`` route definition: + .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 32 :linenos: :language: python + This is because ``view_page``'s route definition uses a catch-all - "replacement marker" ``/{pagename}`` (see :ref:_route_pattern_syntax ) + "replacement marker" ``/{pagename}`` (see :ref:`route_pattern_syntax`) which will catch any route that was not already caught by any route listed above it in ``__init__.py``. Hence, for ``login`` and ``logout`` views to have the opportunity of being matched -- cgit v1.2.3 From 49923dbf430fc6becc92133ba7672e93f1bc955e Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 21 Mar 2014 06:47:55 -0700 Subject: - fix indentation of code block --- docs/quick_tutorial/scaffolds.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/quick_tutorial/scaffolds.rst b/docs/quick_tutorial/scaffolds.rst index 8ca2d27df..4f2694100 100644 --- a/docs/quick_tutorial/scaffolds.rst +++ b/docs/quick_tutorial/scaffolds.rst @@ -63,11 +63,11 @@ Steps On startup, ``pserve`` logs some output: - .. code-block:: bash + .. code-block:: bash - Starting subprocess with file monitor - Starting server in PID 72213. - Starting HTTP server on http://0.0.0.0:6543 + Starting subprocess with file monitor + Starting server in PID 72213. + Starting HTTP server on http://0.0.0.0:6543 #. Open http://localhost:6543/ in your browser. -- cgit v1.2.3 From 71c979fb9a89cffdc57a9533a61101483c23b93b Mon Sep 17 00:00:00 2001 From: Daniel Haaker Date: Wed, 26 Mar 2014 09:56:15 +0100 Subject: Remove whitespace before the open parenthesis --- docs/quick_tutorial/hello_world/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quick_tutorial/hello_world/app.py b/docs/quick_tutorial/hello_world/app.py index 210075023..0a95f9ad3 100644 --- a/docs/quick_tutorial/hello_world/app.py +++ b/docs/quick_tutorial/hello_world/app.py @@ -4,7 +4,7 @@ from pyramid.response import Response def hello_world(request): - print ('Incoming request') + print('Incoming request') return Response('

Hello World!

') @@ -14,4 +14,4 @@ if __name__ == '__main__': config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) - server.serve_forever() \ No newline at end of file + server.serve_forever() -- cgit v1.2.3 From 92b27dc06a4a73ea4ff0649855a5a84cd30b458c Mon Sep 17 00:00:00 2001 From: Daniel Haaker Date: Wed, 26 Mar 2014 10:10:54 +0100 Subject: Update hello_world.rst Modify 'Extra Credit' to reflect code example --- docs/quick_tutorial/hello_world.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst index 86e1319f0..1a9ba4c9d 100644 --- a/docs/quick_tutorial/hello_world.rst +++ b/docs/quick_tutorial/hello_world.rst @@ -96,13 +96,13 @@ Extra Credit .. code-block:: python - print ('Starting up server on http://localhost:6547') + print('Incoming request') ...instead of: .. code-block:: python - print 'Starting up server on http://localhost:6547' + print 'Incoming request' #. What happens if you return a string of HTML? A sequence of integers? -- cgit v1.2.3 From 32200c3af84c352c066eb2c402496305375912e4 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 27 Mar 2014 22:03:59 -0500 Subject: broadcast 3.4 support --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 3c17af20d..bb443330d 100644 --- a/setup.py +++ b/setup.py @@ -79,6 +79,7 @@ setup(name='pyramid', "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Framework :: Pyramid", -- cgit v1.2.3 From e4dc7443ddf8e5e3d861c66e0cef565a6d907789 Mon Sep 17 00:00:00 2001 From: Wichert Akkerman Date: Tue, 8 Apr 2014 20:07:59 +0200 Subject: Update documentation for Lingua 2 --- docs/narr/i18n.rst | 182 ++++++++----------------------- pyramid/scaffolds/alchemy/setup.cfg_tmpl | 21 ---- pyramid/scaffolds/starter/setup.cfg_tmpl | 21 ---- pyramid/scaffolds/zodb/setup.cfg_tmpl | 21 ---- 4 files changed, 48 insertions(+), 197 deletions(-) diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 5f50ca212..31b63664b 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -245,88 +245,69 @@ GNU gettext uses three types of files in the translation framework, A ``.po`` file is turned into a machine-readable binary file, which is the ``.mo`` file. Compiling the translations to machine code - makes the localized program run faster. + makes the localized program start faster. The tools for working with :term:`gettext` translation files related to a -:app:`Pyramid` application is :term:`Babel` and :term:`Lingua`. Lingua is a -Babel extension that provides support for scraping i18n references out of -Python and Chameleon files. +:app:`Pyramid` application are :term:`Lingua` and :term:`Gettext`. Lingua +can scrape i18n references out of Python and Chameleon files and create +the ``.pot`` file. Gettext includes tools to update a ``.po`` file from +an updated ``.pot`` file and to compile ``.po`` files to ``.mo`` files. .. index:: - single: Babel + single: Gettext single: Lingua .. _installing_babel: -Installing Babel and Lingua -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Installing Lingua and Gettext +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In order for the commands related to working with ``gettext`` translation -files to work properly, you will need to have :term:`Babel` and -:term:`Lingua` installed into the same environment in which :app:`Pyramid` is +files to work properly, you will need to have :term:`Lingua` and +:term:`Gettext` installed into the same environment in which :app:`Pyramid` is installed. Installation on UNIX ++++++++++++++++++++ -If the :term:`virtualenv` into which you've installed your :app:`Pyramid` -application lives in ``/my/virtualenv``, you can install Babel and Lingua -like so: +Gettext is often already installed on UNIX systems. You can check if it is +installed by testing if the ``msgfmt`` command is available. If it is not +available you can install it through the packaging system from your OS; +the package name is almost always ``gettext``. For example on a Debian or +Ubuntu system run this command: .. code-block:: text - $ cd /my/virtualenv - $ $VENV/bin/easy_install Babel lingua + $ sudo apt-get install gettext -Installation on Windows -+++++++++++++++++++++++ - -If the :term:`virtualenv` into which you've installed your :app:`Pyramid` -application lives in ``C:\my\virtualenv``, you can install Babel and Lingua +Installing Lingua is done with the Python packaging tools. If the +:term:`virtualenv` into which you've installed your :app:`Pyramid` application +lives in ``/my/virtualenv``, you can install Lingua like so: .. code-block:: text - C> %VENV%\Scripts\easy_install Babel lingua + $ cd /my/virtualenv + $ $VENV/bin/pip install lingua -.. index:: - single: Babel; message extractors - single: Lingua +Installation on Windows ++++++++++++++++++++++++ -Changing the ``setup.py`` -+++++++++++++++++++++++++ +There are several ways to install Gettext on Windows: it is included in the +`Cygwin `_ collection, or you can use the `installer +from the GnuWin32 `_ +or compile it yourself. Make sure the installation path is added to your +``$PATH``. -You need to add a few boilerplate lines to your application's ``setup.py`` -file in order to properly generate :term:`gettext` files from your -application. -.. note:: See :ref:`project_narr` to learn about the - composition of an application's ``setup.py`` file. +Installing Lingua is done with the Python packaging tools. If the +:term:`virtualenv` into which you've installed your :app:`Pyramid` application +lives in ``C:\my\virtualenv``, you can install Lingua like so: -In particular, add the ``Babel`` and ``lingua`` distributions to the -``install_requires`` list and insert a set of references to :term:`Babel` -*message extractors* within the call to :func:`setuptools.setup` inside your -application's ``setup.py`` file: +.. code-block:: text -.. code-block:: python - :linenos: + C> %VENV%\Scripts\pip install lingua - setup(name="mypackage", - # ... - install_requires = [ - # ... - 'Babel', - 'lingua', - ], - message_extractors = { '.': [ - ('**.py', 'lingua_python', None ), - ('**.pt', 'lingua_xml', None ), - ]}, - ) - -The ``message_extractors`` stanza placed into the ``setup.py`` file causes -the :term:`Babel` message catalog extraction machinery to also consider -``*.pt`` files when doing message id extraction. .. index:: pair: extracting; messages @@ -336,90 +317,20 @@ the :term:`Babel` message catalog extraction machinery to also consider Extracting Messages from Code and Templates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Once Babel and Lingua are installed and your application's ``setup.py`` file -has the correct message extractor references, you may extract a message -catalog template from the code and :term:`Chameleon` templates which reside -in your :app:`Pyramid` application. You run a ``setup.py`` command to -extract the messages: +Once Lingua is installed you may extract a message catalog template from the +code and :term:`Chameleon` templates which reside in your :app:`Pyramid` +application. You run a ``pot-create`` command to extract the messages: .. code-block:: text $ cd /place/where/myapplication/setup.py/lives $ mkdir -p myapplication/locale - $ $VENV/bin/python setup.py extract_messages + $ $VENT/bin/pot-create src > myapplication/locale/myapplication.pot The message catalog ``.pot`` template will end up in: ``myapplication/locale/myapplication.pot``. -.. index:: - single: translation domains - -Translation Domains -+++++++++++++++++++ - -The name ``myapplication`` above in the filename ``myapplication.pot`` -denotes the :term:`translation domain` of the translations that must -be performed to localize your application. By default, the -translation domain is the :term:`project` name of your -:app:`Pyramid` application. - -To change the translation domain of the extracted messages in your project, -edit the ``setup.cfg`` file of your application, The default ``setup.cfg`` -file of a ``pcreate`` -generated :app:`Pyramid` application has stanzas in it -that look something like the following: - -.. code-block:: ini - :linenos: - - [compile_catalog] - directory = myproject/locale - domain = MyProject - statistics = true - - [extract_messages] - add_comments = TRANSLATORS: - output_file = myproject/locale/MyProject.pot - width = 80 - - [init_catalog] - domain = MyProject - input_file = myproject/locale/MyProject.pot - output_dir = myproject/locale - - [update_catalog] - domain = MyProject - input_file = myproject/locale/MyProject.pot - output_dir = myproject/locale - previous = true - -In the above example, the project name is ``MyProject``. To indicate -that you'd like the domain of your translations to be ``mydomain`` -instead, change the ``setup.cfg`` file stanzas to look like so: - -.. code-block:: ini - :linenos: - - [compile_catalog] - directory = myproject/locale - domain = mydomain - statistics = true - - [extract_messages] - add_comments = TRANSLATORS: - output_file = myproject/locale/mydomain.pot - width = 80 - - [init_catalog] - domain = mydomain - input_file = myproject/locale/mydomain.pot - output_dir = myproject/locale - - [update_catalog] - domain = mydomain - input_file = myproject/locale/mydomain.pot - output_dir = myproject/locale - previous = true .. index:: pair: initializing; message catalog @@ -432,15 +343,17 @@ Once you've extracted messages into a ``.pot`` file (see in the ``.pot`` file, you need to generate at least one ``.po`` file. A ``.po`` file represents translations of a particular set of messages to a particular locale. Initialize a ``.po`` file for a specific -locale from a pre-generated ``.pot`` template by using the ``setup.py -init_catalog`` command: +locale from a pre-generated ``.pot`` template by using the ``msginit`` +command: .. code-block:: text $ cd /place/where/myapplication/setup.py/lives - $ $VENV/bin/python setup.py init_catalog -l es + $ cd myapplication/locale + $ mkdir -p es/LC_MESSAGES + $ msginit -l es es/LC_MESSAGES/myapplication.po -By default, the message catalog ``.po`` file will end up in: +This will create a new the message catalog ``.po`` file will in: ``myapplication/locale/es/LC_MESSAGES/myapplication.po``. @@ -465,12 +378,13 @@ files based on changes to the ``.pot`` file, so that the new and changed messages can also be translated or re-translated. First, regenerate the ``.pot`` file as per :ref:`extracting_messages`. -Then use the ``setup.py update_catalog`` command. +Then use the ``msgmerge`` command. .. code-block:: text $ cd /place/where/myapplication/setup.py/lives - $ $VENV/bin/python setup.py update_catalog + $ cd myapplication/locale + $ msgmerge --update es/LC_MESSAGES/myapplication.po myapplication.pot .. index:: pair: compiling; message catalog @@ -481,12 +395,12 @@ Compiling a Message Catalog File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Finally, to prepare an application for performing actual runtime -translations, compile ``.po`` files to ``.mo`` files: +translations, compile ``.po`` files to ``.mo`` files use the ``msgfmt``: .. code-block:: text $ cd /place/where/myapplication/setup.py/lives - $ $VENV/bin/python setup.py compile_catalog + $ msgfmt myapplication/locale/*/LC_MESSAGES/*.po This will create a ``.mo`` file for each ``.po`` file in your application. As long as the :term:`translation directory` in which diff --git a/pyramid/scaffolds/alchemy/setup.cfg_tmpl b/pyramid/scaffolds/alchemy/setup.cfg_tmpl index 5bec29823..8bf5d22c1 100644 --- a/pyramid/scaffolds/alchemy/setup.cfg_tmpl +++ b/pyramid/scaffolds/alchemy/setup.cfg_tmpl @@ -4,24 +4,3 @@ nocapture=1 cover-package={{package}} with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = {{package}}/locale -domain = {{project}} -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = {{package}}/locale/{{project}}.pot -width = 80 - -[init_catalog] -domain = {{project}} -input_file = {{package}}/locale/{{project}}.pot -output_dir = {{package}}/locale - -[update_catalog] -domain = {{project}} -input_file = {{package}}/locale/{{project}}.pot -output_dir = {{package}}/locale -previous = true diff --git a/pyramid/scaffolds/starter/setup.cfg_tmpl b/pyramid/scaffolds/starter/setup.cfg_tmpl index 04c738049..1bc9ec437 100644 --- a/pyramid/scaffolds/starter/setup.cfg_tmpl +++ b/pyramid/scaffolds/starter/setup.cfg_tmpl @@ -4,24 +4,3 @@ nocapture = 1 cover-package = {{package}} with-coverage = 1 cover-erase = 1 - -[compile_catalog] -directory = {{package}}/locale -domain = {{project}} -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = {{package}}/locale/{{project}}.pot -width = 80 - -[init_catalog] -domain = {{project}} -input_file = {{package}}/locale/{{project}}.pot -output_dir = {{package}}/locale - -[update_catalog] -domain = {{project}} -input_file = {{package}}/locale/{{project}}.pot -output_dir = {{package}}/locale -previous = true diff --git a/pyramid/scaffolds/zodb/setup.cfg_tmpl b/pyramid/scaffolds/zodb/setup.cfg_tmpl index 5bec29823..8bf5d22c1 100644 --- a/pyramid/scaffolds/zodb/setup.cfg_tmpl +++ b/pyramid/scaffolds/zodb/setup.cfg_tmpl @@ -4,24 +4,3 @@ nocapture=1 cover-package={{package}} with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = {{package}}/locale -domain = {{project}} -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = {{package}}/locale/{{project}}.pot -width = 80 - -[init_catalog] -domain = {{project}} -input_file = {{package}}/locale/{{project}}.pot -output_dir = {{package}}/locale - -[update_catalog] -domain = {{project}} -input_file = {{package}}/locale/{{project}}.pot -output_dir = {{package}}/locale -previous = true -- cgit v1.2.3 From 98a99d726f8892376f69fba0fa6b99752972f1c8 Mon Sep 17 00:00:00 2001 From: Wichert Akkerman Date: Tue, 8 Apr 2014 20:11:37 +0200 Subject: Update Lingua glossary item. --- docs/glossary.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 0e340491b..2cc461a77 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -801,8 +801,9 @@ Glossary application. Lingua - A package by Wichert Akkerman which provides :term:`Babel` message - extractors for Python source files and Chameleon ZPT template files. + A package by Wichert Akkerman which provides the ``pot-create`` + command to extract translateable messages from Python sources + and Chameleon ZPT template files. Message Identifier A string used as a translation lookup key during localization. -- cgit v1.2.3 From cfaec8b63b271e141789244ad276d3045e00a2a8 Mon Sep 17 00:00:00 2001 From: Wichert Akkerman Date: Tue, 8 Apr 2014 21:01:43 +0200 Subject: Keep using easy_install. --- docs/narr/i18n.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 31b63664b..122f33130 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -288,7 +288,7 @@ like so: .. code-block:: text $ cd /my/virtualenv - $ $VENV/bin/pip install lingua + $ $VENV/bin/easy_install lingua Installation on Windows +++++++++++++++++++++++ @@ -306,7 +306,7 @@ lives in ``C:\my\virtualenv``, you can install Lingua like so: .. code-block:: text - C> %VENV%\Scripts\pip install lingua + C> %VENV%\Scripts\easy_install lingua .. index:: -- cgit v1.2.3 From d3a70e5e656eea3f527b56e9f03b6f754731dc4a Mon Sep 17 00:00:00 2001 From: Wichert Akkerman Date: Tue, 8 Apr 2014 21:04:57 +0200 Subject: Fix $VENT typo --- 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 122f33130..50e5a6817 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -325,7 +325,7 @@ application. You run a ``pot-create`` command to extract the messages: $ cd /place/where/myapplication/setup.py/lives $ mkdir -p myapplication/locale - $ $VENT/bin/pot-create src > myapplication/locale/myapplication.pot + $ $VENV/bin/pot-create src > myapplication/locale/myapplication.pot The message catalog ``.pot`` template will end up in: -- cgit v1.2.3 From c1cbf085fbf4e0280481acfbf1e9b6b0f8692cbd Mon Sep 17 00:00:00 2001 From: Wichert Akkerman Date: Tue, 8 Apr 2014 21:07:24 +0200 Subject: Remove lingua references from setup.cfg description. --- docs/narr/project.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 62b91de0e..3631a9782 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -760,8 +760,8 @@ Our generated ``setup.cfg`` looks like this: :language: guess :linenos: -The values in the default setup file allow various commonly-used -internationalization commands and testing commands to work more smoothly. +The values in the default setup file make the testing commands to work more +smoothly. .. index:: single: package -- cgit v1.2.3 From 0a4433df3711755170396090d8daa69bfaef76f9 Mon Sep 17 00:00:00 2001 From: Wichert Akkerman Date: Tue, 8 Apr 2014 21:09:34 +0200 Subject: Always mention the msg* commands come from Gettext --- docs/narr/i18n.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 50e5a6817..1de2c8941 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -250,8 +250,9 @@ GNU gettext uses three types of files in the translation framework, The tools for working with :term:`gettext` translation files related to a :app:`Pyramid` application are :term:`Lingua` and :term:`Gettext`. Lingua can scrape i18n references out of Python and Chameleon files and create -the ``.pot`` file. Gettext includes tools to update a ``.po`` file from -an updated ``.pot`` file and to compile ``.po`` files to ``.mo`` files. +the ``.pot`` file. Gettext includes ``msgmerge`` tool to update a ``.po`` file +from an updated ``.pot`` file and ``msgfmt`` to compile ``.po`` files to +``.mo`` files. .. index:: single: Gettext @@ -344,7 +345,7 @@ in the ``.pot`` file, you need to generate at least one ``.po`` file. A ``.po`` file represents translations of a particular set of messages to a particular locale. Initialize a ``.po`` file for a specific locale from a pre-generated ``.pot`` template by using the ``msginit`` -command: +command from Gettext: .. code-block:: text @@ -378,7 +379,7 @@ files based on changes to the ``.pot`` file, so that the new and changed messages can also be translated or re-translated. First, regenerate the ``.pot`` file as per :ref:`extracting_messages`. -Then use the ``msgmerge`` command. +Then use the ``msgmerge`` command from Gettext. .. code-block:: text @@ -395,7 +396,8 @@ Compiling a Message Catalog File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Finally, to prepare an application for performing actual runtime -translations, compile ``.po`` files to ``.mo`` files use the ``msgfmt``: +translations, compile ``.po`` files to ``.mo`` files use the ``msgfmt`` +command from Gettext: .. code-block:: text -- cgit v1.2.3 From 832c2e8967fa1904fb1a0d3e5d707a11c32aa543 Mon Sep 17 00:00:00 2001 From: Wichert Akkerman Date: Tue, 8 Apr 2014 21:49:44 +0200 Subject: Remove Babel from all setup.cfg files --- docs/narr/MyProject/setup.cfg | 21 --------------------- docs/quick_tour/awesome/setup.cfg | 22 ---------------------- docs/quick_tour/package/setup.cfg | 22 ---------------------- docs/quick_tour/sqla_demo/setup.cfg | 21 --------------------- docs/quick_tutorial/scaffolds/setup.cfg | 21 --------------------- docs/tutorials/wiki/src/authorization/setup.cfg | 22 ---------------------- docs/tutorials/wiki/src/basiclayout/setup.cfg | 21 --------------------- docs/tutorials/wiki/src/models/setup.cfg | 22 ---------------------- docs/tutorials/wiki/src/tests/setup.cfg | 22 ---------------------- docs/tutorials/wiki/src/views/setup.cfg | 22 ---------------------- docs/tutorials/wiki2/src/authorization/setup.cfg | 21 --------------------- docs/tutorials/wiki2/src/basiclayout/setup.cfg | 21 --------------------- docs/tutorials/wiki2/src/models/setup.cfg | 21 --------------------- docs/tutorials/wiki2/src/tests/setup.cfg | 21 --------------------- docs/tutorials/wiki2/src/views/setup.cfg | 21 --------------------- .../test_scaffolds/fixture_scaffold/setup.cfg_tmpl | 21 --------------------- 16 files changed, 342 deletions(-) diff --git a/docs/narr/MyProject/setup.cfg b/docs/narr/MyProject/setup.cfg index 332e80a60..229a686f8 100644 --- a/docs/narr/MyProject/setup.cfg +++ b/docs/narr/MyProject/setup.cfg @@ -4,24 +4,3 @@ nocapture = 1 cover-package = myproject with-coverage = 1 cover-erase = 1 - -[compile_catalog] -directory = myproject/locale -domain = MyProject -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = myproject/locale/MyProject.pot -width = 80 - -[init_catalog] -domain = MyProject -input_file = myproject/locale/MyProject.pot -output_dir = myproject/locale - -[update_catalog] -domain = MyProject -input_file = myproject/locale/MyProject.pot -output_dir = myproject/locale -previous = true diff --git a/docs/quick_tour/awesome/setup.cfg b/docs/quick_tour/awesome/setup.cfg index b1cd90d2c..14f322085 100644 --- a/docs/quick_tour/awesome/setup.cfg +++ b/docs/quick_tour/awesome/setup.cfg @@ -4,25 +4,3 @@ nocapture = 1 cover-package = awesome with-coverage = 1 cover-erase = 1 - -[compile_catalog] -directory = awesome/locale -domain = awesome -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = awesome/locale/awesome.pot -width = 80 -mapping_file = message-extraction.ini - -[init_catalog] -domain = awesome -input_file = awesome/locale/awesome.pot -output_dir = awesome/locale - -[update_catalog] -domain = awesome -input_file = awesome/locale/awesome.pot -output_dir = awesome/locale -previous = true diff --git a/docs/quick_tour/package/setup.cfg b/docs/quick_tour/package/setup.cfg index 186e796fc..ac8bb6e21 100644 --- a/docs/quick_tour/package/setup.cfg +++ b/docs/quick_tour/package/setup.cfg @@ -4,25 +4,3 @@ nocapture = 1 cover-package = hello_world with-coverage = 1 cover-erase = 1 - -[compile_catalog] -directory = hello_world/locale -domain = hello_world -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = hello_world/locale/hello_world.pot -width = 80 -mapping_file = message-extraction.ini - -[init_catalog] -domain = hello_world -input_file = hello_world/locale/hello_world.pot -output_dir = hello_world/locale - -[update_catalog] -domain = hello_world -input_file = hello_world/locale/hello_world.pot -output_dir = hello_world/locale -previous = true diff --git a/docs/quick_tour/sqla_demo/setup.cfg b/docs/quick_tour/sqla_demo/setup.cfg index 9f91cd122..27dbd2377 100644 --- a/docs/quick_tour/sqla_demo/setup.cfg +++ b/docs/quick_tour/sqla_demo/setup.cfg @@ -4,24 +4,3 @@ nocapture=1 cover-package=sqla_demo with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = sqla_demo/locale -domain = sqla_demo -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = sqla_demo/locale/sqla_demo.pot -width = 80 - -[init_catalog] -domain = sqla_demo -input_file = sqla_demo/locale/sqla_demo.pot -output_dir = sqla_demo/locale - -[update_catalog] -domain = sqla_demo -input_file = sqla_demo/locale/sqla_demo.pot -output_dir = sqla_demo/locale -previous = true diff --git a/docs/quick_tutorial/scaffolds/setup.cfg b/docs/quick_tutorial/scaffolds/setup.cfg index c980261e3..16e18719c 100644 --- a/docs/quick_tutorial/scaffolds/setup.cfg +++ b/docs/quick_tutorial/scaffolds/setup.cfg @@ -4,24 +4,3 @@ nocapture = 1 cover-package = scaffolds with-coverage = 1 cover-erase = 1 - -[compile_catalog] -directory = scaffolds/locale -domain = scaffolds -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = scaffolds/locale/scaffolds.pot -width = 80 - -[init_catalog] -domain = scaffolds -input_file = scaffolds/locale/scaffolds.pot -output_dir = scaffolds/locale - -[update_catalog] -domain = scaffolds -input_file = scaffolds/locale/scaffolds.pot -output_dir = scaffolds/locale -previous = true diff --git a/docs/tutorials/wiki/src/authorization/setup.cfg b/docs/tutorials/wiki/src/authorization/setup.cfg index 3d7ea6e23..807ea6b0e 100644 --- a/docs/tutorials/wiki/src/authorization/setup.cfg +++ b/docs/tutorials/wiki/src/authorization/setup.cfg @@ -4,25 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true - diff --git a/docs/tutorials/wiki/src/basiclayout/setup.cfg b/docs/tutorials/wiki/src/basiclayout/setup.cfg index 23b2ad983..807ea6b0e 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.cfg +++ b/docs/tutorials/wiki/src/basiclayout/setup.cfg @@ -4,24 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true diff --git a/docs/tutorials/wiki/src/models/setup.cfg b/docs/tutorials/wiki/src/models/setup.cfg index 3d7ea6e23..807ea6b0e 100644 --- a/docs/tutorials/wiki/src/models/setup.cfg +++ b/docs/tutorials/wiki/src/models/setup.cfg @@ -4,25 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true - diff --git a/docs/tutorials/wiki/src/tests/setup.cfg b/docs/tutorials/wiki/src/tests/setup.cfg index 3d7ea6e23..807ea6b0e 100644 --- a/docs/tutorials/wiki/src/tests/setup.cfg +++ b/docs/tutorials/wiki/src/tests/setup.cfg @@ -4,25 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true - diff --git a/docs/tutorials/wiki/src/views/setup.cfg b/docs/tutorials/wiki/src/views/setup.cfg index 3d7ea6e23..807ea6b0e 100644 --- a/docs/tutorials/wiki/src/views/setup.cfg +++ b/docs/tutorials/wiki/src/views/setup.cfg @@ -4,25 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true - diff --git a/docs/tutorials/wiki2/src/authorization/setup.cfg b/docs/tutorials/wiki2/src/authorization/setup.cfg index 23b2ad983..807ea6b0e 100644 --- a/docs/tutorials/wiki2/src/authorization/setup.cfg +++ b/docs/tutorials/wiki2/src/authorization/setup.cfg @@ -4,24 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.cfg b/docs/tutorials/wiki2/src/basiclayout/setup.cfg index 23b2ad983..807ea6b0e 100644 --- a/docs/tutorials/wiki2/src/basiclayout/setup.cfg +++ b/docs/tutorials/wiki2/src/basiclayout/setup.cfg @@ -4,24 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true diff --git a/docs/tutorials/wiki2/src/models/setup.cfg b/docs/tutorials/wiki2/src/models/setup.cfg index 23b2ad983..807ea6b0e 100644 --- a/docs/tutorials/wiki2/src/models/setup.cfg +++ b/docs/tutorials/wiki2/src/models/setup.cfg @@ -4,24 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true diff --git a/docs/tutorials/wiki2/src/tests/setup.cfg b/docs/tutorials/wiki2/src/tests/setup.cfg index 23b2ad983..807ea6b0e 100644 --- a/docs/tutorials/wiki2/src/tests/setup.cfg +++ b/docs/tutorials/wiki2/src/tests/setup.cfg @@ -4,24 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true diff --git a/docs/tutorials/wiki2/src/views/setup.cfg b/docs/tutorials/wiki2/src/views/setup.cfg index 23b2ad983..807ea6b0e 100644 --- a/docs/tutorials/wiki2/src/views/setup.cfg +++ b/docs/tutorials/wiki2/src/views/setup.cfg @@ -4,24 +4,3 @@ nocapture=1 cover-package=tutorial with-coverage=1 cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true diff --git a/pyramid/tests/test_scaffolds/fixture_scaffold/setup.cfg_tmpl b/pyramid/tests/test_scaffolds/fixture_scaffold/setup.cfg_tmpl index 04c738049..1bc9ec437 100644 --- a/pyramid/tests/test_scaffolds/fixture_scaffold/setup.cfg_tmpl +++ b/pyramid/tests/test_scaffolds/fixture_scaffold/setup.cfg_tmpl @@ -4,24 +4,3 @@ nocapture = 1 cover-package = {{package}} with-coverage = 1 cover-erase = 1 - -[compile_catalog] -directory = {{package}}/locale -domain = {{project}} -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = {{package}}/locale/{{project}}.pot -width = 80 - -[init_catalog] -domain = {{project}} -input_file = {{package}}/locale/{{project}}.pot -output_dir = {{package}}/locale - -[update_catalog] -domain = {{project}} -input_file = {{package}}/locale/{{project}}.pot -output_dir = {{package}}/locale -previous = true -- cgit v1.2.3 From 90600c7484d7e3ec8620e9fbe42f3449ffa6feb2 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 8 Apr 2014 15:54:25 -0400 Subject: add changenote --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index fdf2ac644..32881ef8a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,6 +13,9 @@ Unreleased ``reissue_time=None`` would cause an exception when modifying the session. See https://github.com/Pylons/pyramid/issues/1247 +- Updated docs and scaffolds to keep in step with new 2.0 release of + ``Lingua``. + 1.5b1 (2014-02-08) ================== -- cgit v1.2.3 From eab0eb5068754da33123d5a7bc3faf025a3fc14e Mon Sep 17 00:00:00 2001 From: Wichert Akkerman Date: Tue, 8 Apr 2014 22:04:47 +0200 Subject: Drop setup.cfg from scaffolds Since setup.cfg is no longer needed for Babel, and no scaffold or documentation references nose there is no need to keep them. --- docs/narr/MyProject/setup.cfg | 6 ------ docs/narr/project.rst | 22 ---------------------- docs/quick_tour/awesome/setup.cfg | 6 ------ docs/quick_tour/package/setup.cfg | 6 ------ docs/quick_tour/sqla_demo/setup.cfg | 6 ------ docs/quick_tutorial/scaffolds/setup.cfg | 6 ------ docs/tutorials/wiki/src/authorization/setup.cfg | 6 ------ docs/tutorials/wiki/src/basiclayout/setup.cfg | 6 ------ docs/tutorials/wiki/src/models/setup.cfg | 6 ------ docs/tutorials/wiki/src/tests/setup.cfg | 6 ------ docs/tutorials/wiki/src/views/setup.cfg | 6 ------ docs/tutorials/wiki2/src/authorization/setup.cfg | 6 ------ docs/tutorials/wiki2/src/basiclayout/setup.cfg | 6 ------ docs/tutorials/wiki2/src/models/setup.cfg | 6 ------ docs/tutorials/wiki2/src/tests/setup.cfg | 6 ------ docs/tutorials/wiki2/src/views/setup.cfg | 6 ------ pyramid/scaffolds/alchemy/setup.cfg_tmpl | 6 ------ pyramid/scaffolds/starter/setup.cfg_tmpl | 6 ------ pyramid/scaffolds/zodb/setup.cfg_tmpl | 6 ------ .../test_scaffolds/fixture_scaffold/setup.cfg_tmpl | 6 ------ 20 files changed, 136 deletions(-) delete mode 100644 docs/narr/MyProject/setup.cfg delete mode 100644 docs/quick_tour/awesome/setup.cfg delete mode 100644 docs/quick_tour/package/setup.cfg delete mode 100644 docs/quick_tour/sqla_demo/setup.cfg delete mode 100644 docs/quick_tutorial/scaffolds/setup.cfg delete mode 100644 docs/tutorials/wiki/src/authorization/setup.cfg delete mode 100644 docs/tutorials/wiki/src/basiclayout/setup.cfg delete mode 100644 docs/tutorials/wiki/src/models/setup.cfg delete mode 100644 docs/tutorials/wiki/src/tests/setup.cfg delete mode 100644 docs/tutorials/wiki/src/views/setup.cfg delete mode 100644 docs/tutorials/wiki2/src/authorization/setup.cfg delete mode 100644 docs/tutorials/wiki2/src/basiclayout/setup.cfg delete mode 100644 docs/tutorials/wiki2/src/models/setup.cfg delete mode 100644 docs/tutorials/wiki2/src/tests/setup.cfg delete mode 100644 docs/tutorials/wiki2/src/views/setup.cfg delete mode 100644 pyramid/scaffolds/alchemy/setup.cfg_tmpl delete mode 100644 pyramid/scaffolds/starter/setup.cfg_tmpl delete mode 100644 pyramid/scaffolds/zodb/setup.cfg_tmpl delete mode 100644 pyramid/tests/test_scaffolds/fixture_scaffold/setup.cfg_tmpl diff --git a/docs/narr/MyProject/setup.cfg b/docs/narr/MyProject/setup.cfg deleted file mode 100644 index 229a686f8..000000000 --- a/docs/narr/MyProject/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match = ^test -nocapture = 1 -cover-package = myproject -with-coverage = 1 -cover-erase = 1 diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 3631a9782..39e55706f 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -492,7 +492,6 @@ structure: │   ├── tests.py │   └── views.py ├── production.ini - ├── setup.cfg └── setup.py The ``MyProject`` :term:`Project` @@ -515,9 +514,6 @@ describe, run, and test your application. #. ``production.ini`` is a :term:`PasteDeploy` configuration file that can be used to execute your application in a production configuration. -#. ``setup.cfg`` is a :term:`setuptools` configuration file used by - ``setup.py``. - #. ``MANIFEST.in`` is a :term:`distutils` "manifest" file, naming which files should be included in a source distribution of the package when ``python setup.py sdist`` is run. @@ -745,24 +741,6 @@ This will create a tarball of your application in a ``dist`` subdirectory named ``MyProject-0.1.tar.gz``. You can send this tarball to other people who want to install and use your application. -.. index:: - single: setup.cfg - -``setup.cfg`` -~~~~~~~~~~~~~ - -The ``setup.cfg`` file is a :term:`setuptools` configuration file. It -contains various settings related to testing and internationalization: - -Our generated ``setup.cfg`` looks like this: - -.. literalinclude:: MyProject/setup.cfg - :language: guess - :linenos: - -The values in the default setup file make the testing commands to work more -smoothly. - .. index:: single: package diff --git a/docs/quick_tour/awesome/setup.cfg b/docs/quick_tour/awesome/setup.cfg deleted file mode 100644 index 14f322085..000000000 --- a/docs/quick_tour/awesome/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match = ^test -nocapture = 1 -cover-package = awesome -with-coverage = 1 -cover-erase = 1 diff --git a/docs/quick_tour/package/setup.cfg b/docs/quick_tour/package/setup.cfg deleted file mode 100644 index ac8bb6e21..000000000 --- a/docs/quick_tour/package/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match = ^test -nocapture = 1 -cover-package = hello_world -with-coverage = 1 -cover-erase = 1 diff --git a/docs/quick_tour/sqla_demo/setup.cfg b/docs/quick_tour/sqla_demo/setup.cfg deleted file mode 100644 index 27dbd2377..000000000 --- a/docs/quick_tour/sqla_demo/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=sqla_demo -with-coverage=1 -cover-erase=1 diff --git a/docs/quick_tutorial/scaffolds/setup.cfg b/docs/quick_tutorial/scaffolds/setup.cfg deleted file mode 100644 index 16e18719c..000000000 --- a/docs/quick_tutorial/scaffolds/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match = ^test -nocapture = 1 -cover-package = scaffolds -with-coverage = 1 -cover-erase = 1 diff --git a/docs/tutorials/wiki/src/authorization/setup.cfg b/docs/tutorials/wiki/src/authorization/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki/src/authorization/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/docs/tutorials/wiki/src/basiclayout/setup.cfg b/docs/tutorials/wiki/src/basiclayout/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki/src/basiclayout/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/docs/tutorials/wiki/src/models/setup.cfg b/docs/tutorials/wiki/src/models/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki/src/models/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/docs/tutorials/wiki/src/tests/setup.cfg b/docs/tutorials/wiki/src/tests/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki/src/tests/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/docs/tutorials/wiki/src/views/setup.cfg b/docs/tutorials/wiki/src/views/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki/src/views/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/docs/tutorials/wiki2/src/authorization/setup.cfg b/docs/tutorials/wiki2/src/authorization/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki2/src/authorization/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.cfg b/docs/tutorials/wiki2/src/basiclayout/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki2/src/basiclayout/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/docs/tutorials/wiki2/src/models/setup.cfg b/docs/tutorials/wiki2/src/models/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki2/src/models/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/docs/tutorials/wiki2/src/tests/setup.cfg b/docs/tutorials/wiki2/src/tests/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki2/src/tests/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/docs/tutorials/wiki2/src/views/setup.cfg b/docs/tutorials/wiki2/src/views/setup.cfg deleted file mode 100644 index 807ea6b0e..000000000 --- a/docs/tutorials/wiki2/src/views/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 diff --git a/pyramid/scaffolds/alchemy/setup.cfg_tmpl b/pyramid/scaffolds/alchemy/setup.cfg_tmpl deleted file mode 100644 index 8bf5d22c1..000000000 --- a/pyramid/scaffolds/alchemy/setup.cfg_tmpl +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package={{package}} -with-coverage=1 -cover-erase=1 diff --git a/pyramid/scaffolds/starter/setup.cfg_tmpl b/pyramid/scaffolds/starter/setup.cfg_tmpl deleted file mode 100644 index 1bc9ec437..000000000 --- a/pyramid/scaffolds/starter/setup.cfg_tmpl +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match = ^test -nocapture = 1 -cover-package = {{package}} -with-coverage = 1 -cover-erase = 1 diff --git a/pyramid/scaffolds/zodb/setup.cfg_tmpl b/pyramid/scaffolds/zodb/setup.cfg_tmpl deleted file mode 100644 index 8bf5d22c1..000000000 --- a/pyramid/scaffolds/zodb/setup.cfg_tmpl +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package={{package}} -with-coverage=1 -cover-erase=1 diff --git a/pyramid/tests/test_scaffolds/fixture_scaffold/setup.cfg_tmpl b/pyramid/tests/test_scaffolds/fixture_scaffold/setup.cfg_tmpl deleted file mode 100644 index 1bc9ec437..000000000 --- a/pyramid/tests/test_scaffolds/fixture_scaffold/setup.cfg_tmpl +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match = ^test -nocapture = 1 -cover-package = {{package}} -with-coverage = 1 -cover-erase = 1 -- cgit v1.2.3 From aef68bf93127265134ebf63dca0403cf9c1955a8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 8 Apr 2014 17:14:42 -0400 Subject: add changenote --- CHANGES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 32881ef8a..0e452d011 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -14,7 +14,8 @@ Unreleased See https://github.com/Pylons/pyramid/issues/1247 - Updated docs and scaffolds to keep in step with new 2.0 release of - ``Lingua``. + ``Lingua``. This included removing all ``setup.cfg`` files from scaffolds + and documentation environments. 1.5b1 (2014-02-08) ================== -- cgit v1.2.3 From 610b85ab54452568728fc6390e6cd18670036ba2 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 8 Apr 2014 17:20:32 -0400 Subject: fix typo and update whatsnew --- CHANGES.txt | 2 +- docs/whatsnew-1.5.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 0e452d011..2e3996b3e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,7 @@ Unreleased ========== -- Avoid crash in ``pserve --reload`` under Py3k, when iterating over posiibly +- Avoid crash in ``pserve --reload`` under Py3k, when iterating over possibly mutated ``sys.modules``. - ``UnencryptedCookieSessionFactoryConfig`` failed if the secret contained diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst index 2f73af661..65e8393f8 100644 --- a/docs/whatsnew-1.5.rst +++ b/docs/whatsnew-1.5.rst @@ -511,6 +511,9 @@ Scaffolding Enhancements - All scaffolds have a new HTML + CSS theme. +- Updated docs and scaffolds to keep in step with new 2.0 release of + ``Lingua``. This included removing all ``setup.cfg`` files from scaffolds + and documentation environments. Dependency Changes ------------------ -- cgit v1.2.3 From b66661a72ea2f0fe4bd6aacb83c6c5487401141b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 8 Apr 2014 17:44:36 -0400 Subject: i have no idea why we do this in here, but let's make it at least correct --- docs/conf.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a447c9968..eba776628 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -138,17 +138,21 @@ if book: # Add and use Pylons theme if 'sphinx-build' in ' '.join(sys.argv): # protect against dumb importers from subprocess import call, Popen, PIPE - - p = Popen('which git', shell=True, stdout=PIPE) cwd = os.getcwd() - _themes = os.path.join(cwd, '_themes') + p = Popen('which git', shell=True, stdout=PIPE) + here = os.path.abspath(os.path.dirname(__file__)) + parent = os.path.abspath(os.path.dirname(here)) + _themes = os.path.join(here, '_themes') git = p.stdout.read().strip() - if not os.listdir(_themes): - call([git, 'submodule', '--init']) - else: - call([git, 'submodule', 'update']) - - sys.path.append(os.path.abspath('_themes')) + try: + os.chdir(parent) + if not os.listdir(_themes): + call([git, 'submodule', '--init']) + else: + call([git, 'submodule', 'update']) + sys.path.append(_themes) + finally: + os.chdir(cwd) html_theme_path = ['_themes'] html_theme = 'pyramid' -- cgit v1.2.3 From c3ef4170b195f08f9563256a3cbdd614d786acef Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 8 Apr 2014 17:51:30 -0400 Subject: make pdf build again --- docs/narr/project.rst | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 39e55706f..0ada1a379 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -476,23 +476,23 @@ structure: .. code-block:: text MyProject/ - ├── CHANGES.txt - ├── MANIFEST.in - ├── README.txt - ├── development.ini - ├── myproject - │   ├── __init__.py - │   ├── static - │   │   ├── pyramid-16x16.png - │   │   ├── pyramid.png - │   │   ├── theme.css - │   │   └── theme.min.css - │   ├── templates - │   │   └── mytemplate.pt - │   ├── tests.py - │   └── views.py - ├── production.ini - └── setup.py + |-- CHANGES.txt + |-- development.ini + |-- MANIFEST.in + |-- myproject + | |-- __init__.py + | |-- static + | | |-- pyramid-16x16.png + | | |-- pyramid.png + | | |-- theme.css + | | `-- theme.min.css + | |-- templates + | | `-- mytemplate.pt + | |-- tests.py + | `-- views.py + |-- production.ini + |-- README.txt + `-- setup.py The ``MyProject`` :term:`Project` --------------------------------- -- cgit v1.2.3 From 2ecc60e1f55e42a99d8d5339e0724babb4c82a2e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 8 Apr 2014 18:30:29 -0400 Subject: no idea how this ever worked without pyramid being installed into the python being used to run the tests first --- pyramid/scaffolds/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/scaffolds/tests.py b/pyramid/scaffolds/tests.py index d913d022a..dfbf9b6cf 100644 --- a/pyramid/scaffolds/tests.py +++ b/pyramid/scaffolds/tests.py @@ -1,6 +1,5 @@ import sys import os -import pkg_resources import shutil import subprocess import tempfile @@ -26,7 +25,8 @@ class TemplateTest(object): self.old_cwd = os.getcwd() self.directory = tempfile.mkdtemp() self.make_venv(self.directory) - os.chdir(pkg_resources.get_distribution('pyramid').location) + here = os.path.abspath(os.path.dirname(__file__)) + os.chdir(os.path.dirname(os.path.dirname(here))) subprocess.check_call( [os.path.join(self.directory, 'bin', 'python'), 'setup.py', 'develop']) -- cgit v1.2.3 From 0cb759758c97feba48c0bbba149a774f85f2606a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 8 Apr 2014 19:09:47 -0400 Subject: prep for 1.5 release --- CHANGES.txt | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2e3996b3e..900eb116d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -Unreleased -========== +1.5 (2014-04-08) +================ - Avoid crash in ``pserve --reload`` under Py3k, when iterating over possibly mutated ``sys.modules``. diff --git a/setup.py b/setup.py index bb443330d..2214611b7 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ testing_extras = tests_require + [ ] setup(name='pyramid', - version='1.5b1', + version='1.5', description='The Pyramid Web Framework, a Pylons project', long_description=README + '\n\n' + CHANGES, classifiers=[ -- cgit v1.2.3 From c61755f77b4f945057522a9f4d4360ed6e5504d7 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 8 Apr 2014 19:16:23 -0400 Subject: vb master to 1.6dev --- CHANGES.txt | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 900eb116d..264497f5b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,8 @@ +Next release +============ + +- ... + 1.5 (2014-04-08) ================ diff --git a/setup.py b/setup.py index 2214611b7..0ace211d7 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ testing_extras = tests_require + [ ] setup(name='pyramid', - version='1.5', + version='1.6dev', description='The Pyramid Web Framework, a Pylons project', long_description=README + '\n\n' + CHANGES, classifiers=[ -- cgit v1.2.3 From 0117573edbc5dff565868187f8841859b3e36a51 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 8 Apr 2014 20:07:23 -0400 Subject: add bdist_wheel unversality --- setup.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.cfg b/setup.cfg index d7622683f..a877ffb7f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,3 +11,7 @@ cover-erase=1 [aliases] dev = develop easy_install pyramid[testing] docs = develop easy_install pyramid[docs] + +[bdist_wheel] +universal = 1 + -- cgit v1.2.3 From 25c886ebad924786bef43cd27ce7a57d95ce3482 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Wed, 9 Apr 2014 14:46:30 -0400 Subject: - clarify Windows setuptools instructions - correct typo on location --- docs/quick_tutorial/requirements.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index 72bb4a4f8..b79be4b3a 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -187,9 +187,15 @@ pipe it to your environment's version of Python. $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | $VENV/bin/python # Windows - # Use your browser to download: - # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.p - # ...into c:\projects\quick_tutorial\ez_setup.py + # + # Use your web browser to download this file: + # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setupy + # + # ...and save it to: + # c:\projects\quick_tutorial\ez_setup.py + # + # Then run the following command: + c:\> %VENV%\Scripts\python ez_setup.py If ``wget`` complains with a certificate error, then run this command instead: -- cgit v1.2.3 From ca1273b6897a42d72adca8276e78458962c1b9bb Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Wed, 9 Apr 2014 16:29:55 -0400 Subject: - add name of views.py --- docs/quick_tutorial/json.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/json.rst b/docs/quick_tutorial/json.rst index ece8a61c0..aa789d833 100644 --- a/docs/quick_tutorial/json.rst +++ b/docs/quick_tutorial/json.rst @@ -40,7 +40,7 @@ Steps :linenos: #. Rather than implement a new view, we will "stack" another decorator - on the ``hello`` view: + on the ``hello`` view in ``views.py``: .. literalinclude:: json/tutorial/views.py :linenos: -- cgit v1.2.3 From c462c0770b801be06f26acdbdcc00b9b92260286 Mon Sep 17 00:00:00 2001 From: thapar Date: Wed, 9 Apr 2014 16:38:47 -0400 Subject: Typo correction "setupy"-->"setup.py" --- docs/quick_tutorial/requirements.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index b79be4b3a..b5778ea42 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -189,7 +189,7 @@ pipe it to your environment's version of Python. # Windows # # Use your web browser to download this file: - # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setupy + # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py # # ...and save it to: # c:\projects\quick_tutorial\ez_setup.py -- cgit v1.2.3 From 77005d96fa57a83ac91c2547892fbb3e3c34e553 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 14 Apr 2014 01:56:02 -0400 Subject: mature dev status --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 0ace211d7..54ced84e3 100644 --- a/setup.py +++ b/setup.py @@ -72,6 +72,7 @@ setup(name='pyramid', description='The Pyramid Web Framework, a Pylons project', long_description=README + '\n\n' + CHANGES, classifiers=[ + "Development Status :: 6 - Mature", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 2.6", -- cgit v1.2.3 From f0398e9e32d8fee1b0f46eef0ffcc814d7e6efb0 Mon Sep 17 00:00:00 2001 From: Nina Zakharenko Date: Mon, 14 Apr 2014 14:46:11 -0400 Subject: Update python installation instructions for mac osx. --- docs/narr/install.rst | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index e419a8b20..89791115f 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -32,20 +32,32 @@ dependency will fall back to using pure Python instead. For Mac OS X Users ~~~~~~~~~~~~~~~~~~ -From `Python.org `_: +Python comes pre-installed on Mac OS X, but due to Apple's release cycle, +it is often out of date. Unless you have a need for a specific earlier version, it is recommended to +install the latest 2.x or 3.x version of Python. - Python comes pre-installed on Mac OS X, but due to Apple's release cycle, - it's often one or even two years old. The overwhelming recommendation of - the "MacPython" community is to upgrade your Python by downloading and - installing a newer version from `the Python standard release page - `_. +You can install the latest version of Python for Mac using the `homebrew `_ package manager. -It is recommended to download one of the *installer* versions, unless you -prefer to install your Python through a packgage manager (e.g., macports or -homebrew) or to build your Python from source. +To install homebrew: -Unless you have a need for a specific earlier version, it is recommended to -install the latest 2.x or 3.x version of Python. +.. code-block:: text + + $ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)" + +Then update the homebrew package index. + +.. code-block:: text + + $ brew update & brew doctor + +If the message says you're ready to brew, install the latest python2 or python3 using: + +.. code-block:: text + + $ brew install python + $ brew install python3 + +Alternatively, you can install it via the binaries on the `python.org `_ site. If you use an installer for your Python, then you can skip to the section :ref:`installing_unix`. -- cgit v1.2.3 From 836894d87eb2b1127b69f7eed70b770460f913ca Mon Sep 17 00:00:00 2001 From: Nina Zakharenko Date: Mon, 14 Apr 2014 16:07:23 -0400 Subject: Update the documentation for Python installation on OSX. Binaries are primary method. --- docs/narr/install.rst | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 89791115f..c0f633686 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -33,32 +33,19 @@ For Mac OS X Users ~~~~~~~~~~~~~~~~~~ Python comes pre-installed on Mac OS X, but due to Apple's release cycle, -it is often out of date. Unless you have a need for a specific earlier version, it is recommended to -install the latest 2.x or 3.x version of Python. +it is often out of date. Unless you have a need for a specific earlier version, +it is recommended to install the latest 2.x or 3.x version of Python. -You can install the latest version of Python for Mac using the `homebrew `_ package manager. +You can install the latest verion of Python for OSX from the binaries on +`python.org `_. -To install homebrew: - -.. code-block:: text - - $ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)" - -Then update the homebrew package index. - -.. code-block:: text - - $ brew update & brew doctor - -If the message says you're ready to brew, install the latest python2 or python3 using: +Alternatively, you can use the `homebrew `_ package manager. .. code-block:: text $ brew install python $ brew install python3 -Alternatively, you can install it via the binaries on the `python.org `_ site. - If you use an installer for your Python, then you can skip to the section :ref:`installing_unix`. -- cgit v1.2.3 From 8af14089b511723912063bb1b8f1471378d4a0ba Mon Sep 17 00:00:00 2001 From: Nina Zakharenko Date: Mon, 14 Apr 2014 16:20:04 -0400 Subject: Change OSX to Mac OS X --- docs/narr/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index c0f633686..54984e6a2 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -36,7 +36,7 @@ Python comes pre-installed on Mac OS X, but due to Apple's release cycle, it is often out of date. Unless you have a need for a specific earlier version, it is recommended to install the latest 2.x or 3.x version of Python. -You can install the latest verion of Python for OSX from the binaries on +You can install the latest verion of Python for Mac OS X from the binaries on `python.org `_. Alternatively, you can use the `homebrew `_ package manager. -- cgit v1.2.3 From abab3574569d39eeea5459eaad102eb6c7b2d7ef Mon Sep 17 00:00:00 2001 From: Nina Zakharenko Date: Mon, 14 Apr 2014 16:44:32 -0400 Subject: adding comments for python versions --- docs/narr/install.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 54984e6a2..4ccf65c65 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -43,7 +43,10 @@ Alternatively, you can use the `homebrew `_ package manager. .. code-block:: text + # for python 2.7 $ brew install python + + # for python 3.4 $ brew install python3 If you use an installer for your Python, then you can skip to the section -- cgit v1.2.3 From a51a5627b0dda868f467588c464215d6f74a0958 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 14 Apr 2014 16:46:59 -0400 Subject: clarify python version support --- docs/narr/install.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index e419a8b20..a6ae7df60 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -15,8 +15,8 @@ You will need `Python `_ version 2.6 or better to run .. sidebar:: Python Versions As of this writing, :app:`Pyramid` has been tested under Python 2.6, Python - 2.7, Python 3.2, and Python 3.3. :app:`Pyramid` does not run under any - version of Python before 2.6. + 2.7, Python 3.2, Python 3.3, Python 3.4 and PyPy 2.2. :app:`Pyramid` does + not run under any version of Python before 2.6. :app:`Pyramid` is known to run on all popular UNIX-like systems such as Linux, Mac OS X, and FreeBSD as well as on Windows platforms. It is also known to run -- cgit v1.2.3 From 176914bfea1de3f04617ecde594c07600d8842c4 Mon Sep 17 00:00:00 2001 From: Leonardo Jimenez Date: Mon, 14 Apr 2014 17:38:12 -0400 Subject: Modified the way anonymous commiters configure the remote, the older version only worked for users with access to the repository as commiters. --- HACKING.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HACKING.txt b/HACKING.txt index 460d02047..8e2c049c2 100644 --- a/HACKING.txt +++ b/HACKING.txt @@ -31,7 +31,7 @@ By Hand $ cd hack-on-pyramid # Configure remotes such that you can pull changes from the Pyramid # repository into your local repository. - $ git remote add upstream git@github.com:Pylons/pyramid.git + $ git remote add upstream https://github.com:Pylons/pyramid.git # fetch and merge changes from upstream into master $ git fetch upstream $ git merge upstream/master -- cgit v1.2.3 From ac16ec2f722dcdaa45d0389ad87952bd6be6e389 Mon Sep 17 00:00:00 2001 From: westurner Date: Tue, 15 Apr 2014 01:28:03 -0500 Subject: DOC: Response.content_type defaults to text/html --- docs/narr/webob.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index f0a4b5a0b..6a331e4bf 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -408,6 +408,8 @@ Here are some highlights: The content type *not* including the ``charset`` parameter. Typical use: ``response.content_type = 'text/html'``. + Default value: ``response.content_type = 'text/html'``. + ``response.charset``: The ``charset`` parameter of the content-type, it also informs encoding in ``response.unicode_body``. @@ -466,9 +468,12 @@ argument to the class; e.g.: from pyramid.response import Response response = Response(body='hello world!', content_type='text/plain') -The status defaults to ``'200 OK'``. The content_type does not default to -anything, though if you subclass :class:`pyramid.response.Response` and set -``default_content_type`` you can override this behavior. +The status defaults to ``'200 OK'``. + +The value of content_type defaults to +``webob.response.Response.default_content_type``; which is `text/html`. +You can subclass :class:`pyramid.response.Response` and set +``default_content_type`` to override this behavior. .. index:: single: exception responses -- cgit v1.2.3 From f1f35b771cb361a0e6e47a271292d48bf21c3cdd Mon Sep 17 00:00:00 2001 From: westurner Date: Tue, 15 Apr 2014 10:27:38 -0500 Subject: DOC: Add cgi.escape to quick_tour/views/views.py (Fixes #1294) --- docs/quick_tour/views/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/quick_tour/views/views.py b/docs/quick_tour/views/views.py index 9dc795f14..0ca1347f2 100644 --- a/docs/quick_tour/views/views.py +++ b/docs/quick_tour/views/views.py @@ -2,6 +2,7 @@ from pyramid.httpexceptions import HTTPFound from pyramid.response import Response from pyramid.view import view_config +import cgi # First view, available at http://localhost:6543/ @view_config(route_name='home') @@ -14,7 +15,7 @@ def home_view(request): def hello_view(request): name = request.params.get('name', 'No Name') body = '

Hi %s, this redirects

' - return Response(body % name) + return Response(body % cgi.escape(name)) # /goto which issues HTTP redirect to the last view -- cgit v1.2.3 From 4083b3bb431b464f330fb17e22a6465aeb6f2fe0 Mon Sep 17 00:00:00 2001 From: westurner Date: Tue, 15 Apr 2014 11:55:10 -0500 Subject: DOC: Comment re: XSS, PEP8 imports, typo --- docs/quick_tour/views/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/quick_tour/views/views.py b/docs/quick_tour/views/views.py index 0ca1347f2..1449cbb38 100644 --- a/docs/quick_tour/views/views.py +++ b/docs/quick_tour/views/views.py @@ -1,8 +1,9 @@ +import cgi + from pyramid.httpexceptions import HTTPFound from pyramid.response import Response from pyramid.view import view_config -import cgi # First view, available at http://localhost:6543/ @view_config(route_name='home') @@ -15,6 +16,7 @@ def home_view(request): def hello_view(request): name = request.params.get('name', 'No Name') body = '

Hi %s, this redirects

' + # cgi.escape to prevent Cross-Site Scripting (XSS) [CWE 79] return Response(body % cgi.escape(name)) @@ -24,7 +26,7 @@ def redirect_view(request): return HTTPFound(location="/problem") -# /problem which causes an site error +# /problem which causes a site error @view_config(route_name='exception') def exception_view(request): raise Exception() -- cgit v1.2.3 From 1aa8eb60c7d54324d8d0d31a667b3e9c4c4e3b25 Mon Sep 17 00:00:00 2001 From: tylereaves Date: Wed, 16 Apr 2014 16:13:03 -0400 Subject: Update debugtoolbar.rst Fix wording to reflect how debugtoolbar actually works --- docs/quick_tutorial/debugtoolbar.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst index 1c540d8a2..88b55869e 100644 --- a/docs/quick_tutorial/debugtoolbar.rst +++ b/docs/quick_tutorial/debugtoolbar.rst @@ -71,9 +71,9 @@ 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 (and collapsible) menu in the right of -your browser, providing introspective access to debugging information. -Even better, if your web application generates an error, +You'll now see an attractive button in the right 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 -- cgit v1.2.3 From b6ff39af94797c3b81adc367e86a546a099df519 Mon Sep 17 00:00:00 2001 From: tylereaves Date: Wed, 16 Apr 2014 16:13:33 -0400 Subject: Update debugtoolbar.rst grammar fix --- docs/quick_tutorial/debugtoolbar.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst index 88b55869e..45279b541 100644 --- a/docs/quick_tutorial/debugtoolbar.rst +++ b/docs/quick_tutorial/debugtoolbar.rst @@ -71,7 +71,7 @@ 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 in the right of +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 -- cgit v1.2.3 From b666df0d5f299eca20e9370ac43cddee71b6dd06 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 16 Apr 2014 15:15:50 -0500 Subject: minor tweaks based on toolbar changes in 2.x --- docs/quick_tutorial/debugtoolbar.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst index 45279b541..90750c633 100644 --- a/docs/quick_tutorial/debugtoolbar.rst +++ b/docs/quick_tutorial/debugtoolbar.rst @@ -73,14 +73,15 @@ 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, +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 mutates the HTML generated by our app and uses jQuery to -overlay itself. If you are using the toolbar while you're developing and you +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. -- cgit v1.2.3 From 0eff6638d2470f77bd444941cb8261d91c236591 Mon Sep 17 00:00:00 2001 From: Michael R Date: Wed, 16 Apr 2014 16:49:53 -0400 Subject: add pyramid version and pyramid docs branch to the scaffold generation machinery so it is available in scaffold templates, this will help generate proper links in scaffolds and not confiuse people when they go to documentation --- .../starter/+package+/templates/mytemplate.pt_tmpl | 4 +- pyramid/scaffolds/starter/+package+/views.py_tmpl | 5 +- pyramid/scripts/pcreate.py | 13 +++++ pyramid/tests/test_scripts/test_pcreate.py | 58 ++++++++++++++++++---- 4 files changed, 67 insertions(+), 13 deletions(-) diff --git a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl index 76451f9b5..5f860da26 100644 --- a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl @@ -41,8 +41,8 @@
-- cgit v1.2.3 From 18ca2efc3130642d359f47b97f50046f8b045e7c Mon Sep 17 00:00:00 2001 From: george Date: Wed, 4 Jun 2014 16:07:58 -0600 Subject: Scaffold templates: close more ul tags. Missing tags in other scaffold templates. --- pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl | 1 + pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl | 1 + 2 files changed, 2 insertions(+) diff --git a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl index 73d0976b9..3f1d23d47 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl @@ -46,6 +46,7 @@
  • Github Project
  • IRC Channel
  • Pylons Project
  • +
    diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl index 855944899..e109ab829 100644 --- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl @@ -46,6 +46,7 @@
  • Github Project
  • IRC Channel
  • Pylons Project
  • +
    -- cgit v1.2.3 From 32602159eeef8595c7db6edbf1461c58e9ff0349 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 5 Jun 2014 22:47:35 -0400 Subject: - Work around a bug introduced in Python 2.7.7 on Windows where ``mimetypes.guess_type`` returns Unicode rather than str for the content type, unlike any previous version of Python. See https://github.com/Pylons/pyramid/issues/1360 for more information. Closes #1360. --- CHANGES.txt | 5 +++++ pyramid/response.py | 10 ++++++++-- pyramid/tests/test_response.py | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e60c6efac..51af8ee01 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -20,6 +20,11 @@ Bug Fixes - 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 +- Work around a bug introduced in Python 2.7.7 on Windows where + ``mimetypes.guess_type`` returns Unicode rather than str for the content + type, unlike any previous version of Python. See + https://github.com/Pylons/pyramid/issues/1360 for more information. + Docs ---- diff --git a/pyramid/response.py b/pyramid/response.py index adc903b44..d11fd0123 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -53,10 +53,16 @@ class FileResponse(Response): def __init__(self, path, request=None, cache_max_age=None, content_type=None, content_encoding=None): if content_type is None: - content_type, content_encoding = ( - mimetypes.guess_type(path, strict=False)) + content_type, content_encoding = mimetypes.guess_type( + path, + strict=False + ) if content_type is None: content_type = 'application/octet-stream' + # str-ifying content_type is a workaround for a bug in Python 2.7.7 + # on Windows where mimetypes.guess_type returns unicode for the + # content_type. + content_type = str(content_type) super(FileResponse, self).__init__( conditional_response=True, content_type=content_type, diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py index 8731fa764..a16eb8d33 100644 --- a/pyramid/tests/test_response.py +++ b/pyramid/tests/test_response.py @@ -62,6 +62,27 @@ class TestFileResponse(unittest.TestCase): self.assertEqual(r.headers['content-type'], content_type) r.app_iter.close() + def test_python_277_bug_15207(self): + # python 2.7.7 on windows has a bug where its mimetypes.guess_type + # function returns Unicode for the content_type, unlike any previous + # version of Python. See https://github.com/Pylons/pyramid/issues/1360 + # for more information. + from pyramid.compat import text_ + import mimetypes as old_mimetypes + from pyramid import response + class FakeMimetypesModule(object): + def guess_type(self, *arg, **kw): + return text_('foo/bar'), None + fake_mimetypes = FakeMimetypesModule() + try: + response.mimetypes = fake_mimetypes + path = self._getPath('xml') + r = self._makeOne(path) + self.assertEqual(r.content_type, 'foo/bar') + self.assertEqual(type(r.content_type), str) + finally: + response.mimetypes = old_mimetypes + class TestFileIter(unittest.TestCase): def _makeOne(self, file, block_size): from pyramid.response import FileIter -- cgit v1.2.3 From 6c6bd98b59f97492458cc4154d439e581d4b420a Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Thu, 19 Jun 2014 22:01:47 -0700 Subject: Enable automated testing with Python 3.4 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 29e499e76..ce27b5ec3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ python: - pypy - 3.2 - 3.3 + - 3.4 install: python setup.py dev -- cgit v1.2.3 From c688c70fb2bf6731bbdbf68682eebb203b540a04 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 2 Jul 2014 14:36:27 -0400 Subject: dont need to use any settings, we include pyramid_jinja2 in main --- docs/quick_tutorial/jinja2.rst | 6 ------ docs/quick_tutorial/jinja2/tutorial/tests.py | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/docs/quick_tutorial/jinja2.rst b/docs/quick_tutorial/jinja2.rst index 2f1e295dd..ad6da7a9e 100644 --- a/docs/quick_tutorial/jinja2.rst +++ b/docs/quick_tutorial/jinja2.rst @@ -45,12 +45,6 @@ Steps .. literalinclude:: jinja2/tutorial/home.jinja2 :language: html -#. Get the ``pyramid.includes`` into the functional test setup in - ``jinja2/tutorial/tests.py``: - - .. literalinclude:: jinja2/tutorial/tests.py - :linenos: - #. Now run the tests: .. code-block:: bash diff --git a/docs/quick_tutorial/jinja2/tutorial/tests.py b/docs/quick_tutorial/jinja2/tutorial/tests.py index 0b22946f3..4381235ec 100644 --- a/docs/quick_tutorial/jinja2/tutorial/tests.py +++ b/docs/quick_tutorial/jinja2/tutorial/tests.py @@ -30,13 +30,7 @@ class TutorialViewTests(unittest.TestCase): class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main - - settings = { - 'pyramid.includes': [ - 'pyramid_jinja2' - ] - } - app = main({}, **settings) + app = main({}) from webtest import TestApp self.testapp = TestApp(app) -- cgit v1.2.3 From 7dd856ffed7df4bec637c29f4529cabc07e5e191 Mon Sep 17 00:00:00 2001 From: LiJunjie Date: Mon, 16 Jun 2014 14:36:30 +0800 Subject: Correct handler name for logger_wsgi --- 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 75428d513..71029bb33 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -377,7 +377,7 @@ FileHandler to the list of handlers (named ``accesslog``), and ensure that the [logger_wsgi] level = INFO - handlers = handler_accesslog + handlers = accesslog qualname = wsgi propagate = 0 -- cgit v1.2.3 From 3f87c228c920edbb85a85f3332a5340063e49b11 Mon Sep 17 00:00:00 2001 From: Kamal Gill Date: Mon, 2 Jun 2014 13:51:09 -0700 Subject: Fix path to pshell --- docs/narr/commandline.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 3cabbd8f4..4f16617c4 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -146,7 +146,7 @@ name ``main`` as a section name: .. code-block:: text - $ $VENV/bin starter/development.ini#main + $ $VENV/bin/pshell starter/development.ini#main Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32) [GCC 4.4.3] on linux2 Type "help" for more information. -- cgit v1.2.3 From 7a479d270d654f61fca00f8a27b64fd2bf99d35d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 13 Jul 2014 23:39:38 -0400 Subject: remove lie --- docs/narr/templates.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 460cda8ee..4c1364493 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -316,8 +316,7 @@ template renderer: we're using a Chameleon renderer, it means "relative to the directory in which the file which defines the view configuration lives". In this case, this is the directory containing the file that defines the ``my_view`` - function. View-configuration-relative asset specifications work only - in Chameleon, not in Mako templates. + function. Similar renderer configuration can be done imperatively. See :ref:`views_which_use_a_renderer`. -- cgit v1.2.3 From a8eb53fb79981e1b6fb93af3c80a6bdbae7f9d8f Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Mon, 14 Jul 2014 11:13:28 -0400 Subject: Narrative scifi. --- docs/narr/assets.rst | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index b0a8d18b0..a2976de22 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -286,6 +286,71 @@ the application is being run in development or in production (use a different suggestion for a pattern; any setting name other than ``media_location`` could be used. +.. _cache_busting: + +Cache Busting +------------- + +In order to maximize performance of a web application, you generally want to +limit the number of times a particular client requests the same static asset. +Ideally a client would cache a particular static asset "forever", requiring +it to be sent to the client a single time. The HTTP protocol allows you to +send headers with an HTTP response that can instruct a client to cache a +particular asset for an amount of time. As long as the client has a copy of +the asset in its cache and that cache hasn't expired, the client will use the +cached copy rather than request a new copy from the server. The drawback to +sending cache headers to the client for a static asset is that at some point +the static asset may change, and then you'll want the client to load a new copy +of the asset. Under normal circumstances you'd just need to wait for the +client's cached copy to expire before they get the new version of the static +resource. + +A commonly used workaround to this problem is a technique known as "cache +busting". Cache busting schemes generally involve generating a URL for a +static asset that changes when the static asset changes. This way headers can +be sent along with the static asset instructing the client to cache the asset +for a very long time. When a static asset is changed, the URL used to refer to +it in a web page also changes, so the client sees it as a new resource and +requests a copy, regardless of any caching policy set for the resource's old +URL. + +:app:`Pyramid` can be configured to produce cache busting URLs for static +assets by passing the optional argument, `cache_bust` to +:meth:`~pyramid.config.Configurator.add_static_view`: + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + config.add_static_view(name='static', path='mypackage:folder/static', + cache_bust='md5') + +Supplying the `cache_bust` argument instructs :app:`Pyramid` to add a query +string to URLs generated for this static view which includes the md5 checksum +of the static file being served: + +.. code-block:: python + :linenos: + + js_url = request.static_url('mypackage:folder/static/js/myapp.js') + # Returns: 'http://www.example.com/static/js/myapp.js?md5=c9658b3c0a314a1ca21e5988e662a09e` + +When the asset changes, so will its md5 checksum, and therefore so will its +URL. Supplying the `cache_bust` argument also causes the static view to set +headers instructing clients to cache the asset for ten years, unless the +`max_cache_age` argument is also passed, in which case that value is used. + +.. note:: + + `md5` is currently the only possible value for the `cache_bust` argument to + :meth:`~pyramid.config.Configurator.add_static_view`. + +.. note:: + + md5 checksums are cached in RAM so if you change a static resource without + restarting your application, you may still generate URLs with a stale md5 + checksum. + .. index:: single: static assets view -- cgit v1.2.3 From b648516a5dd61b3ce155586465f473338c230bf9 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Mon, 14 Jul 2014 11:25:33 -0400 Subject: API docs scifi. --- pyramid/config/views.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 7a6157ec8..d938a7632 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -302,7 +302,7 @@ class ViewDeriver(object): raise PredicateMismatch( 'predicate mismatch for view %s (%s)' % ( view_name, predicate.text())) - return view(context, request) + return view(context, request) def checker(context, request): return all((predicate(context, request) for predicate in preds)) @@ -894,8 +894,8 @@ class ViewsConfiguratorMixin(object): request_param - This value can be any string or any sequence of strings. A view - declaration with this argument ensures that the view will only be + This value can be any string or any sequence of strings. A view + declaration with this argument ensures that the view will only be called when the :term:`request` has a key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` variable) that has a name which matches the supplied value (if the value is a string) @@ -1001,7 +1001,7 @@ class ViewsConfiguratorMixin(object): Note that using this feature requires a :term:`session factory` to have been configured. - + .. versionadded:: 1.4a2 physical_path @@ -1039,7 +1039,7 @@ class ViewsConfiguratorMixin(object): This value should be a sequence of references to custom predicate callables. Use custom predicates when no set of predefined predicates do what you need. Custom predicates - can be combined with predefined predicates as necessary. + can be combined with predefined predicates as necessary. Each custom predicate callable should accept two arguments: ``context`` and ``request`` and should return either ``True`` or ``False`` after doing arbitrary evaluation of @@ -1074,7 +1074,7 @@ class ViewsConfiguratorMixin(object): DeprecationWarning, stacklevel=4 ) - + view = self.maybe_dotted(view) context = self.maybe_dotted(context) for_ = self.maybe_dotted(for_) @@ -1160,7 +1160,7 @@ class ViewsConfiguratorMixin(object): view_desc = self.object_description(view) tmpl_intr = None - + view_intr = self.introspectable('views', discriminator, view_desc, @@ -1569,7 +1569,7 @@ class ViewsConfiguratorMixin(object): wrapper=None, route_name=None, request_type=None, - request_method=None, + request_method=None, request_param=None, containment=None, xhr=None, @@ -1612,7 +1612,7 @@ class ViewsConfiguratorMixin(object): '%s may not be used as an argument to add_forbidden_view' % arg ) - + settings = dict( view=view, context=HTTPForbidden, @@ -1623,7 +1623,7 @@ class ViewsConfiguratorMixin(object): containment=containment, xhr=xhr, accept=accept, - header=header, + header=header, path_info=path_info, custom_predicates=custom_predicates, decorator=decorator, @@ -1638,7 +1638,7 @@ class ViewsConfiguratorMixin(object): return self.add_view(**settings) set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias - + @viewdefaults @action_method def add_notfound_view( @@ -1649,7 +1649,7 @@ class ViewsConfiguratorMixin(object): wrapper=None, route_name=None, request_type=None, - request_method=None, + request_method=None, request_param=None, containment=None, xhr=None, @@ -1700,7 +1700,7 @@ class ViewsConfiguratorMixin(object): '%s may not be used as an argument to add_notfound_view' % arg ) - + settings = dict( view=view, context=HTTPNotFound, @@ -1711,7 +1711,7 @@ class ViewsConfiguratorMixin(object): containment=containment, xhr=xhr, accept=accept, - header=header, + header=header, path_info=path_info, custom_predicates=custom_predicates, decorator=decorator, @@ -1786,7 +1786,15 @@ class ViewsConfiguratorMixin(object): ``Expires`` and ``Cache-Control`` headers for static assets served. Note that this argument has no effect when the ``name`` is a *url prefix*. By default, this argument is ``None``, meaning that no - particular Expires or Cache-Control headers are set in the response. + particular Expires or Cache-Control headers are set in the response, + unless ``cache_bust`` is specified. + + The ``cache_bust`` keyword argument may be set to ``"md5"`` to cause + :meth:`~pyramid.request.Request.static_url` to generate URLs with an + additional query string which includes the md5 checksum for the static + asset. This argument modifies the default for ``cache_max_age``, + making it ten years. ``cache_max_age`` may still be explicitly + provided to override this default. The ``permission`` keyword argument is used to specify the :term:`permission` required by a user to execute the static view. By -- cgit v1.2.3 From 5e61602652b2963ee0ef2df30ac81f29c42d3415 Mon Sep 17 00:00:00 2001 From: Alexey Torkhov Date: Mon, 14 Jul 2014 22:49:59 +0400 Subject: Update i18n.rst To set output file for pot-create -o flag should be used, not redirect. --- 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 1de2c8941..cb2cd049c 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -326,7 +326,7 @@ application. You run a ``pot-create`` command to extract the messages: $ cd /place/where/myapplication/setup.py/lives $ mkdir -p myapplication/locale - $ $VENV/bin/pot-create src > myapplication/locale/myapplication.pot + $ $VENV/bin/pot-create -o myapplication/locale/myapplication.pot src The message catalog ``.pot`` template will end up in: -- cgit v1.2.3 From 0445bf2ac9c4cb7862464f1ce8f42c640c11ea7d Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Mon, 14 Jul 2014 15:59:05 -0400 Subject: Try this impl on and see how it feels. --- pyramid/cachebust.py | 34 +++++++++++++++++++++++++++++ pyramid/config/views.py | 19 ++++++++++++++--- pyramid/interfaces.py | 57 +++++++++++++++++++++++++++++++++++++++++++------ pyramid/static.py | 7 +++--- 4 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 pyramid/cachebust.py diff --git a/pyramid/cachebust.py b/pyramid/cachebust.py new file mode 100644 index 000000000..69c7eb1d2 --- /dev/null +++ b/pyramid/cachebust.py @@ -0,0 +1,34 @@ +import hashlib +import pkg_resources + +from zope.interface import implementer + +from .interfaces import ICacheBuster + +from pyramid.asset import resolve_asset_spec + + +def generate_md5(spec): + package, filename = resolve_asset_spec(spec) + md5 = hashlib.md5() + with pkg_resources.resource_stream(package, filename) as stream: + for block in iter(lambda: stream.read(4096), ''): + md5.update(block) + return md5.hexdigest() + + +@implementer(ICacheBuster) +class DefaultCacheBuster(object): + + def generate_token(self, request, pathspec): + token_cache = request.registry.setdefault('md5-token-cache', {}) + token = token_cache.get(pathspec) + if not token: + token_cache[pathspec] = token = generate_md5(pathspec) + return token + + def pregenerate_url(self, request, token, subpath, kw): + return token + '/' + subpath, kw + + def match_url(self, request, path_elements): + return path_elements[1:] diff --git a/pyramid/config/views.py b/pyramid/config/views.py index d938a7632..78c415b14 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -34,6 +34,7 @@ from pyramid.interfaces import ( ) from pyramid import renderers +from pyramid.cachebust import DefaultCacheBuster from pyramid.compat import ( string_types, @@ -1905,11 +1906,16 @@ class StaticURLInfo(object): registry = request.registry except AttributeError: # bw compat (for tests) registry = get_current_registry() - for (url, spec, route_name) in self._get_registrations(registry): + registrations = self._get_registrations(registry) + for (url, spec, route_name, cachebust) in registrations: if path.startswith(spec): subpath = path[len(spec):] if WIN: # pragma: no cover subpath = subpath.replace('\\', '/') # windows + if cachebust: + token = cachebust.generate_token(request, spec + subpath) + subpath, kw = cachebust.pregenerate_url( + request, token, subpath, kw) if url is None: kw['subpath'] = subpath return request.route_url(route_name, **kw) @@ -1949,6 +1955,10 @@ class StaticURLInfo(object): # make sure it ends with a slash name = name + '/' + cachebust = extra.pop('cachebust', None) + if cachebust is True: + cachebust = DefaultCacheBuster() + if url_parse(name).netloc: # it's a URL # url, spec, route_name @@ -1958,9 +1968,12 @@ class StaticURLInfo(object): # it's a view name url = None cache_max_age = extra.pop('cache_max_age', None) + if cache_max_age is None and cachebust: + cache_max_age = 10 * 365 * 24 * 60 * 60 # Ten(ish) years + # create a view view = static_view(spec, cache_max_age=cache_max_age, - use_subpath=True) + use_subpath=True, cachebust=cachebust) # Mutate extra to allow factory, etc to be passed through here. # Treat permission specially because we'd like to default to @@ -2001,7 +2014,7 @@ class StaticURLInfo(object): registrations.pop(idx) # url, spec, route_name - registrations.append((url, spec, route_name)) + registrations.append((url, spec, route_name, cachebust)) intr = config.introspectable('static views', name, diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index aa2dbdafd..e60898dbc 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -708,7 +708,7 @@ class IRoute(Interface): pregenerator = Attribute('This attribute should either be ``None`` or ' 'a callable object implementing the ' '``IRoutePregenerator`` interface') - + def match(path): """ If the ``path`` passed to this function can be matched by the @@ -803,7 +803,7 @@ class IContextURL(IResourceURL): # <__main__.Fudge object at 0x1cda890> # # <__main__.Another object at 0x1cda850> - + def virtual_root(): """ Return the virtual root related to a request and the current context""" @@ -837,9 +837,9 @@ class IPEP302Loader(Interface): def get_code(fullname): """ Return the code object for the module identified by 'fullname'. - + Return 'None' if it's a built-in or extension module. - + If the loader doesn't have the code object but it does have the source code, return the compiled source code. @@ -848,16 +848,16 @@ class IPEP302Loader(Interface): def get_source(fullname): """ Return the source code for the module identified by 'fullname'. - + Return a string, using newline characters for line endings, or None if the source is not available. - + Raise ImportError if the module can't be found by the importer at all. """ def get_filename(fullname): """ Return the value of '__file__' if the named module was loaded. - + If the module is not found, raise ImportError. """ @@ -1164,6 +1164,49 @@ class IJSONAdapter(Interface): class IPredicateList(Interface): """ Interface representing a predicate list """ +class ICacheBuster(Interface): + """ + An instance of a class which implements this interface may be passed as the + ``cachebust`` argument to + :meth:`pyramid.config.Configurator.add_static_view` to add cache busting + capability to a static view. + """ + def generate_token(request, pathspec): + """ + Return a token string for a static asset to be used to rewrite a + static asset URL for cache busting. + + The ``pathspec`` argument is the path specification for the asset we're + generating a token for. + """ + + def pregenerate_url(request, token, subpath, kw): + """ + Modifies the elements and/or keywords used to generate the URL for a + given static asset. + + The ``token`` argument is the result of calling + :meth:`~pyramid.interfaces.ICacheBuster.generate_token` for a static + asset. + + The ``subpath`` argument is the subpath in the static asset URL that + would normally be generated without cache busting. The ``kw`` + argument is the keywords dict that would be passed to + :meth:`~pyramid.request.Request.route_url`. + The return value should be a two-tuple of elements ``(subpath, kw)`` + which are modified from the incoming arguments. + """ + + def match_url(request, path_elements): + """ + Undo any modification to the subpath which may have been done by + :meth:`~pyramid.interfaces.ICacheBuster.pregenerate_url`. The + ``path_elements`` argument is a tuple of path elements that represent + the subpath of the asset request URL. The return value should be + a modified (or not) version of ``path_elements``, which will be used + ultimately to find the asset. + """ + # configuration phases: a lower phase number means the actions associated # with this phase will be executed earlier than those with later phase # numbers. The default phase number is 0, FTR. diff --git a/pyramid/static.py b/pyramid/static.py index aa67568d3..be191971a 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -78,7 +78,7 @@ class static_view(object): """ def __init__(self, root_dir, cache_max_age=3600, package_name=None, - use_subpath=False, index='index.html'): + use_subpath=False, index='index.html', cachebust=None): # package_name is for bw compat; it is preferred to pass in a # package-relative path as root_dir # (e.g. ``anotherpackage:foo/static``). @@ -91,13 +91,15 @@ class static_view(object): self.docroot = docroot self.norm_docroot = normcase(normpath(docroot)) self.index = index + self.cachebust = cachebust def __call__(self, context, request): if self.use_subpath: path_tuple = request.subpath else: path_tuple = traversal_path_info(request.environ['PATH_INFO']) - + if self.cachebust: + path_tuple = self.cachebust.match_url(request, path_tuple) path = _secure_path(path_tuple) if path is None: @@ -153,4 +155,3 @@ def _secure_path(path_tuple): return None encoded = slash.join(path_tuple) # will be unicode return encoded - -- cgit v1.2.3 From 4fcbd9e5351bb7ec417cb10ba89dc3af2a6ef9a7 Mon Sep 17 00:00:00 2001 From: Alexey Torkhov Date: Tue, 15 Jul 2014 12:35:13 +0400 Subject: Update i18n.rst msgfmt produces 'messages.mo' file in current dir by default, it needs -o to specify destination. Also, added ref to adding a translation directory. --- docs/narr/i18n.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index cb2cd049c..6bfbc5136 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -402,11 +402,11 @@ command from Gettext: .. code-block:: text $ cd /place/where/myapplication/setup.py/lives - $ msgfmt myapplication/locale/*/LC_MESSAGES/*.po + $ msgfmt -o myapplication/locale/es/LC_MESSAGES/myapplication.mo myapplication/locale/es/LC_MESSAGES/myapplication.po This will create a ``.mo`` file for each ``.po`` file in your application. As long as the :term:`translation directory` in which -the ``.mo`` file ends up in is configured into your application, these +the ``.mo`` file ends up in is configured into your application (see :ref:`adding_a_translation_directory`), these translations will be available to :app:`Pyramid`. .. index:: -- cgit v1.2.3 From 9d521efce433af574382c86a7397f1ac53a73804 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Tue, 15 Jul 2014 09:56:28 -0400 Subject: Try something a little more decoupled and consistent. --- pyramid/cachebust.py | 34 --------------------- pyramid/config/views.py | 79 ++++++++++++++++++++++++++++++++++++++----------- pyramid/interfaces.py | 79 ++++++++++++++++++++++++++++--------------------- pyramid/static.py | 8 ++--- 4 files changed, 112 insertions(+), 88 deletions(-) delete mode 100644 pyramid/cachebust.py diff --git a/pyramid/cachebust.py b/pyramid/cachebust.py deleted file mode 100644 index 69c7eb1d2..000000000 --- a/pyramid/cachebust.py +++ /dev/null @@ -1,34 +0,0 @@ -import hashlib -import pkg_resources - -from zope.interface import implementer - -from .interfaces import ICacheBuster - -from pyramid.asset import resolve_asset_spec - - -def generate_md5(spec): - package, filename = resolve_asset_spec(spec) - md5 = hashlib.md5() - with pkg_resources.resource_stream(package, filename) as stream: - for block in iter(lambda: stream.read(4096), ''): - md5.update(block) - return md5.hexdigest() - - -@implementer(ICacheBuster) -class DefaultCacheBuster(object): - - def generate_token(self, request, pathspec): - token_cache = request.registry.setdefault('md5-token-cache', {}) - token = token_cache.get(pathspec) - if not token: - token_cache[pathspec] = token = generate_md5(pathspec) - return token - - def pregenerate_url(self, request, token, subpath, kw): - return token + '/' + subpath, kw - - def match_url(self, request, path_elements): - return path_elements[1:] diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 78c415b14..c09ddc73d 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1,6 +1,8 @@ +import hashlib import inspect import operator import os +import pkg_resources import warnings from zope.interface import ( @@ -34,7 +36,7 @@ from pyramid.interfaces import ( ) from pyramid import renderers -from pyramid.cachebust import DefaultCacheBuster +from pyramid.asset import resolve_asset_spec from pyramid.compat import ( string_types, @@ -45,11 +47,6 @@ from pyramid.compat import ( is_nonstr_iter ) -from pyramid.encode import ( - quote_plus, - urlencode, -) - from pyramid.exceptions import ( ConfigurationError, PredicateMismatch, @@ -1907,15 +1904,13 @@ class StaticURLInfo(object): except AttributeError: # bw compat (for tests) registry = get_current_registry() registrations = self._get_registrations(registry) - for (url, spec, route_name, cachebust) in registrations: + for (url, spec, route_name, cachebuster) in registrations: if path.startswith(spec): subpath = path[len(spec):] if WIN: # pragma: no cover subpath = subpath.replace('\\', '/') # windows - if cachebust: - token = cachebust.generate_token(request, spec + subpath) - subpath, kw = cachebust.pregenerate_url( - request, token, subpath, kw) + if cachebuster: + subpath, kw = cachebuster(subpath, kw) if url is None: kw['subpath'] = subpath return request.route_url(route_name, **kw) @@ -1955,9 +1950,22 @@ class StaticURLInfo(object): # make sure it ends with a slash name = name + '/' - cachebust = extra.pop('cachebust', None) - if cachebust is True: - cachebust = DefaultCacheBuster() + cb = extra.pop('cachebust', None) + if cb is True: + cb_token, cb_pregen, cb_match = DefaultCacheBuster() + elif cb: + cb_token, cb_pregen, cb_match = cb + else: + cb_token = cb_pregen = cb_match = None + + if cb_token and cb_pregen: + def cachebuster(subpath, kw): + token = cb_token(spec + subpath) + subpath_tuple = tuple(subpath.split('/')) + subpath_tuple, kw = cb_pregen(token, subpath_tuple, kw) + return '/'.join(subpath_tuple), kw + else: + cachebuster = None if url_parse(name).netloc: # it's a URL @@ -1968,12 +1976,12 @@ class StaticURLInfo(object): # it's a view name url = None cache_max_age = extra.pop('cache_max_age', None) - if cache_max_age is None and cachebust: + if cache_max_age is None and cb: cache_max_age = 10 * 365 * 24 * 60 * 60 # Ten(ish) years # create a view view = static_view(spec, cache_max_age=cache_max_age, - use_subpath=True, cachebust=cachebust) + use_subpath=True, cachebust_match=cb_match) # Mutate extra to allow factory, etc to be passed through here. # Treat permission specially because we'd like to default to @@ -2014,7 +2022,7 @@ class StaticURLInfo(object): registrations.pop(idx) # url, spec, route_name - registrations.append((url, spec, route_name, cachebust)) + registrations.append((url, spec, route_name, cachebuster)) intr = config.introspectable('static views', name, @@ -2026,3 +2034,40 @@ class StaticURLInfo(object): config.action(None, callable=register, introspectables=(intr,)) +def _generate_md5(spec): + package, filename = resolve_asset_spec(spec) + md5 = hashlib.md5() + with pkg_resources.resource_stream(package, filename) as stream: + for block in iter(lambda: stream.read(4096), ''): + md5.update(block) + return md5.hexdigest() + + +def DefaultCacheBuster(): + token_cache = {} + + def generate_token(pathspec): + # An astute observer will notice that this use of token_cache doesn't + # look particular thread safe. Basic read/write operations on Python + # dicts, however, are atomic, so simply accessing and writing values + # to the dict shouldn't cause a segfault or other catastrophic failure. + # (See: http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm) + # + # We do have a race condition that could result in the same md5 + # checksum getting computed twice or more times in parallel. Since + # the program would still function just fine if this were to occur, + # the extra overhead of using locks to serialize access to the dict + # seems an unnecessary burden. + # + token = token_cache.get(pathspec) + if not token: + token_cache[pathspec] = token = _generate_md5(pathspec) + return token + + def pregenerate_url(token, subpath, kw): + return (token,) + subpath, kw + + def match_url(subpath): + return subpath[1:] + + return (generate_token, pregenerate_url, match_url) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index e60898dbc..84a6ad833 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1164,47 +1164,60 @@ class IJSONAdapter(Interface): class IPredicateList(Interface): """ Interface representing a predicate list """ -class ICacheBuster(Interface): - """ - An instance of a class which implements this interface may be passed as the - ``cachebust`` argument to - :meth:`pyramid.config.Configurator.add_static_view` to add cache busting - capability to a static view. - """ - def generate_token(request, pathspec): +class ICachebustTokenGenerator(Interface): + def __call__(pathspec): """ - Return a token string for a static asset to be used to rewrite a - static asset URL for cache busting. + A function which computes and returns a token string used for cache + busting. ``pathspec`` is the path specification for the resource to be + cache busted. Often a cachebust token might be computed for a specific + asset (e.g. an md5 checksum), but probably just as often people use + schemes where a single cachebust token is used globally. It could be a + git commit sha1, a timestamp, or something configured manually. A + pattern that can be useful is to use to a factory function and a + closure to return a function that depends on some configuration. For + example: - The ``pathspec`` argument is the path specification for the asset we're - generating a token for. + .. code-block:: python + :linenos: + + def use_configured_cachebust_token(config): + # config is an instance of pyramid.config.Configurator + token = config.registry.settings['myapp.cachebust_token'] + def cachebust_token(pathspec): + return token + return cachebust_token """ - def pregenerate_url(request, token, subpath, kw): +class ICachebustURLPregenerator(Interface): + def __call__(token, subpath, kw): """ - Modifies the elements and/or keywords used to generate the URL for a - given static asset. - - The ``token`` argument is the result of calling - :meth:`~pyramid.interfaces.ICacheBuster.generate_token` for a static - asset. - - The ``subpath`` argument is the subpath in the static asset URL that - would normally be generated without cache busting. The ``kw`` - argument is the keywords dict that would be passed to - :meth:`~pyramid.request.Request.route_url`. - The return value should be a two-tuple of elements ``(subpath, kw)`` - which are modified from the incoming arguments. + A function which modifies a subpath and/or keyword arguments from which + a static asset URL will be computed during URL generation. The + ``token`` argument is a token string computed by an instance of + :class:`~pyramid.interfaces.ICachebustTokenGenerator` for a particular + asset. The ``subpath`` argument is a tuple of path elements that + represent the portion of the asset URL which is used to find the asset. + The ``kw`` argument is a dict of keywords that are to be passed + eventually to :meth:`~pyramid.request.Request.route_url` for URL + generation. The return value of this function should be two-tuple of + ``(subpath, kw)`` which are versions of the same arguments modified to + include the cachebust token in the generated URL. """ - def match_url(request, path_elements): +class ICachebustURLMatcher(Interface): + def __call__(subpath): """ - Undo any modification to the subpath which may have been done by - :meth:`~pyramid.interfaces.ICacheBuster.pregenerate_url`. The - ``path_elements`` argument is a tuple of path elements that represent - the subpath of the asset request URL. The return value should be - a modified (or not) version of ``path_elements``, which will be used - ultimately to find the asset. + A function which performs the logical inverse of an + :class:`~pyramid.interfaces.ICacheBustURLPregenerator`, by taking a + subpath from a cache busted URL and removing the cachebust token, so + that :app:`Pyramid` can find the underlying asset. If the cache + busting scheme in use doesn't specifically modify the path portion of + the generated URL (e.g. it adds a query string), a function which + implements this interface may not be necessary. + + ``subpath`` is the subpath portion of the URL for an incoming request + for a static asset. The return value should be the same tuple with the + cache busting token elided. """ # configuration phases: a lower phase number means the actions associated diff --git a/pyramid/static.py b/pyramid/static.py index be191971a..87bbcd34c 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -78,7 +78,7 @@ class static_view(object): """ def __init__(self, root_dir, cache_max_age=3600, package_name=None, - use_subpath=False, index='index.html', cachebust=None): + use_subpath=False, index='index.html', cachebust_match=None): # package_name is for bw compat; it is preferred to pass in a # package-relative path as root_dir # (e.g. ``anotherpackage:foo/static``). @@ -91,15 +91,15 @@ class static_view(object): self.docroot = docroot self.norm_docroot = normcase(normpath(docroot)) self.index = index - self.cachebust = cachebust + self.cachebust_match = cachebust_match def __call__(self, context, request): if self.use_subpath: path_tuple = request.subpath else: path_tuple = traversal_path_info(request.environ['PATH_INFO']) - if self.cachebust: - path_tuple = self.cachebust.match_url(request, path_tuple) + if self.cachebust_match: + path_tuple = self.cachebust_match(path_tuple) path = _secure_path(path_tuple) if path is None: -- cgit v1.2.3 From 9af33504d9d621bc0f87752837a09f9110e454e5 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Tue, 15 Jul 2014 10:52:14 -0400 Subject: Show an example. --- pyramid/interfaces.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 84a6ad833..95aa1d60e 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1202,6 +1202,15 @@ class ICachebustURLPregenerator(Interface): generation. The return value of this function should be two-tuple of ``(subpath, kw)`` which are versions of the same arguments modified to include the cachebust token in the generated URL. + + Here is an example which places the token in a query string: + + .. code-block:: python + :linenos: + + def cb_pregen(token, subpath kw): + kw.setdefault('_query', {})['cb'] = token + return subpath, kw """ class ICachebustURLMatcher(Interface): -- cgit v1.2.3 From de2996ddcc7c2ac5c3e59101df0fed1ab832701b Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Tue, 15 Jul 2014 10:57:36 -0400 Subject: Make sure it's possible to still set cache_max_age to None even if cache busting is being used. --- pyramid/config/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index c09ddc73d..b583b59a0 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1975,9 +1975,9 @@ class StaticURLInfo(object): else: # it's a view name url = None - cache_max_age = extra.pop('cache_max_age', None) - if cache_max_age is None and cb: - cache_max_age = 10 * 365 * 24 * 60 * 60 # Ten(ish) years + ten_years = 10 * 365 * 24 * 60 * 60 # more or less + default = ten_years if cb else None + cache_max_age = extra.pop('cache_max_age', default) # create a view view = static_view(spec, cache_max_age=cache_max_age, -- cgit v1.2.3 From 2a1ca8c542e752bdd1de2bfdac0f3365a209c072 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Tue, 15 Jul 2014 14:19:07 -0400 Subject: I kind of like Raydeo's last idea. --- pyramid/config/views.py | 62 ++++++++++--------------------------------------- pyramid/interfaces.py | 29 +++++++++++++---------- pyramid/static.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 62 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index b583b59a0..4b7bdaa81 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1,8 +1,6 @@ -import hashlib import inspect import operator import os -import pkg_resources import warnings from zope.interface import ( @@ -36,7 +34,10 @@ from pyramid.interfaces import ( ) from pyramid import renderers -from pyramid.asset import resolve_asset_spec +from pyramid.static import ( + Md5AssetTokenGenerator, + PathSegmentCacheBuster, +) from pyramid.compat import ( string_types, @@ -1950,19 +1951,14 @@ class StaticURLInfo(object): # make sure it ends with a slash name = name + '/' - cb = extra.pop('cachebust', None) + cb = extra.pop('cachebuster', None) if cb is True: - cb_token, cb_pregen, cb_match = DefaultCacheBuster() - elif cb: - cb_token, cb_pregen, cb_match = cb - else: - cb_token = cb_pregen = cb_match = None - - if cb_token and cb_pregen: + cb = DefaultCacheBuster() + if cb: def cachebuster(subpath, kw): - token = cb_token(spec + subpath) + token = cb.token(spec + subpath) subpath_tuple = tuple(subpath.split('/')) - subpath_tuple, kw = cb_pregen(token, subpath_tuple, kw) + subpath_tuple, kw = cb.pregenerate(token, subpath_tuple, kw) return '/'.join(subpath_tuple), kw else: cachebuster = None @@ -1980,6 +1976,7 @@ class StaticURLInfo(object): cache_max_age = extra.pop('cache_max_age', default) # create a view + cb_match = getattr(cb, 'match', None) view = static_view(spec, cache_max_age=cache_max_age, use_subpath=True, cachebust_match=cb_match) @@ -2033,41 +2030,6 @@ class StaticURLInfo(object): config.action(None, callable=register, introspectables=(intr,)) - -def _generate_md5(spec): - package, filename = resolve_asset_spec(spec) - md5 = hashlib.md5() - with pkg_resources.resource_stream(package, filename) as stream: - for block in iter(lambda: stream.read(4096), ''): - md5.update(block) - return md5.hexdigest() - - def DefaultCacheBuster(): - token_cache = {} - - def generate_token(pathspec): - # An astute observer will notice that this use of token_cache doesn't - # look particular thread safe. Basic read/write operations on Python - # dicts, however, are atomic, so simply accessing and writing values - # to the dict shouldn't cause a segfault or other catastrophic failure. - # (See: http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm) - # - # We do have a race condition that could result in the same md5 - # checksum getting computed twice or more times in parallel. Since - # the program would still function just fine if this were to occur, - # the extra overhead of using locks to serialize access to the dict - # seems an unnecessary burden. - # - token = token_cache.get(pathspec) - if not token: - token_cache[pathspec] = token = _generate_md5(pathspec) - return token - - def pregenerate_url(token, subpath, kw): - return (token,) + subpath, kw - - def match_url(subpath): - return subpath[1:] - - return (generate_token, pregenerate_url, match_url) + return PathSegmentCacheBuster(Md5AssetTokenGenerator()) + diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 95aa1d60e..822d1624c 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1164,8 +1164,12 @@ class IJSONAdapter(Interface): class IPredicateList(Interface): """ Interface representing a predicate list """ -class ICachebustTokenGenerator(Interface): - def __call__(pathspec): +class ICacheBuster(Interface): + """ + A container for functions which implement a cache busting policy for + serving static assets. + """ + def token(pathspec): """ A function which computes and returns a token string used for cache busting. ``pathspec`` is the path specification for the resource to be @@ -1188,13 +1192,12 @@ class ICachebustTokenGenerator(Interface): return cachebust_token """ -class ICachebustURLPregenerator(Interface): - def __call__(token, subpath, kw): + def pregenerate(token, subpath, kw): """ A function which modifies a subpath and/or keyword arguments from which a static asset URL will be computed during URL generation. The ``token`` argument is a token string computed by an instance of - :class:`~pyramid.interfaces.ICachebustTokenGenerator` for a particular + :method:`~pyramid.interfaces.ICacheBuster.token` for a particular asset. The ``subpath`` argument is a tuple of path elements that represent the portion of the asset URL which is used to find the asset. The ``kw`` argument is a dict of keywords that are to be passed @@ -1213,20 +1216,22 @@ class ICachebustURLPregenerator(Interface): return subpath, kw """ -class ICachebustURLMatcher(Interface): - def __call__(subpath): + def match(subpath): """ A function which performs the logical inverse of an - :class:`~pyramid.interfaces.ICacheBustURLPregenerator`, by taking a + :method:`~pyramid.interfaces.ICacheBuster.pregenerate`, by taking a subpath from a cache busted URL and removing the cachebust token, so - that :app:`Pyramid` can find the underlying asset. If the cache - busting scheme in use doesn't specifically modify the path portion of - the generated URL (e.g. it adds a query string), a function which - implements this interface may not be necessary. + that :app:`Pyramid` can find the underlying asset. ``subpath`` is the subpath portion of the URL for an incoming request for a static asset. The return value should be the same tuple with the cache busting token elided. + + If the cache busting scheme in use doesn't specifically modify the path + portion of the generated URL (e.g. it adds a query string), a function + which implements this interface may not be necessary. It is + permissible for an instance of + :class:`~pyramid.interfaces.ICacheBuster` to omit this function. """ # configuration phases: a lower phase number means the actions associated diff --git a/pyramid/static.py b/pyramid/static.py index 87bbcd34c..92251721e 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import hashlib import os +import pkg_resources from os.path import ( normcase, @@ -155,3 +157,57 @@ def _secure_path(path_tuple): return None encoded = slash.join(path_tuple) # will be unicode return encoded + +def _generate_md5(spec): + package, filename = resolve_asset_spec(spec) + md5 = hashlib.md5() + with pkg_resources.resource_stream(package, filename) as stream: + for block in iter(lambda: stream.read(4096), ''): + md5.update(block) + return md5.hexdigest() + +def Md5AssetTokenGenerator(): + token_cache = {} + + def generate_token(pathspec): + # An astute observer will notice that this use of token_cache doesn't + # look particularly thread safe. Basic read/write operations on Python + # dicts, however, are atomic, so simply accessing and writing values + # to the dict shouldn't cause a segfault or other catastrophic failure. + # (See: http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm) + # + # We do have a race condition that could result in the same md5 + # checksum getting computed twice or more times in parallel. Since + # the program would still function just fine if this were to occur, + # the extra overhead of using locks to serialize access to the dict + # seems an unnecessary burden. + # + token = token_cache.get(pathspec) + if not token: + token_cache[pathspec] = token = _generate_md5(pathspec) + return token + + return generate_token + +class PathSegmentCacheBuster(object): + + def __init__(self, token): + self.token = token + + def pregenerate(self, token, subpath, kw): + return (token,) + subpath, kw + + def match(self, subpath): + return subpath[1:] + +class QueryStringCacheBuster(object): + + def __init__(self, token, param='x'): + self.param = param + self.token = token + + def pregenerate(self, token, subpath, kw): + kw.setdefault('_query', {})[self.param] = token + return subpath, kw + + -- cgit v1.2.3 From cac23bb790da283fad7ad51ac4c18fc3903ebb92 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Tue, 15 Jul 2014 16:52:15 -0400 Subject: Fix broken tests. --- pyramid/tests/test_config/test_views.py | 45 +++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 57bb5e9d0..e01aed1f2 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -113,7 +113,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): config.add_view(renderer='dummy.pt') view = self._getViewCallable(config) self.assertRaises(ValueError, view, None, None) - + def test_add_view_with_tmpl_renderer_factory_no_renderer_factory(self): config = self._makeOne(autocommit=True) introspector = DummyIntrospector() @@ -136,7 +136,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): ('renderer factories', '.pt') in introspector.related[-1]) view = self._getViewCallable(config) self.assertTrue(b'Hello!' in view(None, None).body) - + def test_add_view_wrapped_view_is_decorated(self): def view(request): # request-only wrapper """ """ @@ -3742,8 +3742,9 @@ class TestStaticURLInfo(unittest.TestCase): def test_generate_registration_miss(self): inst = self._makeOne() - registrations = [(None, 'spec', 'route_name'), - ('http://example.com/foo/', 'package:path/', None)] + registrations = [ + (None, 'spec', 'route_name', None), + ('http://example.com/foo/', 'package:path/', None, None)] inst._get_registrations = lambda *x: registrations request = self._makeRequest() result = inst.generate('package:path/abc', request) @@ -3751,7 +3752,8 @@ class TestStaticURLInfo(unittest.TestCase): def test_generate_registration_no_registry_on_request(self): inst = self._makeOne() - registrations = [('http://example.com/foo/', 'package:path/', None)] + registrations = [ + ('http://example.com/foo/', 'package:path/', None, None)] inst._get_registrations = lambda *x: registrations request = self._makeRequest() del request.registry @@ -3760,7 +3762,8 @@ class TestStaticURLInfo(unittest.TestCase): def test_generate_slash_in_name1(self): inst = self._makeOne() - registrations = [('http://example.com/foo/', 'package:path/', None)] + registrations = [ + ('http://example.com/foo/', 'package:path/', None, None)] inst._get_registrations = lambda *x: registrations request = self._makeRequest() result = inst.generate('package:path/abc', request) @@ -3768,7 +3771,8 @@ class TestStaticURLInfo(unittest.TestCase): def test_generate_slash_in_name2(self): inst = self._makeOne() - registrations = [('http://example.com/foo/', 'package:path/', None)] + registrations = [ + ('http://example.com/foo/', 'package:path/', None, None)] inst._get_registrations = lambda *x: registrations request = self._makeRequest() result = inst.generate('package:path/', request) @@ -3788,7 +3792,7 @@ class TestStaticURLInfo(unittest.TestCase): def test_generate_route_url(self): inst = self._makeOne() - registrations = [(None, 'package:path/', '__viewname/')] + registrations = [(None, 'package:path/', '__viewname/', None)] inst._get_registrations = lambda *x: registrations def route_url(n, **kw): self.assertEqual(n, '__viewname/') @@ -3801,7 +3805,7 @@ class TestStaticURLInfo(unittest.TestCase): def test_generate_url_unquoted_local(self): inst = self._makeOne() - registrations = [(None, 'package:path/', '__viewname/')] + registrations = [(None, 'package:path/', '__viewname/', None)] inst._get_registrations = lambda *x: registrations def route_url(n, **kw): self.assertEqual(n, '__viewname/') @@ -3814,7 +3818,7 @@ class TestStaticURLInfo(unittest.TestCase): def test_generate_url_quoted_remote(self): inst = self._makeOne() - registrations = [('http://example.com/', 'package:path/', None)] + registrations = [('http://example.com/', 'package:path/', None, None)] inst._get_registrations = lambda *x: registrations request = self._makeRequest() result = inst.generate('package:path/abc def', request, a=1) @@ -3822,7 +3826,7 @@ class TestStaticURLInfo(unittest.TestCase): def test_generate_url_with_custom_query(self): inst = self._makeOne() - registrations = [('http://example.com/', 'package:path/', None)] + registrations = [('http://example.com/', 'package:path/', None, None)] inst._get_registrations = lambda *x: registrations request = self._makeRequest() result = inst.generate('package:path/abc def', request, a=1, @@ -3832,7 +3836,7 @@ class TestStaticURLInfo(unittest.TestCase): def test_generate_url_with_custom_anchor(self): inst = self._makeOne() - registrations = [('http://example.com/', 'package:path/', None)] + registrations = [('http://example.com/', 'package:path/', None, None)] inst._get_registrations = lambda *x: registrations request = self._makeRequest() uc = text_(b'La Pe\xc3\xb1a', 'utf-8') @@ -3846,28 +3850,31 @@ class TestStaticURLInfo(unittest.TestCase): config = self._makeConfig( [('http://example.com/', 'package:path/', None)]) inst.add(config, 'http://example.com', 'anotherpackage:path') - expected = [('http://example.com/', 'anotherpackage:path/', None)] + expected = [ + ('http://example.com/', 'anotherpackage:path/', None, None)] self._assertRegistrations(config, expected) def test_add_url_withendslash(self): inst = self._makeOne() config = self._makeConfig() inst.add(config, 'http://example.com/', 'anotherpackage:path') - expected = [('http://example.com/', 'anotherpackage:path/', None)] + expected = [ + ('http://example.com/', 'anotherpackage:path/', None, None)] self._assertRegistrations(config, expected) def test_add_url_noendslash(self): inst = self._makeOne() config = self._makeConfig() inst.add(config, 'http://example.com', 'anotherpackage:path') - expected = [('http://example.com/', 'anotherpackage:path/', None)] + expected = [ + ('http://example.com/', 'anotherpackage:path/', None, None)] self._assertRegistrations(config, expected) def test_add_url_noscheme(self): inst = self._makeOne() config = self._makeConfig() inst.add(config, '//example.com', 'anotherpackage:path') - expected = [('//example.com/', 'anotherpackage:path/', None)] + expected = [('//example.com/', 'anotherpackage:path/', None, None)] self._assertRegistrations(config, expected) def test_add_viewname(self): @@ -3876,7 +3883,7 @@ class TestStaticURLInfo(unittest.TestCase): config = self._makeConfig() inst = self._makeOne() inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1) - expected = [(None, 'anotherpackage:path/', '__view/')] + expected = [(None, 'anotherpackage:path/', '__view/', None)] self._assertRegistrations(config, expected) self.assertEqual(config.route_args, ('__view/', 'view/*subpath')) self.assertEqual(config.view_kw['permission'], NO_PERMISSION_REQUIRED) @@ -3887,7 +3894,7 @@ class TestStaticURLInfo(unittest.TestCase): config.route_prefix = '/abc' inst = self._makeOne() inst.add(config, 'view', 'anotherpackage:path',) - expected = [(None, 'anotherpackage:path/', '__/abc/view/')] + expected = [(None, 'anotherpackage:path/', '__/abc/view/', None)] self._assertRegistrations(config, expected) self.assertEqual(config.route_args, ('__/abc/view/', 'view/*subpath')) @@ -3904,7 +3911,7 @@ class TestStaticURLInfo(unittest.TestCase): inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1, context=DummyContext) self.assertEqual(config.view_kw['context'], DummyContext) - + def test_add_viewname_with_for_(self): config = self._makeConfig() inst = self._makeOne() -- cgit v1.2.3 From 5350158f666a638293bd2b3d7cd19029e0bab145 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Tue, 15 Jul 2014 18:02:01 -0400 Subject: Test coverage for pyramid.config.views --- pyramid/config/views.py | 11 ++++++---- pyramid/tests/test_config/test_views.py | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 4b7bdaa81..00c5622e7 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1891,6 +1891,12 @@ def isexception(o): @implementer(IStaticURLInfo) class StaticURLInfo(object): + # Indirection for testing + _default_cachebuster = staticmethod(PathSegmentCacheBuster) + _default_asset_token_generator = staticmethod(Md5AssetTokenGenerator) + + def _make_default_cachebuster(self): + return self._default_cachebuster(self._default_asset_token_generator()) def _get_registrations(self, registry): try: @@ -1953,7 +1959,7 @@ class StaticURLInfo(object): cb = extra.pop('cachebuster', None) if cb is True: - cb = DefaultCacheBuster() + cb = self._make_default_cachebuster() if cb: def cachebuster(subpath, kw): token = cb.token(spec + subpath) @@ -2030,6 +2036,3 @@ class StaticURLInfo(object): config.action(None, callable=register, introspectables=(intr,)) -def DefaultCacheBuster(): - return PathSegmentCacheBuster(Md5AssetTokenGenerator()) - diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index e01aed1f2..0b81f5a6f 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -3845,6 +3845,20 @@ class TestStaticURLInfo(unittest.TestCase): self.assertEqual(result, 'http://example.com/abc%20def#La%20Pe%C3%B1a') + def test_generate_url_cachebuster(self): + def cachebuster(subpath, kw): + kw['foo'] = 'bar' + return 'foo' + '/' + subpath, kw + inst = self._makeOne() + registrations = [(None, 'package:path/', '__viewname', cachebuster)] + inst._get_registrations = lambda *x: registrations + request = self._makeRequest() + def route_url(n, **kw): + self.assertEqual(n, '__viewname') + self.assertEqual(kw, {'subpath':'foo/abc', 'foo':'bar'}) + request.route_url = route_url + inst.generate('package:path/abc', request) + def test_add_already_exists(self): inst = self._makeOne() config = self._makeConfig( @@ -3927,6 +3941,31 @@ class TestStaticURLInfo(unittest.TestCase): self.assertEqual(config.view_kw['renderer'], 'mypackage:templates/index.pt') + def test_add_cachebust_default(self): + config = self._makeConfig() + inst = self._makeOne() + inst._default_asset_token_generator = lambda: lambda pathspec: 'foo' + inst.add(config, 'view', 'mypackage:path', cachebuster=True) + cachebuster = config.registry._static_url_registrations[0][3] + subpath, _ = cachebuster('some/path', None) + self.assertEqual(subpath, 'foo/some/path') + + def test_add_cachebust_custom(self): + class DummyCacheBuster(object): + def token(self, pathspec): + return 'foo' + def pregenerate(self, token, subpath, kw): + kw['x'] = token + return subpath, kw + config = self._makeConfig() + inst = self._makeOne() + inst.add(config, 'view', 'mypackage:path', + cachebuster=DummyCacheBuster()) + cachebuster = config.registry._static_url_registrations[0][3] + subpath, kw = cachebuster('some/path', {}) + self.assertEqual(subpath, 'some/path') + self.assertEqual(kw['x'], 'foo') + class Test_view_description(unittest.TestCase): def _callFUT(self, view): from pyramid.config.views import view_description -- cgit v1.2.3 From dc97173e0c7306792814e3fa44dc0cd8e0e493b9 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Wed, 16 Jul 2014 09:10:25 -0400 Subject: Make sure md5 checksum works for non-package assets. --- pyramid/static.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyramid/static.py b/pyramid/static.py index 92251721e..290732640 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -160,8 +160,12 @@ def _secure_path(path_tuple): def _generate_md5(spec): package, filename = resolve_asset_spec(spec) + if package: + stream = pkg_resources.resource_stream(package, filename) + else: + stream = open(filename, 'rb') md5 = hashlib.md5() - with pkg_resources.resource_stream(package, filename) as stream: + with stream: for block in iter(lambda: stream.read(4096), ''): md5.update(block) return md5.hexdigest() -- cgit v1.2.3 From 46c0294c5e66712e186de96f55ced580d3ae4c0b Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Wed, 16 Jul 2014 10:42:37 -0400 Subject: Use the framework, Luke. --- pyramid/static.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pyramid/static.py b/pyramid/static.py index 290732640..7616b0a29 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -28,7 +28,7 @@ from pyramid.httpexceptions import ( HTTPMovedPermanently, ) -from pyramid.path import caller_package +from pyramid.path import AssetResolver, caller_package from pyramid.response import FileResponse from pyramid.traversal import traversal_path_info @@ -159,13 +159,9 @@ def _secure_path(path_tuple): return encoded def _generate_md5(spec): - package, filename = resolve_asset_spec(spec) - if package: - stream = pkg_resources.resource_stream(package, filename) - else: - stream = open(filename, 'rb') + asset = AssetResolver(None).resolve(spec) md5 = hashlib.md5() - with stream: + with asset.stream() as stream: for block in iter(lambda: stream.read(4096), ''): md5.update(block) return md5.hexdigest() -- cgit v1.2.3 From e7339162285144e7bfd716e1e4e000f34974b1c2 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Wed, 16 Jul 2014 10:44:55 -0400 Subject: Unused import. --- pyramid/static.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyramid/static.py b/pyramid/static.py index 7616b0a29..9d691ca46 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import hashlib import os -import pkg_resources from os.path import ( normcase, -- cgit v1.2.3 From faaed6c7cffb453aed823b80f4169e87bfbc8026 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 16 Jul 2014 10:28:47 -0500 Subject: remove mako docs that should be in pyramid_mako package --- docs/narr/environment.rst | 148 ---------------------------------------------- 1 file changed, 148 deletions(-) diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index 412635f08..7bac12ea7 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -13,7 +13,6 @@ single: reload settings single: default_locale_name single: environment variables - single: Mako environment settings single: ini file settings single: PasteDeploy settings @@ -396,153 +395,6 @@ Is equivalent to using the following statements in your configuration code: It is fine to use both or either form. -.. _mako_template_renderer_settings: - -Mako Template Render Settings ------------------------------ - -Mako derives additional settings to configure its template renderer that -should be set when using it. Many of these settings are optional and only need -to be set if they should be different from the default. The Mako Template -Renderer uses a subclass of Mako's `template lookup -`_ and accepts -several arguments to configure it. - -Mako Directories -~~~~~~~~~~~~~~~~ - -The value(s) supplied here are passed in as the template directories. They -should be in :term:`asset specification` format, for example: -``my.package:templates``. - -+-----------------------------+ -| Config File Setting Name | -+=============================+ -| ``mako.directories`` | -| | -| | -| | -+-----------------------------+ - -Mako Module Directory -~~~~~~~~~~~~~~~~~~~~~ - -The value supplied here tells Mako where to store compiled Mako templates. If -omitted, compiled templates will be stored in memory. This value should be an -absolute path, for example: ``%(here)s/data/templates`` would use a directory -called ``data/templates`` in the same parent directory as the INI file. - -+-----------------------------+ -| Config File Setting Name | -+=============================+ -| ``mako.module_directory`` | -| | -| | -| | -+-----------------------------+ - -Mako Input Encoding -~~~~~~~~~~~~~~~~~~~ - -The encoding that Mako templates are assumed to have. By default this is set -to ``utf-8``. If you wish to use a different template encoding, this value -should be changed accordingly. - -+-----------------------------+ -| Config File Setting Name | -+=============================+ -| ``mako.input_encoding`` | -| | -| | -| | -+-----------------------------+ - -Mako Error Handler -~~~~~~~~~~~~~~~~~~ - -A callable (or a :term:`dotted Python name` which names a callable) which is -called whenever Mako compile or runtime exceptions occur. The callable is -passed the current context as well as the exception. If the callable returns -True, the exception is considered to be handled, else it is re-raised after -the function completes. Is used to provide custom error-rendering functions. - -+-----------------------------+ -| Config File Setting Name | -+=============================+ -| ``mako.error_handler`` | -| | -| | -| | -+-----------------------------+ - -Mako Default Filters -~~~~~~~~~~~~~~~~~~~~ - -List of string filter names that will be applied to all Mako expressions. - -+-----------------------------+ -| Config File Setting Name | -+=============================+ -| ``mako.default_filters`` | -| | -| | -| | -+-----------------------------+ - -Mako Import -~~~~~~~~~~~ - -String list of Python statements, typically individual "import" lines, which -will be placed into the module level preamble of all generated Python modules. - - -+-----------------------------+ -| Config File Setting Name | -+=============================+ -| ``mako.imports`` | -| | -| | -| | -+-----------------------------+ - - -Mako Strict Undefined -~~~~~~~~~~~~~~~~~~~~~ - -``true`` or ``false``, representing the "strict undefined" behavior of Mako -(see `Mako Context Variables -`_). By -default, this is ``false``. - -+-----------------------------+ -| Config File Setting Name | -+=============================+ -| ``mako.strict_undefined`` | -| | -| | -| | -+-----------------------------+ - -Mako Preprocessor -~~~~~~~~~~~~~~~~~ - -.. versionadded:: 1.1 - -A callable (or a :term:`dotted Python name` which names a callable) which is -called to preprocess the source before the template is called. The callable -will be passed the full template source before it is parsed. The return -result of the callable will be used as the template source code. - - -+-----------------------------+ -| Config File Setting Name | -+=============================+ -| ``mako.preprocessor`` | -| | -| | -| | -+-----------------------------+ - Examples -------- -- cgit v1.2.3 From b4245a312bfe7f99080d46c1f9814f2c5da2cbf1 Mon Sep 17 00:00:00 2001 From: nick knouf Date: Tue, 15 Jul 2014 17:04:07 -0400 Subject: Updating to current msginit syntax --- 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 6bfbc5136..95f663584 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -352,7 +352,7 @@ command from Gettext: $ cd /place/where/myapplication/setup.py/lives $ cd myapplication/locale $ mkdir -p es/LC_MESSAGES - $ msginit -l es es/LC_MESSAGES/myapplication.po + $ msginit -l es -o es/LC_MESSAGES/myapplication.po This will create a new the message catalog ``.po`` file will in: -- cgit v1.2.3 From eac3ff43f78a33d05e634cd5b4866f7681db34c3 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Wed, 16 Jul 2014 17:22:57 -0400 Subject: Test coverage for static. --- pyramid/tests/test_static.py | 99 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index 94497d4f6..5edb70b50 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -26,7 +26,7 @@ class Test_static_view_use_subpath_False(unittest.TestCase): if kw is not None: environ.update(kw) return Request(environ=environ) - + def test_ctor_defaultargs(self): inst = self._makeOne('package:resource_name') self.assertEqual(inst.package_name, 'package') @@ -110,6 +110,14 @@ class Test_static_view_use_subpath_False(unittest.TestCase): response = inst(context, request) self.assertTrue(b'static' in response.body) + def test_cachebust_match(self): + inst = self._makeOne('pyramid.tests:fixtures/static') + inst.cachebust_match = lambda subpath: subpath[1:] + request = self._makeRequest({'PATH_INFO':'/foo/index.html'}) + context = DummyContext() + response = inst(context, request) + self.assertTrue(b'static' in response.body) + def test_resource_is_file_with_wsgi_file_wrapper(self): from pyramid.response import _BLOCK_SIZE inst = self._makeOne('pyramid.tests:fixtures/static') @@ -218,7 +226,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase): if kw is not None: environ.update(kw) return Request(environ=environ) - + def test_ctor_defaultargs(self): inst = self._makeOne('package:resource_name') self.assertEqual(inst.package_name, 'package') @@ -273,7 +281,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase): context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) - + def test_oob_os_sep(self): import os inst = self._makeOne('pyramid.tests:fixtures/static') @@ -360,6 +368,91 @@ class Test_static_view_use_subpath_True(unittest.TestCase): from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) +class TestMd5AssetTokenGenerator(unittest.TestCase): + + def setUp(self): + import os + import tempfile + self.tmp = tempfile.mkdtemp() + self.fspath = os.path.join(self.tmp, 'test.txt') + + def tearDown(self): + import shutil + shutil.rmtree(self.tmp) + + def _makeOne(self): + from pyramid.static import Md5AssetTokenGenerator as unit + return unit() + + def test_package_resource(self): + fut = self._makeOne() + expected = '76d653a3a044e2f4b38bb001d283e3d9' + token = fut('pyramid.tests:fixtures/static/index.html') + self.assertEqual(token, expected) + + def test_filesystem_resource(self): + fut = self._makeOne() + expected = 'd5155f250bef0e9923e894dbc713c5dd' + with open(self.fspath, 'w') as f: + f.write("Are we rich yet?") + token = fut(self.fspath) + self.assertEqual(token, expected) + + def test_cache(self): + fut = self._makeOne() + expected = 'd5155f250bef0e9923e894dbc713c5dd' + with open(self.fspath, 'w') as f: + f.write("Are we rich yet?") + token = fut(self.fspath) + self.assertEqual(token, expected) + + # md5 shouldn't change because we've cached it + with open(self.fspath, 'w') as f: + f.write("Sorry for the convenience.") + token = fut(self.fspath) + self.assertEqual(token, expected) + +class TestPathSegmentCacheBuster(unittest.TestCase): + + def _makeOne(self): + from pyramid.static import PathSegmentCacheBuster as unit + return unit(lambda pathspec: 'foo') + + def test_token(self): + fut = self._makeOne().token + self.assertEqual(fut('whatever'), 'foo') + + def test_pregenerate(self): + fut = self._makeOne().pregenerate + self.assertEqual(fut('foo', ('bar',), 'kw'), (('foo', 'bar'), 'kw')) + + def test_match(self): + fut = self._makeOne().match + self.assertEqual(fut(('foo', 'bar')), ('bar',)) + +class TestQueryStringCacheBuster(unittest.TestCase): + + def _makeOne(self): + from pyramid.static import QueryStringCacheBuster as unit + return unit(lambda pathspec: 'foo') + + def test_token(self): + fut = self._makeOne().token + self.assertEqual(fut('whatever'), 'foo') + + def test_pregenerate(self): + fut = self._makeOne().pregenerate + self.assertEqual( + fut('foo', ('bar',), {}), + (('bar',), {'_query': {'x': 'foo'}})) + + def test_pregenerate_change_param(self): + from pyramid.static import QueryStringCacheBuster as unit + fut = unit(lambda pathspec: 'foo', 'y').pregenerate + self.assertEqual( + fut('foo', ('bar',), {}), + (('bar',), {'_query': {'y': 'foo'}})) + class DummyContext: pass -- cgit v1.2.3 From 737016eb553701ec154e33d212379a2356917e4c Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Wed, 16 Jul 2014 17:40:46 -0400 Subject: Handle list of tuples as query string. --- pyramid/static.py | 6 +++++- pyramid/tests/test_static.py | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pyramid/static.py b/pyramid/static.py index 9d691ca46..4ae00b056 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -206,7 +206,11 @@ class QueryStringCacheBuster(object): self.token = token def pregenerate(self, token, subpath, kw): - kw.setdefault('_query', {})[self.param] = token + query = kw.setdefault('_query', {}) + if isinstance(query, dict): + query[self.param] = token + else: + kw['_query'] = query + [(self.param, token)] return subpath, kw diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index 5edb70b50..f7b580df2 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -453,6 +453,12 @@ class TestQueryStringCacheBuster(unittest.TestCase): fut('foo', ('bar',), {}), (('bar',), {'_query': {'y': 'foo'}})) + def test_pregenerate_query_is_already_tuples(self): + fut = self._makeOne().pregenerate + self.assertEqual( + fut('foo', ('bar',), {'_query': [('a', 'b')]}), + (('bar',), {'_query': [('a', 'b'), ('x', 'foo')]})) + class DummyContext: pass -- cgit v1.2.3 From d4da82c5ea9713f20205f86c3521db7ebabe2479 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Thu, 17 Jul 2014 09:26:28 -0400 Subject: Fix infinite loop in PY3. --- pyramid/static.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/static.py b/pyramid/static.py index 4ae00b056..09743ac15 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -161,7 +161,7 @@ def _generate_md5(spec): asset = AssetResolver(None).resolve(spec) md5 = hashlib.md5() with asset.stream() as stream: - for block in iter(lambda: stream.read(4096), ''): + for block in iter(lambda: stream.read(4096), b''): md5.update(block) return md5.hexdigest() -- cgit v1.2.3 From f729a1e7f1efc27a6df1ae0eaca7fdffdd86ec2f Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Thu, 17 Jul 2014 16:04:28 -0400 Subject: Write the documentation. --- docs/api/interfaces.rst | 2 ++ docs/api/static.rst | 7 +++++ docs/narr/assets.rst | 72 ++++++++++++++++++++++++++++++++++++++++--------- pyramid/config/views.py | 15 +++++++---- pyramid/interfaces.py | 14 +++++++--- pyramid/static.py | 41 ++++++++++++++++++++++++++++ 6 files changed, 129 insertions(+), 22 deletions(-) diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index d8d935afd..a62976d8a 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -86,3 +86,5 @@ Other Interfaces .. autointerface:: IResourceURL :members: + .. autointerface:: ICacheBuster + :members: diff --git a/docs/api/static.rst b/docs/api/static.rst index c28473584..8ea2fff75 100644 --- a/docs/api/static.rst +++ b/docs/api/static.rst @@ -9,3 +9,10 @@ :members: :inherited-members: + .. autoclass:: PathSegmentCacheBuster + :members: + + .. autoclass:: QueryStringCacheBuster + :members: + + .. autofunction:: Md5AssetTokenGenerator diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index a2976de22..97d473761 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -315,7 +315,7 @@ requests a copy, regardless of any caching policy set for the resource's old URL. :app:`Pyramid` can be configured to produce cache busting URLs for static -assets by passing the optional argument, `cache_bust` to +assets by passing the optional argument, ``cachebuster`` to :meth:`~pyramid.config.Configurator.add_static_view`: .. code-block:: python @@ -323,27 +323,22 @@ assets by passing the optional argument, `cache_bust` to # config is an instance of pyramid.config.Configurator config.add_static_view(name='static', path='mypackage:folder/static', - cache_bust='md5') + cachebuster=True) -Supplying the `cache_bust` argument instructs :app:`Pyramid` to add a query -string to URLs generated for this static view which includes the md5 checksum -of the static file being served: +Setting the ``cachebuster`` argument instructs :app:`Pyramid` to use a cache +busting scheme which adds the md5 checksum for a static asset as a path segment +in the asset's URL: .. code-block:: python :linenos: js_url = request.static_url('mypackage:folder/static/js/myapp.js') - # Returns: 'http://www.example.com/static/js/myapp.js?md5=c9658b3c0a314a1ca21e5988e662a09e` + # Returns: 'http://www.example.com/static/c9658b3c0a314a1ca21e5988e662a09e/js/myapp.js` When the asset changes, so will its md5 checksum, and therefore so will its -URL. Supplying the `cache_bust` argument also causes the static view to set +URL. Supplying the ``cachebuster`` argument also causes the static view to set headers instructing clients to cache the asset for ten years, unless the -`max_cache_age` argument is also passed, in which case that value is used. - -.. note:: - - `md5` is currently the only possible value for the `cache_bust` argument to - :meth:`~pyramid.config.Configurator.add_static_view`. +``max_cache_age`` argument is also passed, in which case that value is used. .. note:: @@ -351,6 +346,57 @@ headers instructing clients to cache the asset for ten years, unless the restarting your application, you may still generate URLs with a stale md5 checksum. +Customizing the Cache Buster +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Revisiting from the previous section: + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + config.add_static_view(name='static', path='mypackage:folder/static', + cachebuster=True) + +Setting ``cachebuster`` to ``True`` instructs :app:`Pyramid` to use a default +cache busting implementation that should work for many situations. The +``cachebuster`` may be set to any object that implements the interface, +:class:`~pyramid.interfaces.ICacheBuster`. The above configuration is exactly +equivalent to: + +.. code-block:: python + :linenos: + + from pyramid.static import ( + Md5AssetTokenGenerator, + PathSegmentCacheBuster) + + # config is an instance of pyramid.config.Configurator + cachebuster = PathSegmentCacheBuster(Md5AssetTokenGenerator()) + config.add_static_view(name='static', path='mypackage:folder/static', + cachebuster=cachebuster) + +:app:`Pyramid` includes two ready to use cache buster implementations: +:class:`~pyramid.static.PathSegmentCacheBuster`, which inserts an asset token +in the path portion of the asset's URL, and +:class:`~pyramid.static.QueryStringCacheBuster`, which adds an asset token to +the query string of the asset's URL. Both of these classes have constructors +which accept a token generator function as an argument, allowing for the way a +token is generated to be decoupled from the way it is inserted into a URL. +:app:`Pyramid` provides a single asset token generator, +:meth:`~pyramid.static.Md5AssetTokenGenerator`. + +In order to implement your own cache buster, see the +:class:`~pyramid.interfaces.ICacheBuster` interface and the existing +implementations in the :mod:`~pyramid.static` module. + +.. note:: + + Many HTTP caching proxy implementations will fail to cache any URL which + has a query string. For this reason, you should probably prefer + :class:`~pyramid.static.PathSegementCacheBuster` to + :class:`~pyramid.static.QueryStringCacheBuster`. + .. index:: single: static assets view diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 00c5622e7..d74ecfadb 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1788,11 +1788,16 @@ class ViewsConfiguratorMixin(object): particular Expires or Cache-Control headers are set in the response, unless ``cache_bust`` is specified. - The ``cache_bust`` keyword argument may be set to ``"md5"`` to cause - :meth:`~pyramid.request.Request.static_url` to generate URLs with an - additional query string which includes the md5 checksum for the static - asset. This argument modifies the default for ``cache_max_age``, - making it ten years. ``cache_max_age`` may still be explicitly + The ``cachebuster`` keyword argument may be set to cause + :meth:`~pyramid.request.Request.static_url` to use cache busting when + generating URLs. See :ref:`cache_busting` for general information + about cache busting. The value of the ``cachebuster`` argument may be + ``True``, in which case a default cache busting implementation is used. + The value of the ``cachebuster`` argument may also be an object which + implements :class:`~pyramid.interfaces.ICacheBuster`. See the + :mod:`~pyramid.static` module for some implementations. If the + ``cachebuster`` argument is provided, the default for ``cache_max_age`` + is modified to be ten years. ``cache_max_age`` may still be explicitly provided to override this default. The ``permission`` keyword argument is used to specify the diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 822d1624c..f3d7b1798 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1168,6 +1168,12 @@ class ICacheBuster(Interface): """ A container for functions which implement a cache busting policy for serving static assets. + + The implementations provided by :app:`Pyramid` use standard instance + methods for ``pregenerate`` and ``match``, while accepting an + implementation of ``token`` as an argument to their constructor. This + pattern allows for the decoupling of how a token is generated and how it is + inserted into a URL. For examples see the :mod:`~pyramid.static` module. """ def token(pathspec): """ @@ -1197,7 +1203,7 @@ class ICacheBuster(Interface): A function which modifies a subpath and/or keyword arguments from which a static asset URL will be computed during URL generation. The ``token`` argument is a token string computed by an instance of - :method:`~pyramid.interfaces.ICacheBuster.token` for a particular + :meth:`~pyramid.interfaces.ICacheBuster.token` for a particular asset. The ``subpath`` argument is a tuple of path elements that represent the portion of the asset URL which is used to find the asset. The ``kw`` argument is a dict of keywords that are to be passed @@ -1218,9 +1224,9 @@ class ICacheBuster(Interface): def match(subpath): """ - A function which performs the logical inverse of an - :method:`~pyramid.interfaces.ICacheBuster.pregenerate`, by taking a - subpath from a cache busted URL and removing the cachebust token, so + A function which performs the logical inverse of + :meth:`~pyramid.interfaces.ICacheBuster.pregenerate` by taking a + subpath from a cache busted URL and removing the cache bust token, so that :app:`Pyramid` can find the underlying asset. ``subpath`` is the subpath portion of the URL for an incoming request diff --git a/pyramid/static.py b/pyramid/static.py index 09743ac15..ab9d47aa5 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -166,6 +166,12 @@ def _generate_md5(spec): return md5.hexdigest() def Md5AssetTokenGenerator(): + """ + A factory method which returns a function that implements + :meth:`~pyramid.interfaces.ICacheBuster.token`. The function computes and + returns md5 checksums for static assets, caching them in memory for speedy + retrieval on subsequent calls. + """ token_cache = {} def generate_token(pathspec): @@ -189,7 +195,23 @@ def Md5AssetTokenGenerator(): return generate_token class PathSegmentCacheBuster(object): + """ + An implementation of :class:`~pyramid.interfaces.ICacheBuster` which + inserts a token for cache busting in the path portion of an asset URL. + + The ``token`` argument should be an implementation of + :meth:`~pyramid.interfaces.ICacheBuster.token`. For example, to use + this cache buster with an md5 token generator: + .. code-block:: python + :linenos: + + from pyramid.static import ( + Md5AssetTokenGenerator, + PathSegmentCacheBuster) + + cachebuster = PathSegmentCacheBuster(Md5AssetTokenGenerator()) + """ def __init__(self, token): self.token = token @@ -200,7 +222,26 @@ class PathSegmentCacheBuster(object): return subpath[1:] class QueryStringCacheBuster(object): + """ + An implementation of :class:`~pyramid.interfaces.ICacheBuster` which + adds a token for cache busting in the query string of an asset URL. + + The ``token`` argument should be an implementation of + :meth:`~pyramid.interfaces.ICacheBuster.token`. For example, to use + this cache buster with an md5 token generator: + + .. code-block:: python + :linenos: + from pyramid.static import ( + Md5AssetTokenGenerator, + PathSegmentCacheBuster) + + cachebuster = QueryStringCacheBuster(Md5AssetTokenGenerator()) + + The optional ``param`` argument determines the name of the parameter added + to the query string and defaults to ``'x'``. + """ def __init__(self, token, param='x'): self.param = param self.token = token -- cgit v1.2.3 From 002da7991f4433e5fd5a07489038a6bd2720a526 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Thu, 17 Jul 2014 16:10:52 -0400 Subject: Add index entry. --- docs/narr/assets.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 97d473761..642211f5b 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -286,6 +286,9 @@ the application is being run in development or in production (use a different suggestion for a pattern; any setting name other than ``media_location`` could be used. +.. index:: + single: Cache Busting + .. _cache_busting: Cache Busting -- cgit v1.2.3 From aa96dda157d39c57c0d2fe8399db0b2175fa83d2 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Fri, 18 Jul 2014 17:18:56 -0400 Subject: Take mcdonc's advice. This should be easier for users to understand. --- docs/api/static.rst | 2 - docs/narr/assets.rst | 41 +++++++++++++------- pyramid/config/views.py | 8 +--- pyramid/static.py | 66 ++++++++++----------------------- pyramid/tests/test_config/test_views.py | 20 +++++----- pyramid/tests/test_static.py | 46 ++++++++++++++--------- 6 files changed, 87 insertions(+), 96 deletions(-) diff --git a/docs/api/static.rst b/docs/api/static.rst index 8ea2fff75..de5bcabda 100644 --- a/docs/api/static.rst +++ b/docs/api/static.rst @@ -14,5 +14,3 @@ .. autoclass:: QueryStringCacheBuster :members: - - .. autofunction:: Md5AssetTokenGenerator diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 642211f5b..7987d03a6 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -370,28 +370,18 @@ equivalent to: .. code-block:: python :linenos: - from pyramid.static import ( - Md5AssetTokenGenerator, - PathSegmentCacheBuster) + from pyramid.static import PathSegmentCacheBuster # config is an instance of pyramid.config.Configurator - cachebuster = PathSegmentCacheBuster(Md5AssetTokenGenerator()) config.add_static_view(name='static', path='mypackage:folder/static', - cachebuster=cachebuster) + cachebuster=PathSegmentCacheBuster()) :app:`Pyramid` includes two ready to use cache buster implementations: :class:`~pyramid.static.PathSegmentCacheBuster`, which inserts an asset token in the path portion of the asset's URL, and :class:`~pyramid.static.QueryStringCacheBuster`, which adds an asset token to -the query string of the asset's URL. Both of these classes have constructors -which accept a token generator function as an argument, allowing for the way a -token is generated to be decoupled from the way it is inserted into a URL. -:app:`Pyramid` provides a single asset token generator, -:meth:`~pyramid.static.Md5AssetTokenGenerator`. - -In order to implement your own cache buster, see the -:class:`~pyramid.interfaces.ICacheBuster` interface and the existing -implementations in the :mod:`~pyramid.static` module. +the query string of the asset's URL. Both of these classes generate md5 +checksums as asset tokens. .. note:: @@ -400,6 +390,29 @@ implementations in the :mod:`~pyramid.static` module. :class:`~pyramid.static.PathSegementCacheBuster` to :class:`~pyramid.static.QueryStringCacheBuster`. +In order to implement your own cache buster, you can write your own class from +scratch which implements the :class:`~pyramid.interfaces.ICacheBuster` +interface. Alternatively you may choose to subclass one of the existing +implementations. One of the most likely scenarios is you'd want to change the +way the asset token is generated. To do this just subclass an existing +implementation and replace the :meth:`~pyramid.interfaces.ICacheBuster.token` +method. Here is an example which just uses a global setting for the asset +token: + +.. code-block:: python + :linenos: + + from pyramid.static import PathSegmentCacheBuster + + class MyCacheBuster(PathSegmentCacheBuster): + + def __init__(self, config): + # config is an instance of pyramid.config.Configurator + self._token = config.registry.settings['myapp.cachebust_token'] + + def token(self, pathspec): + return self._token + .. index:: single: static assets view diff --git a/pyramid/config/views.py b/pyramid/config/views.py index d74ecfadb..f186a44ae 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1897,11 +1897,7 @@ def isexception(o): @implementer(IStaticURLInfo) class StaticURLInfo(object): # Indirection for testing - _default_cachebuster = staticmethod(PathSegmentCacheBuster) - _default_asset_token_generator = staticmethod(Md5AssetTokenGenerator) - - def _make_default_cachebuster(self): - return self._default_cachebuster(self._default_asset_token_generator()) + _default_cachebuster = PathSegmentCacheBuster def _get_registrations(self, registry): try: @@ -1964,7 +1960,7 @@ class StaticURLInfo(object): cb = extra.pop('cachebuster', None) if cb is True: - cb = self._make_default_cachebuster() + cb = self._default_cachebuster() if cb: def cachebuster(subpath, kw): token = cb.token(spec + subpath) diff --git a/pyramid/static.py b/pyramid/static.py index ab9d47aa5..34fc3f55c 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -165,16 +165,16 @@ def _generate_md5(spec): md5.update(block) return md5.hexdigest() -def Md5AssetTokenGenerator(): +class Md5AssetTokenGenerator(object): """ - A factory method which returns a function that implements - :meth:`~pyramid.interfaces.ICacheBuster.token`. The function computes and - returns md5 checksums for static assets, caching them in memory for speedy - retrieval on subsequent calls. + A mixin class which provides an implementation of + :meth:`~pyramid.interfaces.ICacheBuster.target` which generates an md5 + checksum token for an asset, caching it for subsequent calls. """ - token_cache = {} + def __init__(self): + self.token_cache = {} - def generate_token(pathspec): + def token(self, pathspec): # An astute observer will notice that this use of token_cache doesn't # look particularly thread safe. Basic read/write operations on Python # dicts, however, are atomic, so simply accessing and writing values @@ -187,64 +187,36 @@ def Md5AssetTokenGenerator(): # the extra overhead of using locks to serialize access to the dict # seems an unnecessary burden. # - token = token_cache.get(pathspec) + token = self.token_cache.get(pathspec) if not token: - token_cache[pathspec] = token = _generate_md5(pathspec) + self.token_cache[pathspec] = token = _generate_md5(pathspec) return token - return generate_token - -class PathSegmentCacheBuster(object): +class PathSegmentCacheBuster(Md5AssetTokenGenerator): """ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which - inserts a token for cache busting in the path portion of an asset URL. - - The ``token`` argument should be an implementation of - :meth:`~pyramid.interfaces.ICacheBuster.token`. For example, to use - this cache buster with an md5 token generator: - - .. code-block:: python - :linenos: - - from pyramid.static import ( - Md5AssetTokenGenerator, - PathSegmentCacheBuster) - - cachebuster = PathSegmentCacheBuster(Md5AssetTokenGenerator()) + inserts an md5 checksum token for cache busting in the path portion of an + asset URL. Generated md5 checksums are cached in order to speed up + subsequent calls. """ - def __init__(self, token): - self.token = token - def pregenerate(self, token, subpath, kw): return (token,) + subpath, kw def match(self, subpath): return subpath[1:] -class QueryStringCacheBuster(object): +class QueryStringCacheBuster(Md5AssetTokenGenerator): """ - An implementation of :class:`~pyramid.interfaces.ICacheBuster` which - adds a token for cache busting in the query string of an asset URL. - - The ``token`` argument should be an implementation of - :meth:`~pyramid.interfaces.ICacheBuster.token`. For example, to use - this cache buster with an md5 token generator: - - .. code-block:: python - :linenos: - - from pyramid.static import ( - Md5AssetTokenGenerator, - PathSegmentCacheBuster) - - cachebuster = QueryStringCacheBuster(Md5AssetTokenGenerator()) + An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds a + token for cache busting in the query string of an asset URL. Generated md5 + checksums are cached in order to speed up subsequent calls. The optional ``param`` argument determines the name of the parameter added to the query string and defaults to ``'x'``. """ - def __init__(self, token, param='x'): + def __init__(self, param='x'): + super(QueryStringCacheBuster, self).__init__() self.param = param - self.token = token def pregenerate(self, token, subpath, kw): query = kw.setdefault('_query', {}) diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 0b81f5a6f..10a2f6f53 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -3944,19 +3944,14 @@ class TestStaticURLInfo(unittest.TestCase): def test_add_cachebust_default(self): config = self._makeConfig() inst = self._makeOne() - inst._default_asset_token_generator = lambda: lambda pathspec: 'foo' + inst._default_cachebuster = DummyCacheBuster inst.add(config, 'view', 'mypackage:path', cachebuster=True) cachebuster = config.registry._static_url_registrations[0][3] - subpath, _ = cachebuster('some/path', None) - self.assertEqual(subpath, 'foo/some/path') + subpath, kw = cachebuster('some/path', {}) + self.assertEqual(subpath, 'some/path') + self.assertEqual(kw['x'], 'foo') def test_add_cachebust_custom(self): - class DummyCacheBuster(object): - def token(self, pathspec): - return 'foo' - def pregenerate(self, token, subpath, kw): - kw['x'] = token - return subpath, kw config = self._makeConfig() inst = self._makeOne() inst.add(config, 'view', 'mypackage:path', @@ -4071,6 +4066,13 @@ class DummyMultiView: def __permitted__(self, context, request): """ """ +class DummyCacheBuster(object): + def token(self, pathspec): + return 'foo' + def pregenerate(self, token, subpath, kw): + kw['x'] = token + return subpath, kw + def parse_httpdate(s): import datetime # cannot use %Z, must use literal GMT; Jython honors timezone diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index f7b580df2..6ae9b13db 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -369,29 +369,33 @@ class Test_static_view_use_subpath_True(unittest.TestCase): self.assertRaises(HTTPNotFound, inst, context, request) class TestMd5AssetTokenGenerator(unittest.TestCase): + _fspath = None + + @property + def fspath(self): + if self._fspath: + return self._fspath - def setUp(self): import os import tempfile - self.tmp = tempfile.mkdtemp() - self.fspath = os.path.join(self.tmp, 'test.txt') - - def tearDown(self): import shutil - shutil.rmtree(self.tmp) + tmp = tempfile.mkdtemp() + self.addCleanup(lambda: shutil.rmtree(tmp)) + self._fspath = os.path.join(tmp, 'test.txt') + return self._fspath def _makeOne(self): - from pyramid.static import Md5AssetTokenGenerator as unit - return unit() + from pyramid.static import Md5AssetTokenGenerator as cls + return cls() def test_package_resource(self): - fut = self._makeOne() + fut = self._makeOne().token expected = '76d653a3a044e2f4b38bb001d283e3d9' token = fut('pyramid.tests:fixtures/static/index.html') self.assertEqual(token, expected) def test_filesystem_resource(self): - fut = self._makeOne() + fut = self._makeOne().token expected = 'd5155f250bef0e9923e894dbc713c5dd' with open(self.fspath, 'w') as f: f.write("Are we rich yet?") @@ -399,7 +403,7 @@ class TestMd5AssetTokenGenerator(unittest.TestCase): self.assertEqual(token, expected) def test_cache(self): - fut = self._makeOne() + fut = self._makeOne().token expected = 'd5155f250bef0e9923e894dbc713c5dd' with open(self.fspath, 'w') as f: f.write("Are we rich yet?") @@ -415,8 +419,10 @@ class TestMd5AssetTokenGenerator(unittest.TestCase): class TestPathSegmentCacheBuster(unittest.TestCase): def _makeOne(self): - from pyramid.static import PathSegmentCacheBuster as unit - return unit(lambda pathspec: 'foo') + from pyramid.static import PathSegmentCacheBuster as cls + inst = cls() + inst.token = lambda pathspec: 'foo' + return inst def test_token(self): fut = self._makeOne().token @@ -432,9 +438,14 @@ class TestPathSegmentCacheBuster(unittest.TestCase): class TestQueryStringCacheBuster(unittest.TestCase): - def _makeOne(self): - from pyramid.static import QueryStringCacheBuster as unit - return unit(lambda pathspec: 'foo') + def _makeOne(self, param=None): + from pyramid.static import QueryStringCacheBuster as cls + if param: + inst = cls(param) + else: + inst = cls() + inst.token = lambda pathspec: 'foo' + return inst def test_token(self): fut = self._makeOne().token @@ -447,8 +458,7 @@ class TestQueryStringCacheBuster(unittest.TestCase): (('bar',), {'_query': {'x': 'foo'}})) def test_pregenerate_change_param(self): - from pyramid.static import QueryStringCacheBuster as unit - fut = unit(lambda pathspec: 'foo', 'y').pregenerate + fut = self._makeOne('y').pregenerate self.assertEqual( fut('foo', ('bar',), {}), (('bar',), {'_query': {'y': 'foo'}})) -- cgit v1.2.3 From 6596304446f8369dfbcf264d143fe85d75832dba Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Mon, 21 Jul 2014 16:46:35 -0400 Subject: Add 'prevent_cachebuster' setting. --- docs/narr/assets.rst | 11 ++++++++++- docs/narr/environment.rst | 20 +++++++++++++++++++ pyramid/config/settings.py | 9 ++++++++- pyramid/config/views.py | 12 ++++++------ pyramid/tests/test_config/test_settings.py | 31 +++++++++++++++++++++++++++++- pyramid/tests/test_config/test_views.py | 11 ++++++++++- 6 files changed, 84 insertions(+), 10 deletions(-) diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 7987d03a6..fea3fae48 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -349,6 +349,15 @@ headers instructing clients to cache the asset for ten years, unless the restarting your application, you may still generate URLs with a stale md5 checksum. +Disabling the Cache Buster +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It can be useful in some situations (e.g. development) to globally disable all +configured cache busters without changing calls to +:meth:`~pyramid.config.Configurator.add_static_view`. To do this set the +``PYRAMID_PREVENT_CACHEBUSTER`` environment variable or the +``pyramid.prevent_cachebuster`` configuration value to a true value. + Customizing the Cache Buster ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -387,7 +396,7 @@ checksums as asset tokens. Many HTTP caching proxy implementations will fail to cache any URL which has a query string. For this reason, you should probably prefer - :class:`~pyramid.static.PathSegementCacheBuster` to + :class:`~pyramid.static.PathSegmentCacheBuster` to :class:`~pyramid.static.QueryStringCacheBuster`. In order to implement your own cache buster, you can write your own class from diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index 412635f08..7e2f19278 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -158,6 +158,26 @@ feature when this is true. | | | +---------------------------------+----------------------------------+ +Preventing Cache Busting +------------------------ + +Prevent the ``cachebuster`` static view configuration argument from having any +effect globally in this process when this value is true. No cache buster will +be configured or used when this is true. + +.. seealso:: + + See also :ref:`cache_busting`. + ++---------------------------------+----------------------------------+ +| Environment Variable Name | Config File Setting Name | ++=================================+==================================+ +| ``PYRAMID_PREVENT_CACHEBUSTER`` | ``pyramid.prevent_cachebuster`` | +| | or ``prevent_cachebuster`` | +| | | +| | | ++---------------------------------+----------------------------------+ + Debugging All ------------- diff --git a/pyramid/config/settings.py b/pyramid/config/settings.py index 565a6699c..4d7af6015 100644 --- a/pyramid/config/settings.py +++ b/pyramid/config/settings.py @@ -17,7 +17,7 @@ class SettingsConfiguratorMixin(object): def add_settings(self, settings=None, **kw): """Augment the :term:`deployment settings` with one or more - key/value pairs. + key/value pairs. You may pass a dictionary:: @@ -117,6 +117,11 @@ class Settings(dict): config_prevent_http_cache) eff_prevent_http_cache = asbool(eget('PYRAMID_PREVENT_HTTP_CACHE', config_prevent_http_cache)) + config_prevent_cachebuster = self.get('prevent_cachebuster', '') + config_prevent_cachebuster = self.get('pyramid.prevent_cachebuster', + config_prevent_cachebuster) + eff_prevent_cachebuster = asbool(eget('PYRAMID_PREVENT_CACHEBUSTER', + config_prevent_cachebuster)) update = { 'debug_authorization': eff_debug_all or eff_debug_auth, @@ -128,6 +133,7 @@ class Settings(dict): 'reload_assets':eff_reload_all or eff_reload_assets, 'default_locale_name':eff_locale_name, 'prevent_http_cache':eff_prevent_http_cache, + 'prevent_cachebuster':eff_prevent_cachebuster, 'pyramid.debug_authorization': eff_debug_all or eff_debug_auth, 'pyramid.debug_notfound': eff_debug_all or eff_debug_notfound, @@ -138,6 +144,7 @@ class Settings(dict): 'pyramid.reload_assets':eff_reload_all or eff_reload_assets, 'pyramid.default_locale_name':eff_locale_name, 'pyramid.prevent_http_cache':eff_prevent_http_cache, + 'pyramid.prevent_cachebuster':eff_prevent_cachebuster, } self.update(update) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index f186a44ae..62feca77e 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -34,10 +34,7 @@ from pyramid.interfaces import ( ) from pyramid import renderers -from pyramid.static import ( - Md5AssetTokenGenerator, - PathSegmentCacheBuster, -) +from pyramid.static import PathSegmentCacheBuster from pyramid.compat import ( string_types, @@ -1786,7 +1783,7 @@ class ViewsConfiguratorMixin(object): Note that this argument has no effect when the ``name`` is a *url prefix*. By default, this argument is ``None``, meaning that no particular Expires or Cache-Control headers are set in the response, - unless ``cache_bust`` is specified. + unless ``cachebuster`` is specified. The ``cachebuster`` keyword argument may be set to cause :meth:`~pyramid.request.Request.static_url` to use cache busting when @@ -1958,7 +1955,10 @@ class StaticURLInfo(object): # make sure it ends with a slash name = name + '/' - cb = extra.pop('cachebuster', None) + if config.registry.settings.get('pyramid.prevent_cachebuster'): + cb = None + else: + cb = extra.pop('cachebuster', None) if cb is True: cb = self._default_cachebuster() if cb: diff --git a/pyramid/tests/test_config/test_settings.py b/pyramid/tests/test_config/test_settings.py index c74f96375..7cf550c1d 100644 --- a/pyramid/tests/test_config/test_settings.py +++ b/pyramid/tests/test_config/test_settings.py @@ -57,7 +57,7 @@ class TestSettingsConfiguratorMixin(unittest.TestCase): self.assertEqual(settings['a'], 1) class TestSettings(unittest.TestCase): - + def _getTargetClass(self): from pyramid.config.settings import Settings return Settings @@ -131,6 +131,35 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['prevent_http_cache'], True) self.assertEqual(result['pyramid.prevent_http_cache'], True) + def test_prevent_cachebuster(self): + settings = self._makeOne({}) + self.assertEqual(settings['prevent_cachebuster'], False) + self.assertEqual(settings['pyramid.prevent_cachebuster'], False) + result = self._makeOne({'prevent_cachebuster':'false'}) + self.assertEqual(result['prevent_cachebuster'], False) + self.assertEqual(result['pyramid.prevent_cachebuster'], False) + result = self._makeOne({'prevent_cachebuster':'t'}) + self.assertEqual(result['prevent_cachebuster'], True) + self.assertEqual(result['pyramid.prevent_cachebuster'], True) + result = self._makeOne({'prevent_cachebuster':'1'}) + self.assertEqual(result['prevent_cachebuster'], True) + self.assertEqual(result['pyramid.prevent_cachebuster'], True) + result = self._makeOne({'pyramid.prevent_cachebuster':'t'}) + self.assertEqual(result['prevent_cachebuster'], True) + self.assertEqual(result['pyramid.prevent_cachebuster'], True) + result = self._makeOne({}, {'PYRAMID_PREVENT_CACHEBUSTER':'1'}) + self.assertEqual(result['prevent_cachebuster'], True) + self.assertEqual(result['pyramid.prevent_cachebuster'], True) + result = self._makeOne({'prevent_cachebuster':'false', + 'pyramid.prevent_cachebuster':'1'}) + self.assertEqual(result['prevent_cachebuster'], True) + self.assertEqual(result['pyramid.prevent_cachebuster'], True) + result = self._makeOne({'prevent_cachebuster':'false', + 'pyramid.prevent_cachebuster':'f'}, + {'PYRAMID_PREVENT_CACHEBUSTER':'1'}) + self.assertEqual(result['prevent_cachebuster'], True) + self.assertEqual(result['pyramid.prevent_cachebuster'], True) + def test_reload_templates(self): settings = self._makeOne({}) self.assertEqual(settings['reload_templates'], False) diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 10a2f6f53..8f600c1d2 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -3951,6 +3951,14 @@ class TestStaticURLInfo(unittest.TestCase): self.assertEqual(subpath, 'some/path') self.assertEqual(kw['x'], 'foo') + def test_add_cachebust_prevented(self): + config = self._makeConfig() + config.registry.settings['pyramid.prevent_cachebuster'] = True + inst = self._makeOne() + inst.add(config, 'view', 'mypackage:path', cachebuster=True) + cachebuster = config.registry._static_url_registrations[0][3] + self.assertEqual(cachebuster, None) + def test_add_cachebust_custom(self): config = self._makeConfig() inst = self._makeOne() @@ -3980,7 +3988,8 @@ class Test_view_description(unittest.TestCase): class DummyRegistry: - pass + def __init__(self): + self.settings = {} from zope.interface import implementer from pyramid.interfaces import IResponse -- cgit v1.2.3 From 026e292aa8d6da7a2e62eab05d8ceb5f061ac44e Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Mon, 21 Jul 2014 17:26:17 -0400 Subject: Fix tests on py26. --- pyramid/tests/test_static.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index 6ae9b13db..aca5c4bbd 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -370,6 +370,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase): class TestMd5AssetTokenGenerator(unittest.TestCase): _fspath = None + _tmp = None @property def fspath(self): @@ -378,12 +379,15 @@ class TestMd5AssetTokenGenerator(unittest.TestCase): import os import tempfile - import shutil - tmp = tempfile.mkdtemp() - self.addCleanup(lambda: shutil.rmtree(tmp)) + self._tmp = tmp = tempfile.mkdtemp() self._fspath = os.path.join(tmp, 'test.txt') return self._fspath + def tearDown(self): + import shutil + if self._tmp: + shutil.rmtree(self._tmp) + def _makeOne(self): from pyramid.static import Md5AssetTokenGenerator as cls return cls() -- cgit v1.2.3 From 9177d010e6646161e674b82b88bf177ebe8a7b2e Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Mon, 21 Jul 2014 17:29:11 -0400 Subject: Update change log. --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 51af8ee01..54c1a20ed 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,12 @@ Next release ============ +Features +-------- + +- Cache busting for static resources has been added and is available via a new + argument to ``pyramid.config.Configurator.add_static_view``: ``cachebuster``. + Bug Fixes --------- -- cgit v1.2.3 From 168a31d375e35b93c4330c1fd296b1c4ff641029 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 22 Jul 2014 01:08:04 -0700 Subject: fix URL Schema does not change when only host is overridden. --- pyramid/url.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/url.py b/pyramid/url.py index bf4d4ff48..a0f3d7f2f 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -223,7 +223,7 @@ class URLMethodsMixin(object): named portion in the generated URL. For example, if you pass ``_host='foo.com'``, and the URL that would have been generated without the host replacement is ``http://example.com/a``, the result - will be ``https://foo.com/a``. + will be ``http://foo.com/a``. Note that if ``_scheme`` is passed as ``https``, and ``_port`` is not passed, the ``_port`` value is assumed to have been passed as @@ -414,7 +414,7 @@ class URLMethodsMixin(object): portion in the generated URL. For example, if you pass ``host='foo.com'``, and the URL that would have been generated without the host replacement is ``http://example.com/a``, the result - will be ``https://foo.com/a``. + will be ``http://foo.com/a``. If ``scheme`` is passed as ``https``, and an explicit ``port`` is not passed, the ``port`` value is assumed to have been passed as ``443``. -- cgit v1.2.3 From 40c6bfa85a75ffacf23a3ccd128dc5b8bb57a464 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Tue, 22 Jul 2014 09:28:17 -0400 Subject: Forgot to update interface docs earlier. --- pyramid/interfaces.py | 74 ++++++++++++++++----------------------------------- 1 file changed, 23 insertions(+), 51 deletions(-) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index f3d7b1798..2b6ba7eb6 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1166,65 +1166,37 @@ class IPredicateList(Interface): class ICacheBuster(Interface): """ - A container for functions which implement a cache busting policy for - serving static assets. - - The implementations provided by :app:`Pyramid` use standard instance - methods for ``pregenerate`` and ``match``, while accepting an - implementation of ``token`` as an argument to their constructor. This - pattern allows for the decoupling of how a token is generated and how it is - inserted into a URL. For examples see the :mod:`~pyramid.static` module. + Instances of ``ICacheBuster`` may be provided as arguments to + :meth:`~pyramid.config.Configurator.add_static_view`. Instances of + ``ICacheBuster`` provide mechanisms for generating a cache bust token for + a static asset, modifying a static asset URL to include a cache bust token, + and, optionally, unmodifying a static asset URL in order to look up an + asset. See :ref:`cache_busting`. """ def token(pathspec): """ - A function which computes and returns a token string used for cache - busting. ``pathspec`` is the path specification for the resource to be - cache busted. Often a cachebust token might be computed for a specific - asset (e.g. an md5 checksum), but probably just as often people use - schemes where a single cachebust token is used globally. It could be a - git commit sha1, a timestamp, or something configured manually. A - pattern that can be useful is to use to a factory function and a - closure to return a function that depends on some configuration. For - example: - - .. code-block:: python - :linenos: - - def use_configured_cachebust_token(config): - # config is an instance of pyramid.config.Configurator - token = config.registry.settings['myapp.cachebust_token'] - def cachebust_token(pathspec): - return token - return cachebust_token - """ + Computes and returns a token string used for cache busting. + ``pathspec`` is the path specification for the resource to be cache + busted. """ def pregenerate(token, subpath, kw): """ - A function which modifies a subpath and/or keyword arguments from which - a static asset URL will be computed during URL generation. The - ``token`` argument is a token string computed by an instance of - :meth:`~pyramid.interfaces.ICacheBuster.token` for a particular - asset. The ``subpath`` argument is a tuple of path elements that - represent the portion of the asset URL which is used to find the asset. - The ``kw`` argument is a dict of keywords that are to be passed - eventually to :meth:`~pyramid.request.Request.route_url` for URL - generation. The return value of this function should be two-tuple of - ``(subpath, kw)`` which are versions of the same arguments modified to - include the cachebust token in the generated URL. - - Here is an example which places the token in a query string: - - .. code-block:: python - :linenos: - - def cb_pregen(token, subpath kw): - kw.setdefault('_query', {})['cb'] = token - return subpath, kw + Modifies a subpath and/or keyword arguments from which a static asset + URL will be computed during URL generation. The ``token`` argument is + a token string computed by + :meth:`~pyramid.interfaces.ICacheBuster.token` for a particular asset. + The ``subpath`` argument is a tuple of path elements that represent the + portion of the asset URL which is used to find the asset. The ``kw`` + argument is a dict of keywords that are to be passed eventually to + :meth:`~pyramid.request.Request.route_url` for URL generation. The + return value should be a two-tuple of ``(subpath, kw)`` which are + versions of the same arguments modified to include the cachebust token + in the generated URL. """ def match(subpath): """ - A function which performs the logical inverse of + Performs the logical inverse of :meth:`~pyramid.interfaces.ICacheBuster.pregenerate` by taking a subpath from a cache busted URL and removing the cache bust token, so that :app:`Pyramid` can find the underlying asset. @@ -1234,10 +1206,10 @@ class ICacheBuster(Interface): cache busting token elided. If the cache busting scheme in use doesn't specifically modify the path - portion of the generated URL (e.g. it adds a query string), a function + portion of the generated URL (e.g. it adds a query string), a method which implements this interface may not be necessary. It is permissible for an instance of - :class:`~pyramid.interfaces.ICacheBuster` to omit this function. + :class:`~pyramid.interfaces.ICacheBuster` to omit this method. """ # configuration phases: a lower phase number means the actions associated -- cgit v1.2.3 From 4d32f73a86e7223dbdb96b39193d357b38ea1a13 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Tue, 22 Jul 2014 10:00:47 -0400 Subject: Make sure any sequence type works with _query. --- pyramid/static.py | 2 +- pyramid/tests/test_static.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pyramid/static.py b/pyramid/static.py index 34fc3f55c..0cbb5533f 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -223,7 +223,7 @@ class QueryStringCacheBuster(Md5AssetTokenGenerator): if isinstance(query, dict): query[self.param] = token else: - kw['_query'] = query + [(self.param, token)] + kw['_query'] = tuple(query) + ((self.param, token),) return subpath, kw diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index aca5c4bbd..134bea25e 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -471,7 +471,13 @@ class TestQueryStringCacheBuster(unittest.TestCase): fut = self._makeOne().pregenerate self.assertEqual( fut('foo', ('bar',), {'_query': [('a', 'b')]}), - (('bar',), {'_query': [('a', 'b'), ('x', 'foo')]})) + (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))})) + + def test_pregenerate_query_is_tuple_of_tuples(self): + fut = self._makeOne().pregenerate + self.assertEqual( + fut('foo', ('bar',), {'_query': (('a', 'b'),)}), + (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))})) class DummyContext: pass -- cgit v1.2.3 From 15b979413c700fbc289328b25aaa4ba1c4cbdda9 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Thu, 24 Jul 2014 17:13:08 -0400 Subject: cachebuster -> cachebust --- CHANGES.txt | 2 +- docs/narr/assets.rst | 20 +++++------ docs/narr/environment.rst | 6 ++-- pyramid/config/settings.py | 14 ++++---- pyramid/config/views.py | 30 ++++++++--------- pyramid/tests/test_config/test_settings.py | 54 +++++++++++++++--------------- pyramid/tests/test_config/test_views.py | 28 ++++++++-------- 7 files changed, 77 insertions(+), 77 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 54c1a20ed..63987d980 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,7 +5,7 @@ Features -------- - Cache busting for static resources has been added and is available via a new - argument to ``pyramid.config.Configurator.add_static_view``: ``cachebuster``. + argument to ``pyramid.config.Configurator.add_static_view``: ``cachebust``. Bug Fixes --------- diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index fea3fae48..7fb0ec40b 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -318,7 +318,7 @@ requests a copy, regardless of any caching policy set for the resource's old URL. :app:`Pyramid` can be configured to produce cache busting URLs for static -assets by passing the optional argument, ``cachebuster`` to +assets by passing the optional argument, ``cachebust`` to :meth:`~pyramid.config.Configurator.add_static_view`: .. code-block:: python @@ -326,9 +326,9 @@ assets by passing the optional argument, ``cachebuster`` to # config is an instance of pyramid.config.Configurator config.add_static_view(name='static', path='mypackage:folder/static', - cachebuster=True) + cachebust=True) -Setting the ``cachebuster`` argument instructs :app:`Pyramid` to use a cache +Setting the ``cachebust`` argument instructs :app:`Pyramid` to use a cache busting scheme which adds the md5 checksum for a static asset as a path segment in the asset's URL: @@ -339,7 +339,7 @@ in the asset's URL: # Returns: 'http://www.example.com/static/c9658b3c0a314a1ca21e5988e662a09e/js/myapp.js` When the asset changes, so will its md5 checksum, and therefore so will its -URL. Supplying the ``cachebuster`` argument also causes the static view to set +URL. Supplying the ``cachebust`` argument also causes the static view to set headers instructing clients to cache the asset for ten years, unless the ``max_cache_age`` argument is also passed, in which case that value is used. @@ -355,8 +355,8 @@ Disabling the Cache Buster It can be useful in some situations (e.g. development) to globally disable all configured cache busters without changing calls to :meth:`~pyramid.config.Configurator.add_static_view`. To do this set the -``PYRAMID_PREVENT_CACHEBUSTER`` environment variable or the -``pyramid.prevent_cachebuster`` configuration value to a true value. +``PYRAMID_PREVENT_CACHEBUST`` environment variable or the +``pyramid.prevent_cachebust`` configuration value to a true value. Customizing the Cache Buster ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -368,11 +368,11 @@ Revisiting from the previous section: # config is an instance of pyramid.config.Configurator config.add_static_view(name='static', path='mypackage:folder/static', - cachebuster=True) + cachebust=True) -Setting ``cachebuster`` to ``True`` instructs :app:`Pyramid` to use a default +Setting ``cachebust`` to ``True`` instructs :app:`Pyramid` to use a default cache busting implementation that should work for many situations. The -``cachebuster`` may be set to any object that implements the interface, +``cachebust`` may be set to any object that implements the interface, :class:`~pyramid.interfaces.ICacheBuster`. The above configuration is exactly equivalent to: @@ -383,7 +383,7 @@ equivalent to: # config is an instance of pyramid.config.Configurator config.add_static_view(name='static', path='mypackage:folder/static', - cachebuster=PathSegmentCacheBuster()) + cachebust=PathSegmentCacheBuster()) :app:`Pyramid` includes two ready to use cache buster implementations: :class:`~pyramid.static.PathSegmentCacheBuster`, which inserts an asset token diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index 7e2f19278..a81ad19af 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -161,7 +161,7 @@ feature when this is true. Preventing Cache Busting ------------------------ -Prevent the ``cachebuster`` static view configuration argument from having any +Prevent the ``cachebust`` static view configuration argument from having any effect globally in this process when this value is true. No cache buster will be configured or used when this is true. @@ -172,8 +172,8 @@ be configured or used when this is true. +---------------------------------+----------------------------------+ | Environment Variable Name | Config File Setting Name | +=================================+==================================+ -| ``PYRAMID_PREVENT_CACHEBUSTER`` | ``pyramid.prevent_cachebuster`` | -| | or ``prevent_cachebuster`` | +| ``PYRAMID_PREVENT_CACHEBUST`` | ``pyramid.prevent_cachebust`` | +| | or ``prevent_cachebust`` | | | | | | | +---------------------------------+----------------------------------+ diff --git a/pyramid/config/settings.py b/pyramid/config/settings.py index 4d7af6015..492b7d524 100644 --- a/pyramid/config/settings.py +++ b/pyramid/config/settings.py @@ -117,11 +117,11 @@ class Settings(dict): config_prevent_http_cache) eff_prevent_http_cache = asbool(eget('PYRAMID_PREVENT_HTTP_CACHE', config_prevent_http_cache)) - config_prevent_cachebuster = self.get('prevent_cachebuster', '') - config_prevent_cachebuster = self.get('pyramid.prevent_cachebuster', - config_prevent_cachebuster) - eff_prevent_cachebuster = asbool(eget('PYRAMID_PREVENT_CACHEBUSTER', - config_prevent_cachebuster)) + config_prevent_cachebust = self.get('prevent_cachebust', '') + config_prevent_cachebust = self.get('pyramid.prevent_cachebust', + config_prevent_cachebust) + eff_prevent_cachebust = asbool(eget('PYRAMID_PREVENT_CACHEBUST', + config_prevent_cachebust)) update = { 'debug_authorization': eff_debug_all or eff_debug_auth, @@ -133,7 +133,7 @@ class Settings(dict): 'reload_assets':eff_reload_all or eff_reload_assets, 'default_locale_name':eff_locale_name, 'prevent_http_cache':eff_prevent_http_cache, - 'prevent_cachebuster':eff_prevent_cachebuster, + 'prevent_cachebust':eff_prevent_cachebust, 'pyramid.debug_authorization': eff_debug_all or eff_debug_auth, 'pyramid.debug_notfound': eff_debug_all or eff_debug_notfound, @@ -144,7 +144,7 @@ class Settings(dict): 'pyramid.reload_assets':eff_reload_all or eff_reload_assets, 'pyramid.default_locale_name':eff_locale_name, 'pyramid.prevent_http_cache':eff_prevent_http_cache, - 'pyramid.prevent_cachebuster':eff_prevent_cachebuster, + 'pyramid.prevent_cachebust':eff_prevent_cachebust, } self.update(update) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 62feca77e..e6c5baf58 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1783,17 +1783,17 @@ class ViewsConfiguratorMixin(object): Note that this argument has no effect when the ``name`` is a *url prefix*. By default, this argument is ``None``, meaning that no particular Expires or Cache-Control headers are set in the response, - unless ``cachebuster`` is specified. + unless ``cachebust`` is specified. - The ``cachebuster`` keyword argument may be set to cause + The ``cachebust`` keyword argument may be set to cause :meth:`~pyramid.request.Request.static_url` to use cache busting when generating URLs. See :ref:`cache_busting` for general information - about cache busting. The value of the ``cachebuster`` argument may be + about cache busting. The value of the ``cachebust`` argument may be ``True``, in which case a default cache busting implementation is used. - The value of the ``cachebuster`` argument may also be an object which + The value of the ``cachebust`` argument may also be an object which implements :class:`~pyramid.interfaces.ICacheBuster`. See the :mod:`~pyramid.static` module for some implementations. If the - ``cachebuster`` argument is provided, the default for ``cache_max_age`` + ``cachebust`` argument is provided, the default for ``cache_max_age`` is modified to be ten years. ``cache_max_age`` may still be explicitly provided to override this default. @@ -1894,7 +1894,7 @@ def isexception(o): @implementer(IStaticURLInfo) class StaticURLInfo(object): # Indirection for testing - _default_cachebuster = PathSegmentCacheBuster + _default_cachebust = PathSegmentCacheBuster def _get_registrations(self, registry): try: @@ -1909,13 +1909,13 @@ class StaticURLInfo(object): except AttributeError: # bw compat (for tests) registry = get_current_registry() registrations = self._get_registrations(registry) - for (url, spec, route_name, cachebuster) in registrations: + for (url, spec, route_name, cachebust) in registrations: if path.startswith(spec): subpath = path[len(spec):] if WIN: # pragma: no cover subpath = subpath.replace('\\', '/') # windows - if cachebuster: - subpath, kw = cachebuster(subpath, kw) + if cachebust: + subpath, kw = cachebust(subpath, kw) if url is None: kw['subpath'] = subpath return request.route_url(route_name, **kw) @@ -1955,20 +1955,20 @@ class StaticURLInfo(object): # make sure it ends with a slash name = name + '/' - if config.registry.settings.get('pyramid.prevent_cachebuster'): + if config.registry.settings.get('pyramid.prevent_cachebust'): cb = None else: - cb = extra.pop('cachebuster', None) + cb = extra.pop('cachebust', None) if cb is True: - cb = self._default_cachebuster() + cb = self._default_cachebust() if cb: - def cachebuster(subpath, kw): + def cachebust(subpath, kw): token = cb.token(spec + subpath) subpath_tuple = tuple(subpath.split('/')) subpath_tuple, kw = cb.pregenerate(token, subpath_tuple, kw) return '/'.join(subpath_tuple), kw else: - cachebuster = None + cachebust = None if url_parse(name).netloc: # it's a URL @@ -2026,7 +2026,7 @@ class StaticURLInfo(object): registrations.pop(idx) # url, spec, route_name - registrations.append((url, spec, route_name, cachebuster)) + registrations.append((url, spec, route_name, cachebust)) intr = config.introspectable('static views', name, diff --git a/pyramid/tests/test_config/test_settings.py b/pyramid/tests/test_config/test_settings.py index 7cf550c1d..d2a98b347 100644 --- a/pyramid/tests/test_config/test_settings.py +++ b/pyramid/tests/test_config/test_settings.py @@ -131,34 +131,34 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['prevent_http_cache'], True) self.assertEqual(result['pyramid.prevent_http_cache'], True) - def test_prevent_cachebuster(self): + def test_prevent_cachebust(self): settings = self._makeOne({}) - self.assertEqual(settings['prevent_cachebuster'], False) - self.assertEqual(settings['pyramid.prevent_cachebuster'], False) - result = self._makeOne({'prevent_cachebuster':'false'}) - self.assertEqual(result['prevent_cachebuster'], False) - self.assertEqual(result['pyramid.prevent_cachebuster'], False) - result = self._makeOne({'prevent_cachebuster':'t'}) - self.assertEqual(result['prevent_cachebuster'], True) - self.assertEqual(result['pyramid.prevent_cachebuster'], True) - result = self._makeOne({'prevent_cachebuster':'1'}) - self.assertEqual(result['prevent_cachebuster'], True) - self.assertEqual(result['pyramid.prevent_cachebuster'], True) - result = self._makeOne({'pyramid.prevent_cachebuster':'t'}) - self.assertEqual(result['prevent_cachebuster'], True) - self.assertEqual(result['pyramid.prevent_cachebuster'], True) - result = self._makeOne({}, {'PYRAMID_PREVENT_CACHEBUSTER':'1'}) - self.assertEqual(result['prevent_cachebuster'], True) - self.assertEqual(result['pyramid.prevent_cachebuster'], True) - result = self._makeOne({'prevent_cachebuster':'false', - 'pyramid.prevent_cachebuster':'1'}) - self.assertEqual(result['prevent_cachebuster'], True) - self.assertEqual(result['pyramid.prevent_cachebuster'], True) - result = self._makeOne({'prevent_cachebuster':'false', - 'pyramid.prevent_cachebuster':'f'}, - {'PYRAMID_PREVENT_CACHEBUSTER':'1'}) - self.assertEqual(result['prevent_cachebuster'], True) - self.assertEqual(result['pyramid.prevent_cachebuster'], True) + self.assertEqual(settings['prevent_cachebust'], False) + self.assertEqual(settings['pyramid.prevent_cachebust'], False) + result = self._makeOne({'prevent_cachebust':'false'}) + self.assertEqual(result['prevent_cachebust'], False) + self.assertEqual(result['pyramid.prevent_cachebust'], False) + result = self._makeOne({'prevent_cachebust':'t'}) + self.assertEqual(result['prevent_cachebust'], True) + self.assertEqual(result['pyramid.prevent_cachebust'], True) + result = self._makeOne({'prevent_cachebust':'1'}) + self.assertEqual(result['prevent_cachebust'], True) + self.assertEqual(result['pyramid.prevent_cachebust'], True) + result = self._makeOne({'pyramid.prevent_cachebust':'t'}) + self.assertEqual(result['prevent_cachebust'], True) + self.assertEqual(result['pyramid.prevent_cachebust'], True) + result = self._makeOne({}, {'PYRAMID_PREVENT_CACHEBUST':'1'}) + self.assertEqual(result['prevent_cachebust'], True) + self.assertEqual(result['pyramid.prevent_cachebust'], True) + result = self._makeOne({'prevent_cachebust':'false', + 'pyramid.prevent_cachebust':'1'}) + self.assertEqual(result['prevent_cachebust'], True) + self.assertEqual(result['pyramid.prevent_cachebust'], True) + result = self._makeOne({'prevent_cachebust':'false', + 'pyramid.prevent_cachebust':'f'}, + {'PYRAMID_PREVENT_CACHEBUST':'1'}) + self.assertEqual(result['prevent_cachebust'], True) + self.assertEqual(result['pyramid.prevent_cachebust'], True) def test_reload_templates(self): settings = self._makeOne({}) diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 8f600c1d2..a0d9ee0c3 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -3845,12 +3845,12 @@ class TestStaticURLInfo(unittest.TestCase): self.assertEqual(result, 'http://example.com/abc%20def#La%20Pe%C3%B1a') - def test_generate_url_cachebuster(self): - def cachebuster(subpath, kw): + def test_generate_url_cachebust(self): + def cachebust(subpath, kw): kw['foo'] = 'bar' return 'foo' + '/' + subpath, kw inst = self._makeOne() - registrations = [(None, 'package:path/', '__viewname', cachebuster)] + registrations = [(None, 'package:path/', '__viewname', cachebust)] inst._get_registrations = lambda *x: registrations request = self._makeRequest() def route_url(n, **kw): @@ -3944,28 +3944,28 @@ class TestStaticURLInfo(unittest.TestCase): def test_add_cachebust_default(self): config = self._makeConfig() inst = self._makeOne() - inst._default_cachebuster = DummyCacheBuster - inst.add(config, 'view', 'mypackage:path', cachebuster=True) - cachebuster = config.registry._static_url_registrations[0][3] - subpath, kw = cachebuster('some/path', {}) + inst._default_cachebust = DummyCacheBuster + inst.add(config, 'view', 'mypackage:path', cachebust=True) + cachebust = config.registry._static_url_registrations[0][3] + subpath, kw = cachebust('some/path', {}) self.assertEqual(subpath, 'some/path') self.assertEqual(kw['x'], 'foo') def test_add_cachebust_prevented(self): config = self._makeConfig() - config.registry.settings['pyramid.prevent_cachebuster'] = True + config.registry.settings['pyramid.prevent_cachebust'] = True inst = self._makeOne() - inst.add(config, 'view', 'mypackage:path', cachebuster=True) - cachebuster = config.registry._static_url_registrations[0][3] - self.assertEqual(cachebuster, None) + inst.add(config, 'view', 'mypackage:path', cachebust=True) + cachebust = config.registry._static_url_registrations[0][3] + self.assertEqual(cachebust, None) def test_add_cachebust_custom(self): config = self._makeConfig() inst = self._makeOne() inst.add(config, 'view', 'mypackage:path', - cachebuster=DummyCacheBuster()) - cachebuster = config.registry._static_url_registrations[0][3] - subpath, kw = cachebuster('some/path', {}) + cachebust=DummyCacheBuster()) + cachebust = config.registry._static_url_registrations[0][3] + subpath, kw = cachebust('some/path', {}) self.assertEqual(subpath, 'some/path') self.assertEqual(kw['x'], 'foo') -- cgit v1.2.3 From f674a8f691d260d44e0f76e3afecfb15484c45b9 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Mon, 28 Jul 2014 17:26:11 -0400 Subject: Mo' features, mo' problems. --- docs/api/static.rst | 7 +++- docs/narr/assets.rst | 87 +++++++++++++++++++++++++++++++------------- pyramid/config/views.py | 4 +- pyramid/static.py | 30 ++++++++++++--- pyramid/tests/test_static.py | 46 +++++++++++++++++++++-- 5 files changed, 135 insertions(+), 39 deletions(-) diff --git a/docs/api/static.rst b/docs/api/static.rst index de5bcabda..543e526ad 100644 --- a/docs/api/static.rst +++ b/docs/api/static.rst @@ -9,8 +9,11 @@ :members: :inherited-members: - .. autoclass:: PathSegmentCacheBuster + .. autoclass:: PathSegmentMd5CacheBuster :members: - .. autoclass:: QueryStringCacheBuster + .. autoclass:: QueryStringMd5CacheBuster + :members: + + .. autoclass:: QueryStringConstantCacheBuster :members: diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 7fb0ec40b..33677988d 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -379,25 +379,19 @@ equivalent to: .. code-block:: python :linenos: - from pyramid.static import PathSegmentCacheBuster + from pyramid.static import PathSegmentMd5CacheBuster # config is an instance of pyramid.config.Configurator config.add_static_view(name='static', path='mypackage:folder/static', - cachebust=PathSegmentCacheBuster()) + cachebust=PathSegmentMd5CacheBuster()) -:app:`Pyramid` includes two ready to use cache buster implementations: -:class:`~pyramid.static.PathSegmentCacheBuster`, which inserts an asset token -in the path portion of the asset's URL, and -:class:`~pyramid.static.QueryStringCacheBuster`, which adds an asset token to -the query string of the asset's URL. Both of these classes generate md5 -checksums as asset tokens. - -.. note:: - - Many HTTP caching proxy implementations will fail to cache any URL which - has a query string. For this reason, you should probably prefer - :class:`~pyramid.static.PathSegmentCacheBuster` to - :class:`~pyramid.static.QueryStringCacheBuster`. +:app:`Pyramid` includes a handful of ready to use cache buster implementations: +:class:`~pyramid.static.PathSegmentMd5CacheBuster`, which inserts an md5 +checksum token in the path portion of the asset's URL, +:class:`~pyramid.static.QueryStringMd5CacheBuster`, which adds an md5 checksum +token to the query string of the asset's URL, and +:class:`~pyramid.static.QueryStringConstantCacheBuster`, which adds an +arbitrary token you provide to the query string of the asset's URL. In order to implement your own cache buster, you can write your own class from scratch which implements the :class:`~pyramid.interfaces.ICacheBuster` @@ -405,22 +399,65 @@ interface. Alternatively you may choose to subclass one of the existing implementations. One of the most likely scenarios is you'd want to change the way the asset token is generated. To do this just subclass an existing implementation and replace the :meth:`~pyramid.interfaces.ICacheBuster.token` -method. Here is an example which just uses a global setting for the asset -token: +method. Here is an example which just uses Git to get the hash of the +currently checked out code: .. code-block:: python :linenos: - - from pyramid.static import PathSegmentCacheBuster - class MyCacheBuster(PathSegmentCacheBuster): - - def __init__(self, config): - # config is an instance of pyramid.config.Configurator - self._token = config.registry.settings['myapp.cachebust_token'] + import os + import subprocess + from pyramid.static import PathSegmentMd5CacheBuster + + class GitCacheBuster(PathSegmentMd5CacheBuster): + """ + Assuming your code is installed as a Git checkout, as opposed to as an + egg from an egg repository like PYPI, you can use this cachebuster to + get the current commit's SHA1 to use as the cache bust token. + """ + def __init__(self): + here = os.path.dirname(os.path.abspath(__file__)) + self.sha1 = subprocess.check_output( + ['git', 'rev-parse', 'HEAD'], + cwd=here).strip() def token(self, pathspec): - return self._token + return self.sha1 + +Choosing a Cache Buster +~~~~~~~~~~~~~~~~~~~~~~~ + +The default cache buster implementation, +:class:`~pyramid.static.PathSegmentMd5CacheBuster`, works very well assuming +that you're using :app:`Pyramid` to serve your static assets. The md5 checksum +is fine grained enough that browsers should only request new versions of +specific assets that have changed. Many caching HTTP proxies will fail to +cache a resource if the URL contains a query string. In general, therefore, +you should prefer a cache busting strategy which modifies the path segment to +a strategy which adds a query string. + +It is possible, however, that your static assets are being served by another +web server or externally on a CDN. In these cases modifying the path segment +for a static asset URL would cause the external service to fail to find the +asset, causing your customer to get a 404. In these cases you would need to +fall back to a cache buster which adds a query string. It is even possible +that there isn't a copy of your static assets available to the :app:`Pyramid` +application, so a cache busting implementation that generates md5 checksums +would fail since it can't access the assets. In such a case, +:class:`~pyramid.static.QueryStringConstantCacheBuster` is a reasonable +fallback. The following code would set up a cachebuster that just uses the +time at start up as a cachebust token: + +.. code-block:: python + :linenos: + + import time + from pyramid.static import QueryStringConstantCacheBuster + + config.add_static_view( + name='http://mycdn.example.com/', + path='mypackage:static', + cachebust=QueryStringConstantCacheBuster(str(time.time()))) .. index:: single: static assets view diff --git a/pyramid/config/views.py b/pyramid/config/views.py index e6c5baf58..5ca696069 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -34,7 +34,7 @@ from pyramid.interfaces import ( ) from pyramid import renderers -from pyramid.static import PathSegmentCacheBuster +from pyramid.static import PathSegmentMd5CacheBuster from pyramid.compat import ( string_types, @@ -1894,7 +1894,7 @@ def isexception(o): @implementer(IStaticURLInfo) class StaticURLInfo(object): # Indirection for testing - _default_cachebust = PathSegmentCacheBuster + _default_cachebust = PathSegmentMd5CacheBuster def _get_registrations(self, registry): try: diff --git a/pyramid/static.py b/pyramid/static.py index 0cbb5533f..5e017e1cd 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -192,7 +192,7 @@ class Md5AssetTokenGenerator(object): self.token_cache[pathspec] = token = _generate_md5(pathspec) return token -class PathSegmentCacheBuster(Md5AssetTokenGenerator): +class PathSegmentMd5CacheBuster(Md5AssetTokenGenerator): """ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which inserts an md5 checksum token for cache busting in the path portion of an @@ -205,17 +205,18 @@ class PathSegmentCacheBuster(Md5AssetTokenGenerator): def match(self, subpath): return subpath[1:] -class QueryStringCacheBuster(Md5AssetTokenGenerator): +class QueryStringMd5CacheBuster(Md5AssetTokenGenerator): """ - An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds a - token for cache busting in the query string of an asset URL. Generated md5 - checksums are cached in order to speed up subsequent calls. + An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds + an md5 checksum token for cache busting in the query string of an asset + URL. Generated md5 checksums are cached in order to speed up subsequent + calls. The optional ``param`` argument determines the name of the parameter added to the query string and defaults to ``'x'``. """ def __init__(self, param='x'): - super(QueryStringCacheBuster, self).__init__() + super(QueryStringMd5CacheBuster, self).__init__() self.param = param def pregenerate(self, token, subpath, kw): @@ -226,4 +227,21 @@ class QueryStringCacheBuster(Md5AssetTokenGenerator): kw['_query'] = tuple(query) + ((self.param, token),) return subpath, kw +class QueryStringConstantCacheBuster(QueryStringMd5CacheBuster): + """ + An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds + an arbitrary token for cache busting in the query string of an asset URL. + + The ``token`` parameter is the token string to use for cache busting and + will be the same for every request. + + The optional ``param`` argument determines the name of the parameter added + to the query string and defaults to ``'x'``. + """ + def __init__(self, token, param='x'): + self._token = token + self.param = param + + def token(self, pathspec): + return self._token diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index 134bea25e..2f4de249e 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -420,10 +420,10 @@ class TestMd5AssetTokenGenerator(unittest.TestCase): token = fut(self.fspath) self.assertEqual(token, expected) -class TestPathSegmentCacheBuster(unittest.TestCase): +class TestPathSegmentMd5CacheBuster(unittest.TestCase): def _makeOne(self): - from pyramid.static import PathSegmentCacheBuster as cls + from pyramid.static import PathSegmentMd5CacheBuster as cls inst = cls() inst.token = lambda pathspec: 'foo' return inst @@ -440,10 +440,10 @@ class TestPathSegmentCacheBuster(unittest.TestCase): fut = self._makeOne().match self.assertEqual(fut(('foo', 'bar')), ('bar',)) -class TestQueryStringCacheBuster(unittest.TestCase): +class TestQueryStringMd5CacheBuster(unittest.TestCase): def _makeOne(self, param=None): - from pyramid.static import QueryStringCacheBuster as cls + from pyramid.static import QueryStringMd5CacheBuster as cls if param: inst = cls(param) else: @@ -479,6 +479,44 @@ class TestQueryStringCacheBuster(unittest.TestCase): fut('foo', ('bar',), {'_query': (('a', 'b'),)}), (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))})) +class TestQueryStringConstantCacheBuster(TestQueryStringMd5CacheBuster): + + def _makeOne(self, param=None): + from pyramid.static import QueryStringConstantCacheBuster as cls + if param: + inst = cls('foo', param) + else: + inst = cls('foo') + return inst + + def test_token(self): + fut = self._makeOne().token + self.assertEqual(fut('whatever'), 'foo') + + def test_pregenerate(self): + fut = self._makeOne().pregenerate + self.assertEqual( + fut('foo', ('bar',), {}), + (('bar',), {'_query': {'x': 'foo'}})) + + def test_pregenerate_change_param(self): + fut = self._makeOne('y').pregenerate + self.assertEqual( + fut('foo', ('bar',), {}), + (('bar',), {'_query': {'y': 'foo'}})) + + def test_pregenerate_query_is_already_tuples(self): + fut = self._makeOne().pregenerate + self.assertEqual( + fut('foo', ('bar',), {'_query': [('a', 'b')]}), + (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))})) + + def test_pregenerate_query_is_tuple_of_tuples(self): + fut = self._makeOne().pregenerate + self.assertEqual( + fut('foo', ('bar',), {'_query': (('a', 'b'),)}), + (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))})) + class DummyContext: pass -- cgit v1.2.3 From 6b88bdf7680151345debec0c8651f164a149a53a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 28 Jul 2014 21:06:34 -0400 Subject: add versionadded notes --- docs/narr/assets.rst | 2 ++ docs/narr/environment.rst | 2 ++ pyramid/interfaces.py | 2 ++ pyramid/static.py | 6 ++++++ 4 files changed, 12 insertions(+) diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 33677988d..95863848b 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -294,6 +294,8 @@ could be used. Cache Busting ------------- +.. versionadded:: 1.6 + In order to maximize performance of a web application, you generally want to limit the number of times a particular client requests the same static asset. Ideally a client would cache a particular static asset "forever", requiring diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index a81ad19af..e1171d710 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -165,6 +165,8 @@ Prevent the ``cachebust`` static view configuration argument from having any effect globally in this process when this value is true. No cache buster will be configured or used when this is true. +.. versionadded:: 1.6 + .. seealso:: See also :ref:`cache_busting`. diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 2b6ba7eb6..c5a70dbfd 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1172,6 +1172,8 @@ class ICacheBuster(Interface): a static asset, modifying a static asset URL to include a cache bust token, and, optionally, unmodifying a static asset URL in order to look up an asset. See :ref:`cache_busting`. + + .. versionadded:: 1.6 """ def token(pathspec): """ diff --git a/pyramid/static.py b/pyramid/static.py index 5e017e1cd..c4a9e3cc4 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -198,6 +198,8 @@ class PathSegmentMd5CacheBuster(Md5AssetTokenGenerator): inserts an md5 checksum token for cache busting in the path portion of an asset URL. Generated md5 checksums are cached in order to speed up subsequent calls. + + .. versionadded:: 1.6 """ def pregenerate(self, token, subpath, kw): return (token,) + subpath, kw @@ -214,6 +216,8 @@ class QueryStringMd5CacheBuster(Md5AssetTokenGenerator): The optional ``param`` argument determines the name of the parameter added to the query string and defaults to ``'x'``. + + .. versionadded:: 1.6 """ def __init__(self, param='x'): super(QueryStringMd5CacheBuster, self).__init__() @@ -237,6 +241,8 @@ class QueryStringConstantCacheBuster(QueryStringMd5CacheBuster): The optional ``param`` argument determines the name of the parameter added to the query string and defaults to ``'x'``. + + .. versionadded:: 1.6 """ def __init__(self, token, param='x'): self._token = token -- cgit v1.2.3