Chris McDonough
2015-06-23 f3b0c71139da13c88a660ef6edbe4d37ecb9e508
Merge branch 'master' of github.com:Pylons/pyramid
13 files modified
321 ■■■■ changed files
CHANGES.txt 6 ●●●●● patch | view | raw | blame | history
CONTRIBUTORS.txt 2 ●●●●● patch | view | raw | blame | history
docs/narr/assets.rst 87 ●●●●● patch | view | raw | blame | history
docs/narr/viewconfig.rst 4 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/debugtoolbar.rst 24 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/ini.rst 6 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/authorization.rst 12 ●●●● patch | view | raw | blame | history
pyramid/httpexceptions.py 16 ●●●● patch | view | raw | blame | history
pyramid/scaffolds/__init__.py 4 ●●●● patch | view | raw | blame | history
pyramid/scripts/pcreate.py 96 ●●●● patch | view | raw | blame | history
pyramid/tests/test_httpexceptions.py 13 ●●●● patch | view | raw | blame | history
pyramid/tests/test_scaffolds/test_init.py 5 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_pcreate.py 46 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -4,6 +4,12 @@
Features
--------
- pcreate will now ask for confirmation if invoked with
  an argument for a project name that already exists or
  is importable in the current environment.
  See https://github.com/Pylons/pyramid/issues/1357 and
  https://github.com/Pylons/pyramid/pull/1837
- Make it possible to subclass ``pyramid.request.Request`` and also use
  ``pyramid.request.Request.add_request.method``.  See
  https://github.com/Pylons/pyramid/issues/1529
CONTRIBUTORS.txt
@@ -248,3 +248,5 @@
- Donald Stufft, 2015/03/15
- Karen Dalton, 2015/06/01
- Igor Stroh, 2015/06/10
docs/narr/assets.rst
@@ -512,6 +512,93 @@
.. index::
   single: static assets view
CSS and JavaScript source and cache busting
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Often one needs to refer to images and other static assets inside CSS and
JavaScript files. If cache busting is active, the final static asset URL is
not available until the static assets have been assembled. These URLs cannot
be handwritten. Thus, when having static asset references in CSS and
JavaScript, one needs to perform one of the following tasks.
* Process the files by using a precompiler which rewrites URLs to their final
  cache busted form.
* Templatize JS and CSS, and call ``request.static_url()`` inside their
  template code.
* Pass static URL references to CSS and JavaScript via other means.
Below are some simple approaches for CSS and JS programming which consider
asset cache busting. These approaches do not require additional tools or
packages.
Relative cache busted URLs in CSS
+++++++++++++++++++++++++++++++++
Consider a CSS file ``/static/theme/css/site.css`` which contains the
following CSS code.
.. code-block:: css
    body {
        background: url(/static/theme/img/background.jpg);
    }
Any changes to ``background.jpg`` would not appear to the visitor because the
URL path is not cache busted as it is. Instead we would have to construct an
URL to the background image with the default ``PathSegmentCacheBuster`` cache
busting mechanism::
    https://site/static/1eeb262c717/theme/img/background.jpg
Every time the image is updated, the URL would need to be changed. It is not
practical to write this non-human readable URL into a CSS file.
However, the CSS file itself is cache busted and is located under the path for
static assets. This lets us use relative references in our CSS to cache bust
the image.
.. code-block:: css
    body {
        background: url(../img/background.jpg);
    }
The browser would interpret this as having the CSS file hash in URL::
    https://site/static/ab234b262c71/theme/css/../img/background.jpg
The downside of this approach is that if the background image changes, one
needs to bump the CSS file. The CSS file hash change signals the caches that
the relative URL to the image in the CSS has been changed. When updating CSS
and related image assets, updates usually happen hand in hand, so this does
not add extra effort to theming workflow.
Passing cache busted URLs to JavaScript
+++++++++++++++++++++++++++++++++++++++
For JavaScript, one can pass static asset URLs as function arguments or
globals. The globals can be generated in page template code, having access to
the ``request.static_url()`` function.
Below is a simple example of passing a cached busted image URL in the Jinja2
template language. Put the following code into the ``<head>`` section of the
relevant page.
.. code-block:: html
    <script>
        window.assets.backgroundImage =
            "{{ '/theme/img/background.jpg'|static_url() }}";
    </script>
Then in your main ``site.js`` file put the following code.
.. code-block:: javascript
    var image = new Image(window.assets.backgroundImage);
.. _advanced_static:
Advanced: Serving Static Assets Using a View Callable
docs/narr/viewconfig.rst
@@ -119,7 +119,7 @@
``renderer``
  Denotes the :term:`renderer` implementation which will be used to construct
  a :term:`response` from the associated view callable's return value.
  .. seealso:: See also :ref:`renderers_chapter`.
  This is either a single string term (e.g. ``json``) or a string implying a
@@ -1020,7 +1020,7 @@
   @view_config(http_cache=3600)
   def view(request):
       response = Response()
       if not 'should_cache' in request.params:
       if 'should_cache' not in request.params:
           response.cache_control.prevent_auto = True
       return response
docs/quick_tutorial/debugtoolbar.rst
@@ -89,24 +89,24 @@
Extra Credit
============
# Why don't we add ``pyramid_debugtoolbar`` to the list of
  ``install_requires`` dependencies in ``debugtoolbar/setup.py``?
#. Why don't we add ``pyramid_debugtoolbar`` to the list of
   ``install_requires`` dependencies in ``debugtoolbar/setup.py``?
# Introduce a bug into your application:  Change:
#. Introduce a bug into your application:  Change:
  .. code-block:: python
   .. code-block:: python
    def hello_world(request):
        return Response('<body><h1>Hello World!</h1></body>')
     def hello_world(request):
         return Response('<body><h1>Hello World!</h1></body>')
  to:
   to:
  .. code-block:: python
   .. code-block:: python
    def hello_world(request):
        return xResponse('<body><h1>Hello World!</h1></body>')
  Save, and visit http://localhost:6543/ again.  Notice the nice
  traceback display.  On the lowest line, click the "screen" icon to the
  right, and try typing the variable names ``request`` and ``Response``.
  What else can you discover?
   Save, and visit http://localhost:6543/ again.  Notice the nice
   traceback display.  On the lowest line, click the "screen" icon to the
   right, and try typing the variable names ``request`` and ``Response``.
   What else can you discover?
docs/quick_tutorial/ini.rst
@@ -131,6 +131,8 @@
#. The entry point in ``setup.py`` didn't mention ``__init__.py`` when
   it declared ``tutorial:main`` function. Why not?
#. What is the purpose of ``**settings``? What does the ``**`` signify?
.. seealso::
   :ref:`project_narr`,
   :ref:`scaffolding_chapter`,
@@ -138,7 +140,3 @@
   :ref:`environment_chapter`,
   :ref:`paste_chapter`
Extra Credit
============
#. What is the purpose of ``**settings``? What does the ``**`` signify?
docs/tutorials/wiki2/authorization.rst
@@ -5,12 +5,12 @@
====================
:app:`Pyramid` provides facilities for :term:`authentication` and
::term:`authorization`.  We'll make use of both features to provide security
:to our application.  Our application currently allows anyone with access to
:the server to view, edit, and add pages to our wiki.  We'll change that to
:allow only people who are members of a *group* named ``group:editors`` to add
:and edit wiki pages but we'll continue allowing anyone with access to the
:server to view pages.
:term:`authorization`.  We'll make use of both features to provide security
to our application.  Our application currently allows anyone with access to
the server to view, edit, and add pages to our wiki.  We'll change that to
allow only people who are members of a *group* named ``group:editors`` to add
and edit wiki pages but we'll continue allowing anyone with access to the
server to view pages.
We will also add a login page and a logout link on all the pages.  The login
page will be shown when a user is denied access to any of the views that
pyramid/httpexceptions.py
@@ -562,10 +562,7 @@
    a bug.  A server-side traceback is not warranted.  Unless specialized,
    this is a '400 Bad Request'
    """
    code = 400
    title = 'Bad Request'
    explanation = ('The server could not comply with the request since '
                   'it is either malformed or otherwise incorrect.')
    pass
class HTTPBadRequest(HTTPClientError):
    """
@@ -576,7 +573,10 @@
    code: 400, title: Bad Request
    """
    pass
    code = 400
    title = 'Bad Request'
    explanation = ('The server could not comply with the request since '
                   'it is either malformed or otherwise incorrect.')
class HTTPUnauthorized(HTTPClientError):
    """
@@ -988,14 +988,14 @@
    This is an error condition in which the server is presumed to be
    in-error.  Unless specialized, this is a '500 Internal Server Error'.
    """
    pass
class HTTPInternalServerError(HTTPServerError):
    code = 500
    title = 'Internal Server Error'
    explanation = (
      'The server has either erred or is incapable of performing '
      'the requested operation.')
class HTTPInternalServerError(HTTPServerError):
    pass
class HTTPNotImplemented(HTTPServerError):
    """
pyramid/scaffolds/__init__.py
@@ -18,10 +18,6 @@
        misnamings (such as naming a package "site" or naming a package
        logger "root".
        """
        if vars['package'] == 'site':
            raise ValueError('Sorry, you may not name your package "site". '
                             'The package name "site" has a special meaning in '
                             'Python.  Please name it anything except "site".')
        vars['random_string'] = native_(binascii.hexlify(os.urandom(20)))
        package_logger = vars['package']
        if package_logger == 'root':
pyramid/scripts/pcreate.py
@@ -8,12 +8,17 @@
import pkg_resources
import re
import sys
from pyramid.compat import input_
_bad_chars_re = re.compile('[^a-zA-Z0-9_]')
def main(argv=sys.argv, quiet=False):
    command = PCreateCommand(argv, quiet)
    return command.run()
    try:
        return command.run()
    except KeyboardInterrupt: # pragma: no cover
        return 1
class PCreateCommand(object):
    verbosity = 1 # required
@@ -52,6 +57,13 @@
                      dest='interactive',
                      action='store_true',
                      help='When a file would be overwritten, interrogate')
    parser.add_option('--ignore-conflicting-name',
                      dest='force_bad_name',
                      action='store_true',
                      default=False,
                      help='Do create a project even if the chosen name '
                           'is the name of an already existing / importable '
                           'package.')
    pyramid_dist = pkg_resources.get_distribution("pyramid")
@@ -69,25 +81,19 @@
                self.out('')
                self.show_scaffolds()
            return 2
        if not self.options.scaffold_name:
            self.out('You must provide at least one scaffold name: -s <scaffold name>')
            self.out('')
            self.show_scaffolds()
        if not self.validate_input():
            return 2
        if not self.args:
            self.out('You must provide a project name')
            return 2
        available = [x.name for x in self.scaffolds]
        diff = set(self.options.scaffold_name).difference(available)
        if diff:
            self.out('Unavailable scaffolds: %s' % list(diff))
            return 2
        return self.render_scaffolds()
    def render_scaffolds(self):
        options = self.options
        args = self.args
        output_dir = os.path.abspath(os.path.normpath(args[0]))
    @property
    def output_path(self):
        return os.path.abspath(os.path.normpath(self.args[0]))
    @property
    def project_vars(self):
        output_dir = self.output_path
        project_name = os.path.basename(os.path.split(output_dir)[1])
        pkg_name = _bad_chars_re.sub(
            '', project_name.lower().replace('-', '_'))
@@ -111,17 +117,22 @@
            else:
                pyramid_docs_branch = 'latest'
        vars = {
        return {
            'project': project_name,
            'package': pkg_name,
            'egg': egg_name,
            'pyramid_version': pyramid_version,
            'pyramid_docs_branch': pyramid_docs_branch,
            }
        for scaffold_name in options.scaffold_name:
        }
    def render_scaffolds(self):
        props = self.project_vars
        output_dir = self.output_path
        for scaffold_name in self.options.scaffold_name:
            for scaffold in self.scaffolds:
                if scaffold.name == scaffold_name:
                    scaffold.run(self, output_dir, vars)
                    scaffold.run(self, output_dir, props)
        return 0
    def show_scaffolds(self):
@@ -154,5 +165,48 @@
        if not self.quiet:
            print(msg)
    def validate_input(self):
        if not self.options.scaffold_name:
            self.out('You must provide at least one scaffold name: -s <scaffold name>')
            self.out('')
            self.show_scaffolds()
            return False
        if not self.args:
            self.out('You must provide a project name')
            return False
        available = [x.name for x in self.scaffolds]
        diff = set(self.options.scaffold_name).difference(available)
        if diff:
            self.out('Unavailable scaffolds: %s' % ", ".join(sorted(diff)))
            return False
        pkg_name = self.project_vars['package']
        if pkg_name == 'site' and not self.options.force_bad_name:
            self.out('The package name "site" has a special meaning in '
                     'Python. Are you sure you want to use it as your '
                     'project\'s name?')
            return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name))
        # check if pkg_name can be imported (i.e. already exists in current
        # $PYTHON_PATH, if so - let the user confirm
        pkg_exists = True
        try:
            __import__(pkg_name, globals(), locals(), [], 0) # use absolute imports
        except ImportError as error:
            pkg_exists = False
        if not pkg_exists:
            return True
        if self.options.force_bad_name:
            return True
        self.out('A package named "{0}" already exists, are you sure you want '
                 'to use it as your project\'s name?'.format(pkg_name))
        return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name))
    def confirm_bad_name(self, prompt): # pragma: no cover
        answer = input_('{0} [y|N]: '.format(prompt))
        return answer.strip().lower() == 'y'
if __name__ == '__main__': # pragma: no cover
    sys.exit(main() or 0)
pyramid/tests/test_httpexceptions.py
@@ -10,13 +10,22 @@
        from pyramid.httpexceptions import exception_response
        return exception_response(*arg, **kw)
    def test_status_400(self):
        from pyramid.httpexceptions import HTTPBadRequest
        self.assertTrue(isinstance(self._callFUT(400), HTTPBadRequest))
    def test_status_404(self):
        from pyramid.httpexceptions import HTTPNotFound
        self.assertEqual(self._callFUT(404).__class__, HTTPNotFound)
        self.assertTrue(isinstance(self._callFUT(404), HTTPNotFound))
    def test_status_500(self):
        from pyramid.httpexceptions import HTTPInternalServerError
        self.assertTrue(isinstance(self._callFUT(500),
                        HTTPInternalServerError))
    def test_status_201(self):
        from pyramid.httpexceptions import HTTPCreated
        self.assertEqual(self._callFUT(201).__class__, HTTPCreated)
        self.assertTrue(isinstance(self._callFUT(201), HTTPCreated))
    def test_extra_kw(self):
        resp = self._callFUT(404,  headers=[('abc', 'def')])
pyramid/tests/test_scaffolds/test_init.py
@@ -12,11 +12,6 @@
        self.assertTrue(vars['random_string'])
        self.assertEqual(vars['package_logger'], 'one')
    def test_pre_site(self):
        inst = self._makeOne()
        vars = {'package':'site'}
        self.assertRaises(ValueError, inst.pre, 'command', 'output dir', vars)
    def test_pre_root(self):
        inst = self._makeOne()
        vars = {'package':'root'}
pyramid/tests/test_scripts/test_pcreate.py
@@ -1,5 +1,6 @@
import unittest
class TestPCreateCommand(unittest.TestCase):
    def setUp(self):
        from pyramid.compat import NativeIO
@@ -15,7 +16,8 @@
    def _makeOne(self, *args, **kw):
        effargs = ['pcreate']
        effargs.extend(args)
        cmd = self._getTargetClass()(effargs, **kw)
        tgt_class = kw.pop('target_class', self._getTargetClass())
        cmd = tgt_class(effargs, **kw)
        cmd.out = self.out
        return cmd
@@ -220,6 +222,48 @@
             'pyramid_version': '0.10.1dev',
             'pyramid_docs_branch': 'master'})
    def test_confirm_override_conflicting_name(self):
        from pyramid.scripts.pcreate import PCreateCommand
        class YahInputPCreateCommand(PCreateCommand):
            def confirm_bad_name(self, pkg_name):
                return True
        cmd = self._makeOne('-s', 'dummy', 'Unittest', target_class=YahInputPCreateCommand)
        scaffold = DummyScaffold('dummy')
        cmd.scaffolds = [scaffold]
        cmd.pyramid_dist = DummyDist("0.10.1dev")
        result = cmd.run()
        self.assertEqual(result, 0)
        self.assertEqual(
            scaffold.vars,
            {'project': 'Unittest', 'egg': 'Unittest', 'package': 'unittest',
             'pyramid_version': '0.10.1dev',
             'pyramid_docs_branch': 'master'})
    def test_force_override_conflicting_name(self):
        cmd = self._makeOne('-s', 'dummy', 'Unittest', '--ignore-conflicting-name')
        scaffold = DummyScaffold('dummy')
        cmd.scaffolds = [scaffold]
        cmd.pyramid_dist = DummyDist("0.10.1dev")
        result = cmd.run()
        self.assertEqual(result, 0)
        self.assertEqual(
            scaffold.vars,
            {'project': 'Unittest', 'egg': 'Unittest', 'package': 'unittest',
             'pyramid_version': '0.10.1dev',
             'pyramid_docs_branch': 'master'})
    def test_force_override_site_name(self):
        from pyramid.scripts.pcreate import PCreateCommand
        class NayInputPCreateCommand(PCreateCommand):
            def confirm_bad_name(self, pkg_name):
                return False
        cmd = self._makeOne('-s', 'dummy', 'Site', target_class=NayInputPCreateCommand)
        scaffold = DummyScaffold('dummy')
        cmd.scaffolds = [scaffold]
        cmd.pyramid_dist = DummyDist("0.10.1dev")
        result = cmd.run()
        self.assertEqual(result, 2)
class Test_main(unittest.TestCase):
    def _callFUT(self, argv):