From c895f874184371ff5798748b1c7234128ca4b25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20G=C3=B3recki?= Date: Fri, 1 Jul 2016 09:42:30 +0100 Subject: ref #2659 public HTTP Basic credentials extraction --- pyramid/authentication.py | 77 ++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 9bf1de62e..95d6a710d 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -1128,7 +1128,7 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): def unauthenticated_userid(self, request): """ The userid parsed from the ``Authorization`` request header.""" - credentials = self._get_credentials(request) + credentials = extract_http_basic_credentials(request) if credentials: return credentials[0] @@ -1145,42 +1145,14 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): return [('WWW-Authenticate', 'Basic realm="%s"' % self.realm)] def callback(self, username, request): - # Username arg is ignored. Unfortunately _get_credentials winds up + # Username arg is ignored. Unfortunately extract_http_basic_credentials winds up # getting called twice when authenticated_userid is called. Avoiding # that, however, winds up duplicating logic from the superclass. - credentials = self._get_credentials(request) + credentials = extract_http_basic_credentials(request) if credentials: username, password = credentials return self.check(username, password, request) - def _get_credentials(self, request): - authorization = request.headers.get('Authorization') - if not authorization: - return None - try: - authmeth, auth = authorization.split(' ', 1) - except ValueError: # not enough values to unpack - return None - if authmeth.lower() != 'basic': - return None - - try: - authbytes = b64decode(auth.strip()) - except (TypeError, binascii.Error): # can't decode - return None - - # try utf-8 first, then latin-1; see discussion in - # https://github.com/Pylons/pyramid/issues/898 - try: - auth = authbytes.decode('utf-8') - except UnicodeDecodeError: - auth = authbytes.decode('latin-1') - - try: - username, password = auth.split(':', 1) - except ValueError: # not enough values to unpack - return None - return username, password class _SimpleSerializer(object): def loads(self, bstruct): @@ -1188,3 +1160,46 @@ class _SimpleSerializer(object): def dumps(self, appstruct): return bytes_(appstruct) + + +def extract_http_basic_credentials(request): + """ A helper function for extraction of HTTP Basic credentials + from a given `request`. + + ``request`` + The request object + """ + try: + # First try authorization extraction logic from WebOb + try: + authmeth, auth = request.authorization + except AttributeError: # Probably a DummyRequest + authorization = request.headers.get('Authorization') + if not authorization: + return None + + authmeth, auth = authorization.split(' ', 1) + except (ValueError, TypeError): + # not enough values to unpack or None is not iterable + return None + + if authmeth.lower() != 'basic': + return None + + try: + authbytes = b64decode(auth.strip()) + except (TypeError, binascii.Error): # can't decode + return None + + # try utf-8 first, then latin-1; see discussion in + # https://github.com/Pylons/pyramid/issues/898 + try: + auth = authbytes.decode('utf-8') + except UnicodeDecodeError: + auth = authbytes.decode('latin-1') + + try: + username, password = auth.split(':', 1) + except ValueError: # not enough values to unpack + return None + return username, password -- cgit v1.2.3 From 108121ee8a08837c39379cdd0e2e9c2b5b3712e8 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 2 Jul 2016 01:34:25 -0700 Subject: Update Windows installation instructions and related bits. - Use proper Windows commands, drives, and paths - Use doscon for Windows console lexer --- docs/conventions.rst | 15 ++++--------- docs/narr/firstapp.rst | 6 +++--- docs/narr/i18n.rst | 2 +- docs/narr/install.rst | 42 +++++++++++++++++++++++++++--------- docs/narr/project.rst | 16 +++++++------- docs/narr/upgrading.rst | 6 +++--- docs/quick_tour.rst | 4 ++-- docs/quick_tutorial/requirements.rst | 2 +- 8 files changed, 54 insertions(+), 39 deletions(-) diff --git a/docs/conventions.rst b/docs/conventions.rst index 43853882c..de041da04 100644 --- a/docs/conventions.rst +++ b/docs/conventions.rst @@ -57,23 +57,16 @@ character, e.g.: $ $VENV/bin/py.test -q -(See :term:`venv` for the meaning of ``$VENV``) +See :term:`venv` for the meaning of ``$VENV``. -Example blocks representing Windows ``cmd.exe`` commands are prefixed with a -drive letter and/or a directory name, e.g.: +Example blocks representing Windows commands are prefixed with a drive letter +with an optional directory name, e.g.: .. code-block:: doscon c:\examples> %VENV%\Scripts\py.test -q -(See :term:`venv` for the meaning of ``%VENV%``) - -Sometimes, when it's unknown which directory is current, Windows ``cmd.exe`` -example block commands are prefixed only with a ``>`` character, e.g.: - - .. code-block:: doscon - - > %VENV%\Scripts\py.test -q +See :term:`venv` for the meaning of ``%VENV%``. When a command that should be typed on one line is too long to fit on a page, the backslash ``\`` is used to indicate that the following printed line should diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index a8491eabd..ad05976c0 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -27,15 +27,15 @@ installed, an HTTP server is started on TCP port 8080. On UNIX: -.. code-block:: text +.. code-block:: bash $ $VENV/bin/python helloworld.py On Windows: -.. code-block:: text +.. code-block:: doscon - C:\> %VENV%\Scripts\python.exe helloworld.py + c:\> %VENV%\Scripts\python helloworld.py This command will not return and nothing will be printed to the console. When port 8080 is visited by a browser on the URL ``/hello/world``, the server will diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 131832aae..3549b53a5 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -294,7 +294,7 @@ Lingua like so: .. code-block:: doscon - C> %VENV%\Scripts\pip install lingua + c:\> %VENV%\Scripts\pip install lingua .. index:: diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 7d96f4074..c59ced2a5 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -92,8 +92,24 @@ If your Windows system doesn't have a Python interpreter, you'll need to install it by downloading a Python 3.x-series interpreter executable from `python.org's download section `_ (the files labeled "Windows Installer"). Once you've downloaded it, double click on the -executable and accept the defaults during the installation process. You may -also need to download and install the Python for Windows extensions. +executable, and select appropriate options during the installation process. To +standardize this documentation, we used the GUI installer and selected the +following options: + +- Screen 1: Install Python 3.x.x (32- or 64-bit) + - Check "Install launcher for all users (recommended)" + - Check "Add Python 3.x to PATH" + - Click "Customize installation" +- Screen 2: Optional Features + - Check all options + - Click "Next" +- Screen 3: Advanced Options + - Check all options + - Customize install location: "C:\\Python3x", where "x" is the minor + version of Python + - Click "Next" + +You might also need to download and install the Python for Windows extensions. .. seealso:: See the official Python documentation :ref:`Using Python on Windows ` for full details. @@ -104,14 +120,19 @@ also need to download and install the Python for Windows extensions. directions. Make sure you get the proper 32- or 64-bit build and Python version. +.. seealso:: `Python launcher for Windows + `_ provides a command + ``py`` that allows users to run any installed version of Python. + .. warning:: - After you install Python on Windows, you may need to add the ``C:\Python3x`` - directory to your environment's ``Path``, where ``x`` is the minor version - of installed Python, in order to make it possible to invoke Python from a - command prompt by typing ``python``. To do so, right click ``My Computer``, - select ``Properties`` --> ``Advanced Tab`` --> ``Environment Variables`` and - add that directory to the end of the ``Path`` environment variable. + After you install Python on Windows, you might need to add the + ``c:\Python3x`` directory to your environment's ``Path``, where ``x`` is the + minor version of installed Python, in order to make it possible to invoke + Python from a command prompt by typing ``python``. To do so, right click + ``My Computer``, select ``Properties`` --> ``Advanced Tab`` --> + ``Environment Variables``, and add that directory to the end of the ``Path`` + environment variable. .. seealso:: See `Configuring Python (on Windows) `_ for @@ -190,7 +211,8 @@ After installing Python as described previously in c:\> set VENV=c:\env # replace "x" with your minor version of Python 3 - c:\> c:\Python3x\Scripts\python3 -m venv %VENV% + c:\> c:\Python3x\python -m venv %VENV% + c:\> cd %VENV% You can either follow the use of the environment variable ``%VENV%``, or replace it with the root directory of the virtual environment. If you choose @@ -204,7 +226,7 @@ After installing Python as described previously in .. parsed-literal:: - c:\\env> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ " + c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ " What Gets Installed diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 1ce12a938..812404b76 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -87,9 +87,9 @@ On UNIX: Or on Windows: -.. code-block:: text +.. code-block:: doscon - > %VENV%\Scripts\pcreate -s starter MyProject + c:\> %VENV%\Scripts\pcreate -s starter MyProject As a result of invoking the ``pcreate`` command, a directory named ``MyProject`` is created. That directory is a :term:`project` directory. The @@ -161,8 +161,8 @@ Or on Windows: .. code-block:: doscon - > cd MyProject - > %VENV%\Scripts\pip install -e . + c:\> cd MyProject + c:\> %VENV%\Scripts\pip install -e . Elided output from a run of this command on UNIX is shown below: @@ -199,7 +199,7 @@ On Windows: .. code-block:: doscon - > %VENV%\Scripts\pip install -e ".[testing]" + c:\> %VENV%\Scripts\pip install -e ".[testing]" Once the testing requirements are installed, then you can run the tests using the ``py.test`` command that was just installed in the ``bin`` directory of @@ -215,7 +215,7 @@ On Windows: .. code-block:: doscon - > %VENV%\Scripts\py.test -q + c:\> %VENV%\Scripts\py.test -q Here's sample output from a test run on UNIX: @@ -266,7 +266,7 @@ path to the module on which we want to run tests and coverage. Running the Project Application ------------------------------- -.. seealso:: See also the output of :ref:`pserve --help `. +.. seealso:: See also the output of :ref:`pserve --help `. Once a project is installed for development, you can run the application it represents using the ``pserve`` command against the generated configuration @@ -282,7 +282,7 @@ On Windows: .. code-block:: text - > %VENV%\Scripts\pserve development.ini + c:\> %VENV%\Scripts\pserve development.ini Here's sample output from a run of ``pserve`` on UNIX: diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst index 21b696775..4e434c3c6 100644 --- a/docs/narr/upgrading.rst +++ b/docs/narr/upgrading.rst @@ -205,10 +205,10 @@ On UNIX, you can do that via: On Windows, you need to issue two commands: -.. code-block:: bash +.. code-block:: doscon - C:\> set PYTHONWARNINGS=default - C:\> Scripts/pserve.exe development.ini + c:\> set PYTHONWARNINGS=default + c:\> Scripts/pserve.exe development.ini At this point, it's ensured that deprecation warnings will be printed to the console whenever a codepath is hit that generates one. You can then click diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index dde91b495..fb957be1c 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -44,9 +44,9 @@ For Windows: .. parsed-literal:: # set an environment variable to where you want your virtual environment - c:\> set VENV=c:\env + c:\\> set VENV=c:\\env # create the virtual environment - c:\\> c:\\Python35\\python3 -m venv %VENV% + c:\\> %VENV%\\Scripts\\python -m venv %VENV% # install pyramid c:\\> %VENV%\\Scripts\\pip install pyramid # or for a specific released version diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index 62dd570fc..958347a90 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -156,7 +156,7 @@ environment variable. .. code-block:: doscon # Windows - c:\> c:\Python35\python3 -m venv %VENV% + c:\> c:\Python35\python -m venv %VENV% .. seealso:: See also Python 3's :mod:`venv module ` and Python 2's `virtualenv `_ package. -- cgit v1.2.3 From 446065219c5e43a8116f5b06142de0c75c779618 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 2 Jul 2016 01:39:02 -0700 Subject: Update Windows installation instructions and related bits. - Use proper Windows commands and paths - Use doscon for Windows console lexer --- CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 4fdd7bf2f..36a8d17ac 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -25,3 +25,5 @@ Deprecations Documentation Changes --------------------- +- Updated Windows installation instructions and related bits. + See: https://github.com/Pylons/pyramid/issues/2661 \ No newline at end of file -- cgit v1.2.3 From fa257dd74588c9cc80cc3a6b4158ff2c627eacd5 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 2 Jul 2016 01:55:29 -0700 Subject: fix overly aggressive refactor --- docs/narr/project.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 812404b76..71bd176f6 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -266,7 +266,7 @@ path to the module on which we want to run tests and coverage. Running the Project Application ------------------------------- -.. seealso:: See also the output of :ref:`pserve --help `. +.. seealso:: See also the output of :ref:`pserve --help `. Once a project is installed for development, you can run the application it represents using the ``pserve`` command against the generated configuration -- cgit v1.2.3 From 65687fee7e933593eda844bfb01aea5a968271aa Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 2 Jul 2016 05:03:39 -0700 Subject: proper case heading --- docs/quick_tutorial/debugtoolbar.rst | 2 +- docs/quick_tutorial/hello_world.rst | 2 +- docs/quick_tutorial/ini.rst | 2 +- docs/quick_tutorial/unit_testing.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst index aaf904390..b02363d40 100644 --- a/docs/quick_tutorial/debugtoolbar.rst +++ b/docs/quick_tutorial/debugtoolbar.rst @@ -90,7 +90,7 @@ temporarily. .. seealso:: See also :ref:`pyramid_debugtoolbar `. -Extra Credit +Extra credit ============ #. Why don't we add ``pyramid_debugtoolbar`` to the list of diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst index 4e35da7bb..56dccde58 100644 --- a/docs/quick_tutorial/hello_world.rst +++ b/docs/quick_tutorial/hello_world.rst @@ -88,7 +88,7 @@ Pyramid development. Building an application from loosely-coupled parts via revisit regularly in this *Quick Tutorial*. -Extra Credit +Extra credit ============ #. Why do we do this: diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst index fba5ce29e..9a65d66d1 100644 --- a/docs/quick_tutorial/ini.rst +++ b/docs/quick_tutorial/ini.rst @@ -120,7 +120,7 @@ filesystem for changes to relevant code (Python files, the INI file, etc.) and, when something changes, restart the application. Very handy during development. -Extra Credit +Extra credit ============ #. If you don't like configuration and/or ``.ini`` files, could you do this diff --git a/docs/quick_tutorial/unit_testing.rst b/docs/quick_tutorial/unit_testing.rst index 56fd2b297..7c85d5289 100644 --- a/docs/quick_tutorial/unit_testing.rst +++ b/docs/quick_tutorial/unit_testing.rst @@ -92,7 +92,7 @@ necessary when your test needs to make use of the ``config`` object (it's a Configurator) to add stuff to the configuration state before calling the view. -Extra Credit +Extra credit ============ #. Change the test to assert that the response status code should be ``404`` -- cgit v1.2.3 From 362a5812733ccc97336cea9ba5b1deebc3120ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20G=C3=B3recki?= Date: Sat, 2 Jul 2016 15:16:45 +0100 Subject: Remove WebOb related logic --- pyramid/authentication.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 95d6a710d..c6fd8f466 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -1169,18 +1169,13 @@ def extract_http_basic_credentials(request): ``request`` The request object """ + authorization = request.headers.get('Authorization') + if not authorization: + return None + try: - # First try authorization extraction logic from WebOb - try: - authmeth, auth = request.authorization - except AttributeError: # Probably a DummyRequest - authorization = request.headers.get('Authorization') - if not authorization: - return None - - authmeth, auth = authorization.split(' ', 1) - except (ValueError, TypeError): - # not enough values to unpack or None is not iterable + authmeth, auth = authorization.split(' ', 1) + except ValueError: # not enough values to unpack return None if authmeth.lower() != 'basic': -- cgit v1.2.3 From 2612de2f38094f3c04ebf991f1ca84b02bf5ae3f Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 3 Jul 2016 17:10:36 -0700 Subject: Show testsetup code in rendered docs. For `testsetup`, Sphinx does not show code in the output: http://www.sphinx-doc.org/en/stable/ext/doctest.html#directive-testsetup This commit fixes that issue, although duplicates code. I'll submit a feature request to https://github.com/sphinx-doc/sphinx/ Fixes #2670 --- pyramid/decorator.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyramid/decorator.py b/pyramid/decorator.py index ea518bfcb..e3a8c707f 100644 --- a/pyramid/decorator.py +++ b/pyramid/decorator.py @@ -8,6 +8,16 @@ class reify(object): replacing the function it decorates with an instance variable. It is, in Python parlance, a non-data descriptor. An example: + .. code-block:: python + + from pyramid.decorator import reify + + class Foo(object): + @reify + def jammy(self): + print('jammy called') + return 1 + .. testsetup:: from pyramid.decorator import reify -- cgit v1.2.3 From 2061971ccddff626fc435b16f0afabefee9a260e Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 4 Jul 2016 14:04:34 -0700 Subject: Show testsetup code in rendered docs the right way. Follow up to @tseaver comment at https://github.com/Pylons/pyramid/pull/2672#issuecomment-230310939 --- pyramid/decorator.py | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/pyramid/decorator.py b/pyramid/decorator.py index e3a8c707f..065a3feed 100644 --- a/pyramid/decorator.py +++ b/pyramid/decorator.py @@ -6,31 +6,18 @@ class reify(object): Python ``@property`` decorator, but it puts the result of the method it decorates into the instance dict after the first call, effectively replacing the function it decorates with an instance variable. It is, in - Python parlance, a non-data descriptor. An example: + Python parlance, a non-data descriptor. The following is an example and + its usage: - .. code-block:: python - - from pyramid.decorator import reify - - class Foo(object): - @reify - def jammy(self): - print('jammy called') - return 1 - - .. testsetup:: - - from pyramid.decorator import reify - - class Foo(object): - @reify - def jammy(self): - print('jammy called') - return 1 + .. doctest:: - And usage of Foo: + >>> from pyramid.decorator import reify - .. doctest:: + >>> class Foo(object): + ... @reify + ... def jammy(self): + ... print('jammy called') + ... return 1 >>> f = Foo() >>> v = f.jammy -- cgit v1.2.3 From 0d37a99d86bf57f5254448b75665499682b9a613 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 5 Jul 2016 03:07:05 -0700 Subject: Add missing tests to quick_tutorial/forms. - Closes #2673 --- docs/quick_tutorial/forms.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/quick_tutorial/forms.rst b/docs/quick_tutorial/forms.rst index 66e77491d..1f421ee67 100644 --- a/docs/quick_tutorial/forms.rst +++ b/docs/quick_tutorial/forms.rst @@ -74,13 +74,18 @@ Steps :language: html :linenos: -#. Finally, a template at ``forms/tutorial/wikipage_view.pt`` for viewing a - wiki page: +#. Add a template at ``forms/tutorial/wikipage_view.pt`` for viewing a wiki + page: .. literalinclude:: forms/tutorial/wikipage_view.pt :language: html :linenos: +#. Our tests in ``forms/tutorial/tests.py`` don't run, so let's modify them: + + .. literalinclude:: forms/tutorial/tests.py + :linenos: + #. Run the tests: .. code-block:: bash -- cgit v1.2.3 From 84c1b61760e08e4ff68578ac3066954d3b01965e Mon Sep 17 00:00:00 2001 From: Maksym Shalenyi Date: Wed, 6 Jul 2016 15:44:38 +0300 Subject: updated old link to Alembic naming convention All docs now live on alembic.zzzcomputing.com, but for some reason link http://alembic.readthedocs.org/en/latest/naming.html redirects to wrong domain. --- pyramid/scaffolds/alchemy/+package+/models/meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/scaffolds/alchemy/+package+/models/meta.py b/pyramid/scaffolds/alchemy/+package+/models/meta.py index fc3e8f1dd..0682247b5 100644 --- a/pyramid/scaffolds/alchemy/+package+/models/meta.py +++ b/pyramid/scaffolds/alchemy/+package+/models/meta.py @@ -3,7 +3,7 @@ from sqlalchemy.schema import MetaData # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more -# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html NAMING_CONVENTION = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", -- cgit v1.2.3 From 9c84d8fd48802c86b342f977e634fae0871b4d90 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Wed, 6 Jul 2016 09:50:45 -0700 Subject: Update links for Alembix --- docs/quick_tour/sqla_demo/sqla_demo/models/meta.py | 2 +- docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py | 2 +- docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py | 2 +- docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py | 2 +- docs/tutorials/wiki2/src/installation/tutorial/models/meta.py | 2 +- docs/tutorials/wiki2/src/models/tutorial/models/meta.py | 2 +- docs/tutorials/wiki2/src/tests/tutorial/models/meta.py | 2 +- docs/tutorials/wiki2/src/views/tutorial/models/meta.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py b/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py index 80ececd8c..03c50ae93 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py @@ -6,7 +6,7 @@ import zope.sqlalchemy # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more -# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html NAMING_CONVENTION = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py index fc3e8f1dd..0682247b5 100644 --- a/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py @@ -3,7 +3,7 @@ from sqlalchemy.schema import MetaData # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more -# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html NAMING_CONVENTION = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py index fc3e8f1dd..0682247b5 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py @@ -3,7 +3,7 @@ from sqlalchemy.schema import MetaData # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more -# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html NAMING_CONVENTION = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py index fc3e8f1dd..0682247b5 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py @@ -3,7 +3,7 @@ from sqlalchemy.schema import MetaData # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more -# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html NAMING_CONVENTION = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py b/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py index fc3e8f1dd..0682247b5 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py @@ -3,7 +3,7 @@ from sqlalchemy.schema import MetaData # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more -# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html NAMING_CONVENTION = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py index fc3e8f1dd..0682247b5 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py @@ -3,7 +3,7 @@ from sqlalchemy.schema import MetaData # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more -# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html NAMING_CONVENTION = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py b/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py index fc3e8f1dd..0682247b5 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py @@ -3,7 +3,7 @@ from sqlalchemy.schema import MetaData # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more -# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html NAMING_CONVENTION = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/meta.py b/docs/tutorials/wiki2/src/views/tutorial/models/meta.py index fc3e8f1dd..0682247b5 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models/meta.py @@ -3,7 +3,7 @@ from sqlalchemy.schema import MetaData # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more -# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html NAMING_CONVENTION = { "ix": 'ix_%(column_0_label)s', "uq": "uq_%(table_name)s_%(column_0_name)s", -- cgit v1.2.3 From 1bdb556e2122204e84429e9b1001d10367203a6c Mon Sep 17 00:00:00 2001 From: John Anderson Date: Wed, 6 Jul 2016 13:40:55 -0400 Subject: This shows the correct view when a class and `attr` is involved. --- pyramid/scripts/proutes.py | 11 +++++++++-- pyramid/tests/test_scripts/dummy.py | 3 +++ pyramid/tests/test_scripts/test_proutes.py | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index a389c303c..19d91cc72 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -184,8 +184,15 @@ def get_route_data(route, registry): request_method = view.get('request_methods') if request_method is not None: - view_callable = view['callable'] - view_module = _get_view_module(view_callable) + if view.get('attr') is not None: + view_callable = getattr(view['callable'], view['attr']) + view_module = '%s.%s' % ( + _get_view_module(view['callable']), + view['attr'] + ) + else: + view_callable = view['callable'] + view_module = _get_view_module(view_callable) if view_module not in view_request_methods: view_request_methods[view_module] = [] diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index a872e197c..4bfd5d46c 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -70,6 +70,9 @@ class DummyView(object): def __init__(self, **attrs): self.__request_attrs__ = attrs + def view(context, request): + return 'view1' + from zope.interface import implementer from pyramid.interfaces import IMultiView @implementer(IMultiView) diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 876572b01..aeaa57060 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -200,6 +200,33 @@ class TestPRoutesCommand(unittest.TestCase): 'pyramid.tests.test_scripts.test_proutes.view'] ) + def test_class_view(self): + from pyramid.renderers import null_renderer as nr + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=dummy.DummyView, + attr='view', + renderer=nr, + request_method='POST' + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + 'pyramid.tests.test_scripts.dummy.DummyView.view', 'POST' + ] + self.assertEqual(compare_to, expected) + def test_single_route_one_view_registered_with_factory(self): from zope.interface import Interface from pyramid.interfaces import IRouteRequest -- cgit v1.2.3 From d9dc88d3cdf8b92941d09af32f0342be58c263cb Mon Sep 17 00:00:00 2001 From: John Anderson Date: Wed, 6 Jul 2016 14:08:51 -0400 Subject: make coverage 100% --- pyramid/tests/test_scripts/dummy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index 4bfd5d46c..f3aa20e7c 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -70,8 +70,7 @@ class DummyView(object): def __init__(self, **attrs): self.__request_attrs__ = attrs - def view(context, request): - return 'view1' + def view(context, request): pass from zope.interface import implementer from pyramid.interfaces import IMultiView -- cgit v1.2.3 From 2b8c7d5e418b2d5df0e05508ae9c17168b30f28d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2016 22:05:34 -0500 Subject: link to hacking and contributing --- README.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 35c335d9c..6ef75e899 100644 --- a/README.rst +++ b/README.rst @@ -50,9 +50,10 @@ for documentation, reporting bugs, and getting support. Developing and Contributing --------------------------- -See ``HACKING.txt`` and ``contributing.md`` for guidelines for running tests, -adding features, coding style, and updating documentation when developing in or -contributing to Pyramid. +See `HACKING.txt `_ and +`contributing.md `_ +for guidelines on running tests, adding features, coding style, and updating +documentation when developing in or contributing to Pyramid. License ------- -- cgit v1.2.3 From d0b37171ec7d63c973a50796263d8d96a58b4470 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 8 Jul 2016 00:50:18 -0700 Subject: Add change note for #2687 --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 36a8d17ac..4c35b8bb2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -19,6 +19,10 @@ Features Bug Fixes --------- +- Fixed bug in `pcreate` such that it now shows the correct view when a class + and `attr` is involved. + See: https://github.com/Pylons/pyramid/pull/2687 + Deprecations ------------ -- cgit v1.2.3 From 674f2b312481a970267ffd5168c707e7376f994b Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 12 Jul 2016 09:26:46 -0700 Subject: Correct change note --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4c35b8bb2..3e659ee9a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -19,7 +19,7 @@ Features Bug Fixes --------- -- Fixed bug in `pcreate` such that it now shows the correct view when a class +- Fixed bug in `proutes` such that it now shows the correct view when a class and `attr` is involved. See: https://github.com/Pylons/pyramid/pull/2687 -- cgit v1.2.3 From 744bf0565a15a40f5c04cc8b0c1fe84a2ca489da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20G=C3=B3recki?= Date: Fri, 15 Jul 2016 17:15:51 +0100 Subject: Add myself to contributors list & PEP8 --- CONTRIBUTORS.txt | 3 +++ pyramid/authentication.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 4edf1b4e9..492c2fac2 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -256,3 +256,6 @@ Contributors - Amos Latteier, 2015/10/22 - Rami Chousein, 2015/10/28 + +- Dariusz Gorecki, 2016/07/15 + diff --git a/pyramid/authentication.py b/pyramid/authentication.py index c6fd8f466..2a54be34d 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -1145,9 +1145,10 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): return [('WWW-Authenticate', 'Basic realm="%s"' % self.realm)] def callback(self, username, request): - # Username arg is ignored. Unfortunately extract_http_basic_credentials winds up - # getting called twice when authenticated_userid is called. Avoiding - # that, however, winds up duplicating logic from the superclass. + # Username arg is ignored. Unfortunately + # extract_http_basic_credentials winds up getting called twice when + # authenticated_userid is called. Avoiding that, however, + # winds up duplicating logic from the superclass. credentials = extract_http_basic_credentials(request) if credentials: username, password = credentials -- cgit v1.2.3 From fb9c227408d77551edf7dc876670b69df488175c Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 16 Jul 2016 10:41:54 -0700 Subject: Fix link to zope.component docs --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 518f7e784..c3a7170fc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -78,7 +78,7 @@ intersphinx_mapping = { 'webtest': ('http://webtest.pythonpaste.org/en/latest', None), 'who': ('http://repozewho.readthedocs.org/en/latest', None), 'zcml': ('http://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None), - 'zcomponent': ('http://docs.zope.org/zope.component', None), + 'zcomponent': ('http://zopecomponent.readthedocs.io/en/stable/', None), } -- cgit v1.2.3 From ba71cf32d543257cf51d1f00104f4a89ca794a42 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 16 Jul 2016 16:53:53 -0500 Subject: fix the wiki2 tutorial to set the password as unicode Something really weird is happening but this fixes it. SQLAlchemy is returning the "password_hash" from queries as the type that it was inserted as. Not consistently unicode or bytes. If I insert bytes, then I get bytes back out. If I insert unicode then I get unicode back out. It's unclear why, as the type is Text, the data we're storing is unambiguously US-ASCII and the connection is using a consistent text_factory for unicode conversions of "str" on Python 3. Here, we ensure that we always insert the value as unicode which appears to fix downstream issues like those mentioned in #2605. I was able to reproduce that bug and confirm this fixes it if the original database is initialized using this fix. Obsoletes #2623. --- docs/tutorials/wiki2/src/authentication/tutorial/models/user.py | 5 ++--- docs/tutorials/wiki2/src/authorization/tutorial/models/user.py | 5 ++--- docs/tutorials/wiki2/src/models/tutorial/models/user.py | 5 ++--- docs/tutorials/wiki2/src/tests/tutorial/models/user.py | 5 ++--- docs/tutorials/wiki2/src/views/tutorial/models/user.py | 5 ++--- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/user.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/user.py index 6fb32a1b2..9228b48f7 100644 --- a/docs/tutorials/wiki2/src/authentication/tutorial/models/user.py +++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/user.py @@ -19,11 +19,10 @@ class User(Base): def set_password(self, pw): pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) - self.password_hash = pwhash + self.password_hash = pwhash.decode('utf8') def check_password(self, pw): if self.password_hash is not None: expected_hash = self.password_hash.encode('utf8') - actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash) - return expected_hash == actual_hash + return bcrypt.checkpw(pw.encode('utf8'), expected_hash) return False diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/user.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/user.py index 6fb32a1b2..9228b48f7 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models/user.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/user.py @@ -19,11 +19,10 @@ class User(Base): def set_password(self, pw): pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) - self.password_hash = pwhash + self.password_hash = pwhash.decode('utf8') def check_password(self, pw): if self.password_hash is not None: expected_hash = self.password_hash.encode('utf8') - actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash) - return expected_hash == actual_hash + return bcrypt.checkpw(pw.encode('utf8'), expected_hash) return False diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/user.py b/docs/tutorials/wiki2/src/models/tutorial/models/user.py index 6fb32a1b2..9228b48f7 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/user.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/user.py @@ -19,11 +19,10 @@ class User(Base): def set_password(self, pw): pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) - self.password_hash = pwhash + self.password_hash = pwhash.decode('utf8') def check_password(self, pw): if self.password_hash is not None: expected_hash = self.password_hash.encode('utf8') - actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash) - return expected_hash == actual_hash + return bcrypt.checkpw(pw.encode('utf8'), expected_hash) return False diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/user.py b/docs/tutorials/wiki2/src/tests/tutorial/models/user.py index 6fb32a1b2..9228b48f7 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/models/user.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/models/user.py @@ -19,11 +19,10 @@ class User(Base): def set_password(self, pw): pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) - self.password_hash = pwhash + self.password_hash = pwhash.decode('utf8') def check_password(self, pw): if self.password_hash is not None: expected_hash = self.password_hash.encode('utf8') - actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash) - return expected_hash == actual_hash + return bcrypt.checkpw(pw.encode('utf8'), expected_hash) return False diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/user.py b/docs/tutorials/wiki2/src/views/tutorial/models/user.py index 6fb32a1b2..9228b48f7 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models/user.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models/user.py @@ -19,11 +19,10 @@ class User(Base): def set_password(self, pw): pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) - self.password_hash = pwhash + self.password_hash = pwhash.decode('utf8') def check_password(self, pw): if self.password_hash is not None: expected_hash = self.password_hash.encode('utf8') - actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash) - return expected_hash == actual_hash + return bcrypt.checkpw(pw.encode('utf8'), expected_hash) return False -- cgit v1.2.3 From 412ed2e1988e2fd70d02e4176c9c9c7855b0093c Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Sat, 16 Jul 2016 20:05:51 -0600 Subject: Warn if the renderer response is text_type but no charset If the Response contains no charset we can't use Response.text. We now implicitly encode to UTF-8 after showing a warning. --- pyramid/renderers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 9b3f19510..324c1b02a 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -1,6 +1,7 @@ import json import os import re +import warnings from zope.interface import ( implementer, @@ -467,7 +468,17 @@ class RendererHelper(object): if result is not None: if isinstance(result, text_type): - response.text = result + if response.charset is None: + warnings.warn( + "Renderer returned a result of type {0}, " + "however the response Content-Type <{1}> does not " + "have a charset. Implicitly encoding the result as " + "UTF-8.".format(type(result), response.content_type), + RuntimeWarning + ) + response.body = result.encode('UTF-8') + else: + response.text = result elif isinstance(result, bytes): response.body = result elif hasattr(result, '__iter__'): -- cgit v1.2.3 From 0dcd259c0263c14e8c51d9e204c1419daffbd2ce Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Sat, 16 Jul 2016 20:07:28 -0600 Subject: JSON renderers now return bytes objects --- pyramid/renderers.py | 4 ++-- pyramid/tests/test_config/test_views.py | 4 ++-- pyramid/tests/test_renderers.py | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 324c1b02a..5b915ffdf 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -273,7 +273,7 @@ class JSON(object): if ct == response.default_content_type: response.content_type = 'application/json' default = self._make_default(request) - return self.serializer(value, default=default, **self.kw) + return self.serializer(value, default=default, **self.kw).encode('UTF-8') return _render @@ -380,7 +380,7 @@ class JSONP(JSON): raise HTTPBadRequest('Invalid JSONP callback function name.') ct = 'application/javascript' - body = '/**/{0}({1});'.format(callback, val) + body = '/**/{0}({1});'.format(callback, val).encode('UTF-8') response = request.response if response.content_type == response.default_content_type: response.content_type = ct diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 878574e88..c57deec7a 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -2168,7 +2168,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): ctx_iface=implementedBy(HTTPNotFound), request_iface=IRequest) result = view(None, request) - self._assertBody(result, '{}') + self._assertBody(result, b'{}') def test_add_forbidden_view_with_renderer(self): from zope.interface import implementedBy @@ -2185,7 +2185,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): ctx_iface=implementedBy(HTTPForbidden), request_iface=IRequest) result = view(None, request) - self._assertBody(result, '{}') + self._assertBody(result, b'{}') def test_set_view_mapper(self): from pyramid.interfaces import IViewMapperFactory diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 65bfa5582..e4e718b71 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -18,7 +18,7 @@ class TestJSON(unittest.TestCase): def test_it(self): renderer = self._makeOne()(None) result = renderer({'a':1}, {}) - self.assertEqual(result, '{"a": 1}') + self.assertEqual(result, b'{"a": 1}') def test_with_request_content_type_notset(self): request = testing.DummyRequest() @@ -43,7 +43,7 @@ class TestJSON(unittest.TestCase): renderer = self._makeOne() renderer.add_adapter(datetime, adapter) result = renderer(None)({'a':now}, {'request':request}) - self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) + self.assertEqual(result, '{{"a": "{0}"}}'.format(now.isoformat()).encode('UTF-8')) def test_with_custom_adapter2(self): request = testing.DummyRequest() @@ -54,7 +54,7 @@ class TestJSON(unittest.TestCase): now = datetime.utcnow() renderer = self._makeOne(adapters=((datetime, adapter),)) result = renderer(None)({'a':now}, {'request':request}) - self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) + self.assertEqual(result, '{{"a": "{0}"}}'.format(now.isoformat()).encode('UTF-8')) def test_with_custom_serializer(self): class Serializer(object): @@ -66,7 +66,7 @@ class TestJSON(unittest.TestCase): renderer = self._makeOne(serializer=serializer, baz=5) obj = {'a':'b'} result = renderer(None)(obj, {}) - self.assertEqual(result, 'foo') + self.assertEqual(result, b'foo') self.assertEqual(serializer.obj, obj) self.assertEqual(serializer.kw['baz'], 5) self.assertTrue('default' in serializer.kw) @@ -84,7 +84,7 @@ class TestJSON(unittest.TestCase): objects = [MyObject(1), MyObject(2)] renderer = self._makeOne()(None) result = renderer(objects, {'request':request}) - self.assertEqual(result, '[{"x": 1}, {"x": 2}]') + self.assertEqual(result, b'[{"x": 1}, {"x": 2}]') def test_with_object_adapter_no___json__(self): class MyObject(object): @@ -492,7 +492,7 @@ class Test_render(unittest.TestCase): request.response = response # use a json renderer, which will mutate the response result = self._callFUT('json', dict(a=1), request=request) - self.assertEqual(result, '{"a": 1}') + self.assertEqual(result, b'{"a": 1}') self.assertEqual(request.response, response) def test_no_response_to_preserve(self): @@ -507,7 +507,7 @@ class Test_render(unittest.TestCase): request = DummyRequestWithClassResponse() # use a json renderer, which will mutate the response result = self._callFUT('json', dict(a=1), request=request) - self.assertEqual(result, '{"a": 1}') + self.assertEqual(result, b'{"a": 1}') self.assertFalse('response' in request.__dict__) class Test_render_to_response(unittest.TestCase): @@ -627,7 +627,7 @@ class TestJSONP(unittest.TestCase): request = testing.DummyRequest() request.GET['callback'] = 'callback' result = renderer({'a':'1'}, {'request':request}) - self.assertEqual(result, '/**/callback({"a": "1"});') + self.assertEqual(result, b'/**/callback({"a": "1"});') self.assertEqual(request.response.content_type, 'application/javascript') @@ -637,7 +637,7 @@ class TestJSONP(unittest.TestCase): request = testing.DummyRequest() request.GET['callback'] = 'angular.callbacks._0' result = renderer({'a':'1'}, {'request':request}) - self.assertEqual(result, '/**/angular.callbacks._0({"a": "1"});') + self.assertEqual(result, b'/**/angular.callbacks._0({"a": "1"});') self.assertEqual(request.response.content_type, 'application/javascript') -- cgit v1.2.3 From c7d8f6515d4b154c4a4cf2cbaac9789fbcd19282 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Sat, 16 Jul 2016 20:08:01 -0600 Subject: Add a test that covers the no charset case --- pyramid/tests/test_renderers.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index e4e718b71..ce337cd99 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -290,6 +290,19 @@ class TestRendererHelper(unittest.TestCase): response = helper._make_response(la.encode('utf-8'), request) self.assertEqual(response.body, la.encode('utf-8')) + def test__make_response_result_is_str_no_charset(self): + from pyramid.response import Response + request = testing.DummyRequest() + request.response = Response(content_type='application/json', charset=None) + + self.assertIsNone(request.response.charset) + + helper = self._makeOne('loo.foo') + la = text_('/La Pe\xc3\xb1a', 'utf-8') + response = helper._make_response(la, request) + self.assertIsNone(response.charset) + self.assertEqual(response.body, la.encode('utf-8')) + def test__make_response_result_is_iterable(self): from pyramid.response import Response request = testing.DummyRequest() -- cgit v1.2.3 From 45f8822009d66484de9d5dcedbbd5bc237baf7a0 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Sat, 16 Jul 2016 20:13:48 -0600 Subject: Update CHANGES.txt for #2706 --- CHANGES.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3e659ee9a..8cb4c602e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -19,10 +19,15 @@ Features Bug Fixes --------- + - Fixed bug in `proutes` such that it now shows the correct view when a class and `attr` is involved. See: https://github.com/Pylons/pyramid/pull/2687 +- The JSON renderers now encode their result as UTF-8. The renderer helper + will now warn the user and encode the result as UTF-8 if a renderer returns a + text type and the response does not have a valid character set. See + https://github.com/Pylons/pyramid/pull/2706 Deprecations ------------ @@ -30,4 +35,4 @@ Deprecations Documentation Changes --------------------- - Updated Windows installation instructions and related bits. - See: https://github.com/Pylons/pyramid/issues/2661 \ No newline at end of file + See: https://github.com/Pylons/pyramid/issues/2661 -- cgit v1.2.3 From 251ddd3c2035fa3172ef9221c4a13fa53cdd3385 Mon Sep 17 00:00:00 2001 From: Christian Kollee Date: Sat, 16 Jul 2016 22:57:58 -0700 Subject: Corrected command line for tests (cherry picked from commit df3db24) --- docs/quick_tutorial/routing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/routing.rst b/docs/quick_tutorial/routing.rst index 27c8c2c22..d88adfa1e 100644 --- a/docs/quick_tutorial/routing.rst +++ b/docs/quick_tutorial/routing.rst @@ -79,7 +79,7 @@ Steps .. code-block:: bash - $ $VENV/bin/$VENV/bin/py.test tutorial/tests.py -q + $ $VENV/bin/py.test tutorial/tests.py -q .. 2 passed in 0.39 seconds -- cgit v1.2.3 From b9ed3ffd2bf7e7061eee6137f527e13b44c79ae8 Mon Sep 17 00:00:00 2001 From: Christian Kollee Date: Sat, 16 Jul 2016 23:27:54 -0700 Subject: Corrected test command in static_assets.rst (cherry picked from commit 619c4b8) --- docs/quick_tutorial/static_assets.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/static_assets.rst b/docs/quick_tutorial/static_assets.rst index 65b34f8f9..b8482492d 100644 --- a/docs/quick_tutorial/static_assets.rst +++ b/docs/quick_tutorial/static_assets.rst @@ -47,7 +47,7 @@ Steps .. code-block:: bash - $ $VENV/bin/$VENV/bin/py.test tutorial/tests.py -q + $ $VENV/bin/py.test tutorial/tests.py -q .... 4 passed in 0.50 seconds -- cgit v1.2.3 From c898ddfe8d31718aa47fe697f8760dbc0ec79572 Mon Sep 17 00:00:00 2001 From: Jon Davidson Date: Mon, 18 Jul 2016 13:05:40 -1000 Subject: Change gendered language in examples Some examples in documentation use "dude" and "bro" -- for example, "Not found, bro". While playful, this language can make some people uncomfortable. I have changed the wording to something equally playful that doesn't make assumptions about the reader's gender. --- CONTRIBUTORS.txt | 4 +++- docs/narr/hooks.rst | 8 ++++---- docs/narr/urldispatch.rst | 6 +++--- pyramid/view.py | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 25ccf6838..869cb7733 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -277,4 +277,6 @@ Contributors - Cris Ewing, 2016/06/03 -- Jean-Christophe Bohin, 2016/06/13 \ No newline at end of file +- Jean-Christophe Bohin, 2016/06/13 + +- Jon Davidson, 2016/07/18 diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 49ef29d3f..c54b213f1 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -26,7 +26,7 @@ Not Found View by using the :linenos: def notfound(request): - return Response('Not Found, dude', status='404 Not Found') + return Response('Not Found', status='404 Not Found') def main(globals, **settings): config = Configurator() @@ -45,7 +45,7 @@ and a :term:`scan`, you can replace the Not Found View by using the @notfound_view_config() def notfound(request): - return Response('Not Found, dude', status='404 Not Found') + return Response('Not Found', status='404 Not Found') def main(globals, **settings): config = Configurator() @@ -67,11 +67,11 @@ Views can carry predicates limiting their applicability. For example: @notfound_view_config(request_method='GET') def notfound_get(request): - return Response('Not Found during GET, dude', status='404 Not Found') + return Response('Not Found during GET', status='404 Not Found') @notfound_view_config(request_method='POST') def notfound_post(request): - return Response('Not Found during POST, dude', status='404 Not Found') + return Response('Not Found during POST', status='404 Not Found') def main(globals, **settings): config = Configurator() diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 7d37c04df..9ac01e24a 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -850,7 +850,7 @@ application: from pyramid.httpexceptions import HTTPNotFound def notfound(request): - return HTTPNotFound('Not found, bro.') + return HTTPNotFound() def no_slash(request): return Response('No slash') @@ -871,7 +871,7 @@ If a request enters the application with the ``PATH_INFO`` value of However, if a request enters the application with the ``PATH_INFO`` value of ``/no_slash/``, *no* route will match, and the slash-appending not found view will not find a matching route with an appended slash. As a result, the -``notfound`` view will be called and it will return a "Not found, bro." body. +``notfound`` view will be called and it will return a "Not found" body. If a request enters the application with the ``PATH_INFO`` value of ``/has_slash/``, the second route will match. If a request enters the @@ -892,7 +892,7 @@ exactly the same job: @notfound_view_config(append_slash=True) def notfound(request): - return HTTPNotFound('Not found, bro.') + return HTTPNotFound() @view_config(route_name='noslash') def no_slash(request): diff --git a/pyramid/view.py b/pyramid/view.py index 88c6397af..0ef2d65d4 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -341,7 +341,7 @@ class notfound_view_config(object): @notfound_view_config() def notfound(request): - return Response('Not found, dude!', status='404 Not Found') + return Response('Not found!', status='404 Not Found') All arguments except ``append_slash`` have the same meaning as :meth:`pyramid.view.view_config` and each predicate -- cgit v1.2.3 From cf428a83b8ee733f8c67b113bcdef33fdff6eeae Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Tue, 19 Jul 2016 16:35:49 -0600 Subject: Fix AuthTktCookieHelper so that it doesn't create bad cookies The AuthTktCookieHelper when provided a type it didn't knoww what to do with would simply pass it through unchanged, this would lead to things like object() being serialised by just having str() called on it, which may included spaces and other characters that are not allowed in cookie values. WebOb would send a RuntimeWarning: RuntimeWarning: Cookie value contains invalid bytes: (b' '). Future versions will raise ValueError upon encountering invalid bytes. This fix warns the user of the library directly, and makes sure to call str() on the provided userid, AND then encode it as base64. The user won't get back the original object after decoding on a request/response round-trip, but at least no cookies are being generated that are invalid. --- pyramid/authentication.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pyramid/authentication.py b/pyramid/authentication.py index e6b888db2..8d0adfa3d 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -5,6 +5,7 @@ import hashlib import base64 import re import time as time_mod +import warnings from zope.interface import implementer @@ -947,8 +948,19 @@ class AuthTktCookieHelper(object): if encoding_data: encoding, encoder = encoding_data - userid = encoder(userid) - user_data = 'userid_type:%s' % encoding + else: + warnings.warn( + "userid is of type {}, and is not supported by the " + "AuthTktAuthenticationPolicy. Explicitly converting to string " + "and storing as base64. Subsequent requests will receive a " + "string as the userid, it will not be decoded back to the type " + "provided.".format(type(userid)), RuntimeWarning + ) + encoding, encoder = self.userid_type_encoders.get(text_type) + userid = str(userid) + + userid = encoder(userid) + user_data = 'userid_type:%s' % encoding new_tokens = [] for token in tokens: -- cgit v1.2.3 From a0cc269f90f5ac0e6f95e337009e26b144090650 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Tue, 19 Jul 2016 15:57:26 -0600 Subject: re.split() requires non-empty match Change from a * to a +, so long as there is something to split on, it will split. Fixes this warning: FutureWarning: split() requires a non-empty pattern match. return _compile(pattern, flags).split(string, maxsplit) --- pyramid/scripts/proutes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 19d91cc72..f75810c06 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -296,7 +296,7 @@ class PRoutesCommand(object): items = config.items('proutes') for k, v in items: if 'format' == k: - cols = re.split(r'[,|\s|\n]*', v) + cols = re.split(r'[,|\s\n]+', v) self.column_format = [x.strip() for x in cols] except configparser.NoSectionError: -- cgit v1.2.3 From 29d12cd3917c1a792c3a891e39ab15f99e8b380d Mon Sep 17 00:00:00 2001 From: Keith Yang Date: Sat, 16 Jul 2016 16:28:25 +0800 Subject: Add one-way password hash to security example in Quick Tutorial. --- docs/quick_tutorial/authentication.rst | 23 ++++++++++++++++++++++ docs/quick_tutorial/authentication/setup.py | 3 ++- .../authentication/tutorial/security.py | 16 +++++++++++++-- .../authentication/tutorial/views.py | 7 +++++-- docs/quick_tutorial/authorization/setup.py | 3 ++- .../authorization/tutorial/security.py | 16 +++++++++++++-- .../quick_tutorial/authorization/tutorial/views.py | 7 +++++-- 7 files changed, 65 insertions(+), 10 deletions(-) diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst index acff97f3b..c28958b33 100644 --- a/docs/quick_tutorial/authentication.rst +++ b/docs/quick_tutorial/authentication.rst @@ -34,6 +34,17 @@ Steps .. code-block:: bash $ cd ..; cp -r view_classes authentication; cd authentication + +#. This step depends on bcrypt_, so add it as a dependency in + ``authentication/setup.py``: + + .. literalinclude:: authentication/setup.py + :linenos: + +#. Now we can activate the development-mode distribution: + + .. code-block:: bash + $ $VENV/bin/pip install -e . #. Put the security hash in the ``authentication/development.ini`` @@ -103,6 +114,11 @@ In this example we chose to use the bundled :ref:`AuthTktAuthenticationPolicy ` policy. We enabled it in our configuration and provided a ticket-signing secret in our INI file. +The function ``hash_password`` hashes user's password by bcrypt_ instead of +storing password in plain text directly as a best practice [1]_. And function +``check_password`` will compare the hashed value of the submitted password +against the hashed value of the user's password. + Our view class grew a login view. When you reached it via a ``GET`` request, it returned a login form. When reached via ``POST``, it processed the submitted username and password against the "groupfinder" callable that we registered in @@ -126,3 +142,10 @@ Extra credit .. seealso:: See also :ref:`security_chapter`, :ref:`AuthTktAuthenticationPolicy `. + +.. _bcrypt: https://pypi.python.org/pypi/bcrypt + +.. [1] We are using the bcrypt_ package from PyPI to hash our passwords + securely. There are other one-way hash algorithms for passwords if + bcrypt is an issue on your system. Just make sure that it's an + algorithm approved for storing passwords versus a generic one-way hash. diff --git a/docs/quick_tutorial/authentication/setup.py b/docs/quick_tutorial/authentication/setup.py index 2221b72e9..7a6ff4226 100644 --- a/docs/quick_tutorial/authentication/setup.py +++ b/docs/quick_tutorial/authentication/setup.py @@ -2,7 +2,8 @@ from setuptools import setup requires = [ 'pyramid', - 'pyramid_chameleon' + 'pyramid_chameleon', + 'bcrypt' ] setup(name='tutorial', diff --git a/docs/quick_tutorial/authentication/tutorial/security.py b/docs/quick_tutorial/authentication/tutorial/security.py index ab90bab2c..e585e2642 100644 --- a/docs/quick_tutorial/authentication/tutorial/security.py +++ b/docs/quick_tutorial/authentication/tutorial/security.py @@ -1,5 +1,17 @@ -USERS = {'editor': 'editor', - 'viewer': 'viewer'} +import bcrypt + + +def hash_password(pw): + pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) + return pwhash.decode('utf8') + +def check_password(pw, hashed_pw): + expected_hash = hashed_pw.encode('utf8') + return bcrypt.checkpw(pw.encode('utf8'), expected_hash) + + +USERS = {'editor': hash_password('editor'), + 'viewer': hash_password('viewer')} GROUPS = {'editor': ['group:editors']} diff --git a/docs/quick_tutorial/authentication/tutorial/views.py b/docs/quick_tutorial/authentication/tutorial/views.py index ab46eb2dd..b07538d5e 100644 --- a/docs/quick_tutorial/authentication/tutorial/views.py +++ b/docs/quick_tutorial/authentication/tutorial/views.py @@ -9,7 +9,10 @@ from pyramid.view import ( view_defaults ) -from .security import USERS +from .security import ( + USERS, + check_password +) @view_defaults(renderer='home.pt') @@ -40,7 +43,7 @@ class TutorialViews: if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] - if USERS.get(login) == password: + if check_password(password, USERS.get(login)): headers = remember(request, login) return HTTPFound(location=came_from, headers=headers) diff --git a/docs/quick_tutorial/authorization/setup.py b/docs/quick_tutorial/authorization/setup.py index 2221b72e9..7a6ff4226 100644 --- a/docs/quick_tutorial/authorization/setup.py +++ b/docs/quick_tutorial/authorization/setup.py @@ -2,7 +2,8 @@ from setuptools import setup requires = [ 'pyramid', - 'pyramid_chameleon' + 'pyramid_chameleon', + 'bcrypt' ] setup(name='tutorial', diff --git a/docs/quick_tutorial/authorization/tutorial/security.py b/docs/quick_tutorial/authorization/tutorial/security.py index ab90bab2c..e585e2642 100644 --- a/docs/quick_tutorial/authorization/tutorial/security.py +++ b/docs/quick_tutorial/authorization/tutorial/security.py @@ -1,5 +1,17 @@ -USERS = {'editor': 'editor', - 'viewer': 'viewer'} +import bcrypt + + +def hash_password(pw): + pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) + return pwhash.decode('utf8') + +def check_password(pw, hashed_pw): + expected_hash = hashed_pw.encode('utf8') + return bcrypt.checkpw(pw.encode('utf8'), expected_hash) + + +USERS = {'editor': hash_password('editor'), + 'viewer': hash_password('viewer')} GROUPS = {'editor': ['group:editors']} diff --git a/docs/quick_tutorial/authorization/tutorial/views.py b/docs/quick_tutorial/authorization/tutorial/views.py index 43d14455a..b2dc905c0 100644 --- a/docs/quick_tutorial/authorization/tutorial/views.py +++ b/docs/quick_tutorial/authorization/tutorial/views.py @@ -10,7 +10,10 @@ from pyramid.view import ( forbidden_view_config ) -from .security import USERS +from .security import ( + USERS, + check_password +) @view_defaults(renderer='home.pt') @@ -42,7 +45,7 @@ class TutorialViews: if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] - if USERS.get(login) == password: + if check_password(password, USERS.get(login)): headers = remember(request, login) return HTTPFound(location=came_from, headers=headers) -- cgit v1.2.3 From f197dd79dd40d70cae9ee1f9d3ee25e86fbc989d Mon Sep 17 00:00:00 2001 From: Keith Yang Date: Fri, 22 Jul 2016 08:00:34 +0800 Subject: Sign CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 869cb7733..12b6fedcf 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -280,3 +280,5 @@ Contributors - Jean-Christophe Bohin, 2016/06/13 - Jon Davidson, 2016/07/18 + +- Keith Yang, 2016/07/22 -- cgit v1.2.3 From e5c279b1d4d0484bc58c9101c523959d09641f7d Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 23 Jul 2016 14:52:09 -0700 Subject: Rewrite Quick Tutorial narrative in authentication.rst for consistent flow --- docs/quick_tutorial/authentication.rst | 42 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst index c28958b33..892beb3ec 100644 --- a/docs/quick_tutorial/authentication.rst +++ b/docs/quick_tutorial/authentication.rst @@ -1,7 +1,7 @@ .. _qtut_authentication: ============================== -20: Logins With Authentication +20: Logins with authentication ============================== Login views that authenticate a username and password against a list of users. @@ -35,13 +35,14 @@ Steps $ cd ..; cp -r view_classes authentication; cd authentication -#. This step depends on bcrypt_, so add it as a dependency in - ``authentication/setup.py``: +#. Add ``bcrypt`` as a dependency in ``authentication/setup.py``: .. literalinclude:: authentication/setup.py + :language: python + :emphasize-lines: 5-6 :linenos: -#. Now we can activate the development-mode distribution: +#. We can now install our project in development mode: .. code-block:: bash @@ -107,23 +108,32 @@ Unlike many web frameworks, Pyramid includes a built-in but optional security model for authentication and authorization. This security system is intended to be flexible and support many needs. In this security model, authentication (who are you) and authorization (what are you allowed to do) are not just pluggable, -but de-coupled. To learn one step at a time, we provide a system that -identifies users and lets them log out. +but decoupled. To learn one step at a time, we provide a system that identifies +users and lets them log out. In this example we chose to use the bundled :ref:`AuthTktAuthenticationPolicy ` policy. We enabled it in our configuration and provided a ticket-signing secret in our INI file. -The function ``hash_password`` hashes user's password by bcrypt_ instead of -storing password in plain text directly as a best practice [1]_. And function -``check_password`` will compare the hashed value of the submitted password -against the hashed value of the user's password. - Our view class grew a login view. When you reached it via a ``GET`` request, it returned a login form. When reached via ``POST``, it processed the submitted username and password against the "groupfinder" callable that we registered in the configuration. +The function ``hash_password`` uses a one-way hashing algorithm with a salt on +the user's password via ``bcrypt``, instead of storing the password in plain +text. This is considered to be a "best practice" for security. + +.. note:: + There are alternative libraries to ``bcrypt`` if it is an issue on your + system. Just make sure that the library uses an algorithm approved for + storing passwords securely. + +The function ``check_password`` will compare the two hashed values of the +submitted password and the user's password stored in the database. If the +hashed values are equivalent, then the user is authenticated, else +authentication fails. + In our template, we fetched the ``logged_in`` value from the view class. We use this to calculate the logged-in user, if any. In the template we can then choose to show a login link to anonymous visitors or a logout link to logged-in @@ -141,11 +151,5 @@ Extra credit request? Use ``import pdb; pdb.set_trace()`` to answer this. .. seealso:: See also :ref:`security_chapter`, - :ref:`AuthTktAuthenticationPolicy `. - -.. _bcrypt: https://pypi.python.org/pypi/bcrypt - -.. [1] We are using the bcrypt_ package from PyPI to hash our passwords - securely. There are other one-way hash algorithms for passwords if - bcrypt is an issue on your system. Just make sure that it's an - algorithm approved for storing passwords versus a generic one-way hash. + :ref:`AuthTktAuthenticationPolicy `, `bcrypt + `_ -- cgit v1.2.3 From ebd2ea5a21978865804fc7569d4383f0ed90d489 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 23 Jul 2016 23:22:05 -0700 Subject: Add highlighting of changed lines --- docs/quick_tutorial/forms.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/quick_tutorial/forms.rst b/docs/quick_tutorial/forms.rst index 1f421ee67..84ceb13d6 100644 --- a/docs/quick_tutorial/forms.rst +++ b/docs/quick_tutorial/forms.rst @@ -41,6 +41,7 @@ Steps pulls in Colander as a dependency: .. literalinclude:: forms/setup.py + :emphasize-lines: 5-6 :linenos: #. We can now install our project in development mode: -- cgit v1.2.3 From a37645742645589bd1700adf771a46d74568877c Mon Sep 17 00:00:00 2001 From: andrew david burt Date: Fri, 29 Jul 2016 14:46:59 -0700 Subject: remove essentially duplicate "note" section under "initializing the database" The note section under "initializing the database" was entered twice, the same except differing verb tenses. I removed one. (cherry picked from commit 96c1e36) --- docs/tutorials/wiki2/installation.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index a214b1306..0440c2d1d 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -402,13 +402,6 @@ initialize our database. already have a database, you should delete it before running ``initialize_tutorial_db`` again. -.. note:: - - The ``initialize_tutorial_db`` command is not performing a migration but - rather simply creating missing tables and adding some dummy data. If you - already have a database, you should delete it before running - ``initialize_tutorial_db`` again. - Type the following command, making sure you are still in the ``tutorial`` directory (the directory with a ``development.ini`` in it): -- cgit v1.2.3 From 3caa9dab20789a4d8cce58a9dec9e4ff25be6127 Mon Sep 17 00:00:00 2001 From: andrew david burt Date: Fri, 29 Jul 2016 16:18:21 -0700 Subject: corrected folder name in docs one of the instances of the tutorial's folder name was mistyped as "tutorials" rather than "tutorial" in the "Route declarations" section (cherry picked from commit 696f17c) --- docs/tutorials/wiki2/basiclayout.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index ce67bb9e3..98a14c644 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -114,7 +114,7 @@ Finally ``main`` is finished configuring things, so it uses the Route declarations ------------------ -Open the ``tutorials/routes.py`` file. It should already contain the following: +Open the ``tutorial/routes.py`` file. It should already contain the following: .. literalinclude:: src/basiclayout/tutorial/routes.py :linenos: -- cgit v1.2.3 From 830bcb8aea8d9c842ef1ccd9a80470836f4c6442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20G=C3=B3recki?= Date: Wed, 10 Aug 2016 11:46:45 +0100 Subject: Add docs & explict tests --- docs/api/authentication.rst | 3 ++ pyramid/authentication.py | 4 +-- pyramid/tests/test_authentication.py | 61 ++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst index 19d08618b..de2c73491 100644 --- a/docs/api/authentication.rst +++ b/docs/api/authentication.rst @@ -35,4 +35,7 @@ Helper Classes :members: +Helper Functions +~~~~~~~~~~~~~~~~ + .. autofunction:: extract_http_basic_credentials diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 712cef08e..46909d84e 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -1120,10 +1120,10 @@ class _SimpleSerializer(object): def extract_http_basic_credentials(request): """ A helper function for extraction of HTTP Basic credentials - from a given `request`. + from a given :term:`request`. ``request`` - The request object + The :term:`request` object """ authorization = request.headers.get('Authorization') if not authorization: diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 0a22e5965..53747b6f0 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -1476,6 +1476,67 @@ class TestBasicAuthAuthenticationPolicy(unittest.TestCase): self.assertEqual(policy.forget(None), [ ('WWW-Authenticate', 'Basic realm="SomeRealm"')]) + +class TestExtractHTTPBasicCredentials(unittest.TestCase): + def _get_func(self): + from pyramid.authentication import extract_http_basic_credentials + return extract_http_basic_credentials + + def test_no_auth_header(self): + request = testing.DummyRequest() + fn = self._get_func() + + self.assertIsNone(fn(request)) + + def test_invalid_payload(self): + import base64 + request = testing.DummyRequest() + request.headers['Authorization'] = 'Basic %s' % base64.b64encode( + bytes_('chrisrpassword')).decode('ascii') + fn = self._get_func() + self.assertIsNone(fn(request)) + + def test_not_a_basic_auth_scheme(self): + import base64 + request = testing.DummyRequest() + request.headers['Authorization'] = 'OtherScheme %s' % base64.b64encode( + bytes_('chrisr:password')).decode('ascii') + fn = self._get_func() + self.assertIsNone(fn(request)) + + def test_no_base64_encoding(self): + request = testing.DummyRequest() + request.headers['Authorization'] = 'Basic ...' + fn = self._get_func() + self.assertIsNone(fn(request)) + + def test_latin1_payload(self): + import base64 + request = testing.DummyRequest() + inputs = (b'm\xc3\xb6rk\xc3\xb6:' + b'm\xc3\xb6rk\xc3\xb6password').decode('utf-8') + request.headers['Authorization'] = 'Basic %s' % ( + base64.b64encode(inputs.encode('latin-1')).decode('latin-1')) + fn = self._get_func() + self.assertEqual(fn(request), ( + b'm\xc3\xb6rk\xc3\xb6'.decode('utf-8'), + b'm\xc3\xb6rk\xc3\xb6password'.decode('utf-8') + )) + + def test_utf8_payload(self): + import base64 + request = testing.DummyRequest() + inputs = (b'm\xc3\xb6rk\xc3\xb6:' + b'm\xc3\xb6rk\xc3\xb6password').decode('utf-8') + request.headers['Authorization'] = 'Basic %s' % ( + base64.b64encode(inputs.encode('utf-8')).decode('latin-1')) + fn = self._get_func() + self.assertEqual(fn(request), ( + b'm\xc3\xb6rk\xc3\xb6'.decode('utf-8'), + b'm\xc3\xb6rk\xc3\xb6password'.decode('utf-8') + )) + + class TestSimpleSerializer(unittest.TestCase): def _makeOne(self): from pyramid.authentication import _SimpleSerializer -- cgit v1.2.3 From 693cb098a7bc8fbff5fb97c1ac031d0b6e397060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20G=C3=B3recki?= Date: Thu, 11 Aug 2016 12:04:28 +0100 Subject: Add this feature to chenges & small improvement --- CHANGES.txt | 5 +++++ pyramid/authentication.py | 13 +++++++++++-- pyramid/tests/test_authentication.py | 12 ++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8cb4c602e..a614a4499 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -17,6 +17,11 @@ Backward Incompatibilities Features -------- +- The `_get_credentials` private method of `BasicAuthAuthenticationPolicy` + has been extracted into standalone function `extract_http_basic_credentials` + in `pyramid.authentication` module, this function extracts HTTP Basic + credentials from `request` object, and returns them as a named tuple. + Bug Fixes --------- diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 46909d84e..034da9e46 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -1,6 +1,7 @@ import binascii from codecs import utf_8_decode from codecs import utf_8_encode +from collections import namedtuple import hashlib import base64 import re @@ -1118,9 +1119,16 @@ class _SimpleSerializer(object): return bytes_(appstruct) +http_basic_credentials = namedtuple('http_basic_credentials', + ['username', 'password']) + + def extract_http_basic_credentials(request): """ A helper function for extraction of HTTP Basic credentials - from a given :term:`request`. + from a given :term:`request`. Returned values: + + - ``None`` - when credentials couldn't be extracted + - ``namedtuple`` with extracted ``username`` and ``password`` attributes ``request`` The :term:`request` object @@ -1153,4 +1161,5 @@ def extract_http_basic_credentials(request): username, password = auth.split(':', 1) except ValueError: # not enough values to unpack return None - return username, password + + return http_basic_credentials(username, password) diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 53747b6f0..32923c9ab 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -1536,6 +1536,18 @@ class TestExtractHTTPBasicCredentials(unittest.TestCase): b'm\xc3\xb6rk\xc3\xb6password'.decode('utf-8') )) + def test_namedtuple_return(self): + import base64 + request = testing.DummyRequest() + request.headers['Authorization'] = 'Basic %s' % base64.b64encode( + bytes_('chrisr:pass')).decode('ascii') + fn = self._get_func() + result = fn(request) + + self.assertEqual(result.username, 'chrisr') + self.assertEqual(result.password, 'pass') + + class TestSimpleSerializer(unittest.TestCase): def _makeOne(self): -- cgit v1.2.3 From 24362ad1dd2a27337e465e4a21bd59efaebb8025 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 15 Aug 2016 00:01:07 -0500 Subject: fix alchemy scaffold help text --- pyramid/scaffolds/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyramid/scaffolds/__init__.py b/pyramid/scaffolds/__init__.py index 62c3eeecc..841dc403e 100644 --- a/pyramid/scaffolds/__init__.py +++ b/pyramid/scaffolds/__init__.py @@ -4,7 +4,7 @@ from textwrap import dedent from pyramid.compat import native_ -from pyramid.scaffolds.template import Template # API +from pyramid.scaffolds.template import Template # API class PyramidTemplate(Template): """ @@ -60,5 +60,6 @@ class ZODBProjectTemplate(PyramidTemplate): class AlchemyProjectTemplate(PyramidTemplate): _template_dir = 'alchemy' - summary = 'Pyramid project using SQLAlchemy, SQLite, URL dispatch, and' - ' Chameleon' + summary = ( + 'Pyramid project using SQLAlchemy, SQLite, URL dispatch, and ' + 'Jinja2') -- cgit v1.2.3 From ab69a91aaa5d04fcdf72dad0e8810eaabea67745 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 15 Aug 2016 01:43:58 -0700 Subject: update output of pcreate --list for Quick Tour --- docs/quick_tour.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index fb957be1c..88cd69400 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -504,10 +504,10 @@ Pyramid's ``pcreate`` command can list the available scaffolds: $ pcreate --list Available scaffolds: - alchemy: Pyramid SQLAlchemy project using url dispatch + alchemy: Pyramid project using SQLAlchemy, SQLite, URL dispatch, and Jinja2 pyramid_jinja2_starter: Pyramid Jinja2 starter project - starter: Pyramid starter project - zodb: Pyramid ZODB project using traversal + starter: Pyramid starter project using URL dispatch and Chameleon + zodb: Pyramid project using ZODB, traversal, and Chameleon The ``pyramid_jinja2`` add-on gave us a scaffold that we can use. From the parent directory of where we want our Python package to be generated, let's use -- cgit v1.2.3 From 314636e5e3f43f582135e687dc0faf4b5ff1fcbf Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 15 Aug 2016 22:23:50 -0700 Subject: Fix up URLs to include default language and reduce verbosity (cherry picked from commit d6b2c66) --- RELEASING.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RELEASING.txt b/RELEASING.txt index d8572fa94..326dea993 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -148,12 +148,12 @@ Here are the changes: <> -A "What's New In Pyramid 1.X" document exists at -http://docs.pylonsproject.org/projects/pyramid/1.X-branch/whatsnew-1.X.html . +What's New In Pyramid 1.X: +http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html -You will be able to see the 1.X release documentation (across all -alphas and betas, as well as when it eventually gets to final release) -at http://docs.pylonsproject.org/projects/pyramid/1.X-branch/ . +1.X release documentation (across all alphas and betas, as well as when it gets +to final release): +http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/ You can install it via PyPI: -- cgit v1.2.3 From 23b7a2a8423a56928e99953bcc41ce281b1ae6be Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 16 Aug 2016 18:11:09 -0500 Subject: Fix #2744 by reverting #2706 This reverts commit c7d8f6515d4b154c4a4cf2cbaac9789fbcd19282. This reverts commit 0dcd259c0263c14e8c51d9e204c1419daffbd2ce. This reverts commit 412ed2e1988e2fd70d02e4176c9c9c7855b0093c. --- CHANGES.txt | 5 ----- pyramid/renderers.py | 17 +++-------------- pyramid/tests/test_config/test_views.py | 4 ++-- pyramid/tests/test_renderers.py | 31 +++++++++---------------------- 4 files changed, 14 insertions(+), 43 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8cb4c602e..02e3271ce 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -24,11 +24,6 @@ Bug Fixes and `attr` is involved. See: https://github.com/Pylons/pyramid/pull/2687 -- The JSON renderers now encode their result as UTF-8. The renderer helper - will now warn the user and encode the result as UTF-8 if a renderer returns a - text type and the response does not have a valid character set. See - https://github.com/Pylons/pyramid/pull/2706 - Deprecations ------------ diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 5b915ffdf..9b3f19510 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -1,7 +1,6 @@ import json import os import re -import warnings from zope.interface import ( implementer, @@ -273,7 +272,7 @@ class JSON(object): if ct == response.default_content_type: response.content_type = 'application/json' default = self._make_default(request) - return self.serializer(value, default=default, **self.kw).encode('UTF-8') + return self.serializer(value, default=default, **self.kw) return _render @@ -380,7 +379,7 @@ class JSONP(JSON): raise HTTPBadRequest('Invalid JSONP callback function name.') ct = 'application/javascript' - body = '/**/{0}({1});'.format(callback, val).encode('UTF-8') + body = '/**/{0}({1});'.format(callback, val) response = request.response if response.content_type == response.default_content_type: response.content_type = ct @@ -468,17 +467,7 @@ class RendererHelper(object): if result is not None: if isinstance(result, text_type): - if response.charset is None: - warnings.warn( - "Renderer returned a result of type {0}, " - "however the response Content-Type <{1}> does not " - "have a charset. Implicitly encoding the result as " - "UTF-8.".format(type(result), response.content_type), - RuntimeWarning - ) - response.body = result.encode('UTF-8') - else: - response.text = result + response.text = result elif isinstance(result, bytes): response.body = result elif hasattr(result, '__iter__'): diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index c57deec7a..878574e88 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -2168,7 +2168,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): ctx_iface=implementedBy(HTTPNotFound), request_iface=IRequest) result = view(None, request) - self._assertBody(result, b'{}') + self._assertBody(result, '{}') def test_add_forbidden_view_with_renderer(self): from zope.interface import implementedBy @@ -2185,7 +2185,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): ctx_iface=implementedBy(HTTPForbidden), request_iface=IRequest) result = view(None, request) - self._assertBody(result, b'{}') + self._assertBody(result, '{}') def test_set_view_mapper(self): from pyramid.interfaces import IViewMapperFactory diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index ce337cd99..65bfa5582 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -18,7 +18,7 @@ class TestJSON(unittest.TestCase): def test_it(self): renderer = self._makeOne()(None) result = renderer({'a':1}, {}) - self.assertEqual(result, b'{"a": 1}') + self.assertEqual(result, '{"a": 1}') def test_with_request_content_type_notset(self): request = testing.DummyRequest() @@ -43,7 +43,7 @@ class TestJSON(unittest.TestCase): renderer = self._makeOne() renderer.add_adapter(datetime, adapter) result = renderer(None)({'a':now}, {'request':request}) - self.assertEqual(result, '{{"a": "{0}"}}'.format(now.isoformat()).encode('UTF-8')) + self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) def test_with_custom_adapter2(self): request = testing.DummyRequest() @@ -54,7 +54,7 @@ class TestJSON(unittest.TestCase): now = datetime.utcnow() renderer = self._makeOne(adapters=((datetime, adapter),)) result = renderer(None)({'a':now}, {'request':request}) - self.assertEqual(result, '{{"a": "{0}"}}'.format(now.isoformat()).encode('UTF-8')) + self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) def test_with_custom_serializer(self): class Serializer(object): @@ -66,7 +66,7 @@ class TestJSON(unittest.TestCase): renderer = self._makeOne(serializer=serializer, baz=5) obj = {'a':'b'} result = renderer(None)(obj, {}) - self.assertEqual(result, b'foo') + self.assertEqual(result, 'foo') self.assertEqual(serializer.obj, obj) self.assertEqual(serializer.kw['baz'], 5) self.assertTrue('default' in serializer.kw) @@ -84,7 +84,7 @@ class TestJSON(unittest.TestCase): objects = [MyObject(1), MyObject(2)] renderer = self._makeOne()(None) result = renderer(objects, {'request':request}) - self.assertEqual(result, b'[{"x": 1}, {"x": 2}]') + self.assertEqual(result, '[{"x": 1}, {"x": 2}]') def test_with_object_adapter_no___json__(self): class MyObject(object): @@ -290,19 +290,6 @@ class TestRendererHelper(unittest.TestCase): response = helper._make_response(la.encode('utf-8'), request) self.assertEqual(response.body, la.encode('utf-8')) - def test__make_response_result_is_str_no_charset(self): - from pyramid.response import Response - request = testing.DummyRequest() - request.response = Response(content_type='application/json', charset=None) - - self.assertIsNone(request.response.charset) - - helper = self._makeOne('loo.foo') - la = text_('/La Pe\xc3\xb1a', 'utf-8') - response = helper._make_response(la, request) - self.assertIsNone(response.charset) - self.assertEqual(response.body, la.encode('utf-8')) - def test__make_response_result_is_iterable(self): from pyramid.response import Response request = testing.DummyRequest() @@ -505,7 +492,7 @@ class Test_render(unittest.TestCase): request.response = response # use a json renderer, which will mutate the response result = self._callFUT('json', dict(a=1), request=request) - self.assertEqual(result, b'{"a": 1}') + self.assertEqual(result, '{"a": 1}') self.assertEqual(request.response, response) def test_no_response_to_preserve(self): @@ -520,7 +507,7 @@ class Test_render(unittest.TestCase): request = DummyRequestWithClassResponse() # use a json renderer, which will mutate the response result = self._callFUT('json', dict(a=1), request=request) - self.assertEqual(result, b'{"a": 1}') + self.assertEqual(result, '{"a": 1}') self.assertFalse('response' in request.__dict__) class Test_render_to_response(unittest.TestCase): @@ -640,7 +627,7 @@ class TestJSONP(unittest.TestCase): request = testing.DummyRequest() request.GET['callback'] = 'callback' result = renderer({'a':'1'}, {'request':request}) - self.assertEqual(result, b'/**/callback({"a": "1"});') + self.assertEqual(result, '/**/callback({"a": "1"});') self.assertEqual(request.response.content_type, 'application/javascript') @@ -650,7 +637,7 @@ class TestJSONP(unittest.TestCase): request = testing.DummyRequest() request.GET['callback'] = 'angular.callbacks._0' result = renderer({'a':'1'}, {'request':request}) - self.assertEqual(result, b'/**/angular.callbacks._0({"a": "1"});') + self.assertEqual(result, '/**/angular.callbacks._0({"a": "1"});') self.assertEqual(request.response.content_type, 'application/javascript') -- cgit v1.2.3 From ddc0ee35b2bf954c8d4b0a69cea3cf0f4f477e10 Mon Sep 17 00:00:00 2001 From: Mark Jones Date: Wed, 17 Aug 2016 11:58:12 -0500 Subject: Clarify that headers are ADDED to the response Make it clear that headers are added to the response instead of overwriting existing headers. Explain how to set the content_type instead of just assuming they will know. --- pyramid/httpexceptions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index e76f43c8a..b44227fc5 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -98,7 +98,9 @@ be forwarded to its :class:`~pyramid.response.Response` superclass: a plain-text override of the default ``detail`` ``headers`` - a list of (k,v) header pairs + a list of (k,v) header pairs or a dict to be added to the + response, use content_type='application/json' kwarg to change + the content type of the response. ``comment`` a plain-text additional information which is -- cgit v1.2.3 From 6775670519e5e869264065e3f6cdd1a0dfe71d66 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 18 Aug 2016 00:32:56 -0700 Subject: update output for pcreate --list in Quick Tutorial --- docs/quick_tutorial/scaffolds.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quick_tutorial/scaffolds.rst b/docs/quick_tutorial/scaffolds.rst index 7845f2b71..ad002f4fd 100644 --- a/docs/quick_tutorial/scaffolds.rst +++ b/docs/quick_tutorial/scaffolds.rst @@ -38,9 +38,9 @@ Steps $ $VENV/bin/pcreate --list Available scaffolds: - alchemy: Pyramid SQLAlchemy project using url dispatch - starter: Pyramid starter project - zodb: Pyramid ZODB project using traversal + alchemy: Pyramid project using SQLAlchemy, SQLite, URL dispatch, and Jinja2 + starter: Pyramid starter project using URL dispatch and Chameleon + zodb: Pyramid project using ZODB, traversal, and Chameleon #. Tell ``pcreate`` to use the ``starter`` scaffold to make our project: -- cgit v1.2.3 From 0b4e6de25aa382bb16eef6751c3fd4da7fe5b520 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 19 Aug 2016 01:44:38 -0700 Subject: attempt to add testing for py36 --- .travis.yml | 2 ++ tox.ini | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fbdd88224..a5ae0977e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,8 @@ matrix: env: TOXENV=docs - python: 3.5 env: TOXENV=pep8 + allow_failures: + - env: TOXENV=py36 install: - travis_retry pip install tox diff --git a/tox.ini b/tox.ini index 0156d9e51..a029182d3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,9 @@ [tox] envlist = - py27,py33,py34,py35,pypy, + py27,py33,py34,py35,py36,pypy, docs,pep8, {py2,py3}-cover,coverage, +skip-missing-interpreters = True [testenv] # Most of these are defaults but if you specify any you can't fall back @@ -12,6 +13,7 @@ basepython = py33: python3.3 py34: python3.4 py35: python3.5 + py36: nightly pypy: pypy py2: python2.7 py3: python3.5 -- cgit v1.2.3 From 202ad628a1431a70fe5557f8fc3ca2d6b0f9314c Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 19 Aug 2016 02:12:26 -0700 Subject: attempt to add testing for py36 --- .travis.yml | 2 ++ tox.ini | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a5ae0977e..7528a948a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,8 @@ matrix: env: TOXENV=docs - python: 3.5 env: TOXENV=pep8 + - python: nightly + env: TOXENV=py36 allow_failures: - env: TOXENV=py36 diff --git a/tox.ini b/tox.ini index a029182d3..6ba30c2a5 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,6 @@ basepython = py33: python3.3 py34: python3.4 py35: python3.5 - py36: nightly pypy: pypy py2: python2.7 py3: python3.5 -- cgit v1.2.3 From 1941d27c65c99a38b29dde9536e71d08dc9f64d9 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 19 Aug 2016 02:56:27 -0700 Subject: attempt to add testing for py36 --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 6ba30c2a5..a74aaadbc 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,7 @@ basepython = py33: python3.3 py34: python3.4 py35: python3.5 + py36: python3.6 pypy: pypy py2: python2.7 py3: python3.5 -- cgit v1.2.3 From 2cb7e099ea67d4f6ef490785c6e777e5fa9ec334 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 4 Aug 2016 23:12:27 -0500 Subject: add test cases to reproduce #2697 --- pyramid/tests/test_config/test_init.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index de199d079..67c2905d8 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -1545,6 +1545,31 @@ class TestActionState(unittest.TestCase): c.execute_actions() self.assertEqual(output, [('f', (1,), {}), ('g', (8,), {})]) + def test_reentrant_action_with_deferred_discriminator(self): + # see https://github.com/Pylons/pyramid/issues/2697 + from pyramid.registry import Deferred + output = [] + c = self._makeOne() + def f(*a, **k): + output.append(('f', a, k)) + c.actions.append((4, g, (4,), {}, (), None, 2)) + def g(*a, **k): + output.append(('g', a, k)) + def h(*a, **k): + output.append(('h', a, k)) + def discrim(): + self.assertEqual(output, [('f', (1,), {}), ('g', (2,), {})]) + return 3 + d = Deferred(discrim) + c.actions = [ + (d, h, (3,), {}, (), None, 1), # order 1 + (1, f, (1,)), # order 0 + (2, g, (2,)), # order 0 + ] + c.execute_actions() + self.assertEqual(output, [ + ('f', (1,), {}), ('g', (2,), {}), ('h', (3,), {}), ('g', (4,), {})]) + def test_reentrant_action_error(self): from pyramid.exceptions import ConfigurationError c = self._makeOne() @@ -1597,6 +1622,21 @@ class Test_reentrant_action_functional(unittest.TestCase): self.assertEqual(route.name, 'foo') self.assertEqual(route.path, '/foo') + def test_deferred_discriminator(self): + # see https://github.com/Pylons/pyramid/issues/2697 + from pyramid.config import PHASE0_CONFIG + config = self._makeConfigurator() + def deriver(view, info): return view + deriver.options = ('foo',) + config.add_view_deriver(deriver, 'foo_view') + # add_view uses a deferred discriminator and will fail if executed + # prior to add_view_deriver executing its action + config.add_view(lambda r: r.response, name='', foo=1) + def dummy_action(): + # trigger a re-entrant action + config.action(None, lambda: None) + config.action(None, dummy_action, order=PHASE0_CONFIG) + config.commit() class Test_resolveConflicts(unittest.TestCase): def _callFUT(self, actions): -- cgit v1.2.3 From 08ff26c443ef95078ab347eb79dc4666a17075dd Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 31 Aug 2016 03:50:44 -0500 Subject: add extra tests for testing action discriminators across orders --- pyramid/tests/test_config/test_init.py | 49 +++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 67c2905d8..7e1a856cf 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -1595,6 +1595,28 @@ class TestActionState(unittest.TestCase): (3, g, (8,)), ]) + def test_executing_conflicting_action_across_orders(self): + from pyramid.exceptions import ConfigurationConflictError + c = self._makeOne() + def f(*a, **k): pass + def g(*a, **k): pass + c.actions = [ + (1, f, (1,), {}, (), None, -1), + (1, g, (2,)), + ] + self.assertRaises(ConfigurationConflictError, c.execute_actions) + + def test_executing_conflicting_action_across_reentrant_orders(self): + from pyramid.exceptions import ConfigurationConflictError + c = self._makeOne() + def f(*a, **k): + c.actions.append((1, g, (8,))) + def g(*a, **k): pass + c.actions = [ + (1, f, (1,), {}, (), None, -1), + ] + self.assertRaises(ConfigurationConflictError, c.execute_actions) + class Test_reentrant_action_functional(unittest.TestCase): def _makeConfigurator(self, *arg, **kw): from pyramid.config import Configurator @@ -1852,7 +1874,32 @@ class Test_resolveConflicts(unittest.TestCase): 'order': 99999} ] ) - + + def test_override_success_across_orders(self): + from pyramid.tests.test_config import dummyfactory as f + result = self._callFUT([ + (1, f, (2,), {}, ('x',), 'eek', 0), + (1, f, (3,), {}, ('x', 'y'), 'ack', 10), + ]) + result = list(result) + self.assertEqual(result, [ + {'info': 'eek', + 'args': (2,), + 'callable': f, + 'introspectables': (), + 'kw': {}, + 'discriminator': 1, + 'includepath': ('x',), + 'order': 0}, + ]) + + def test_conflicts_across_orders(self): + from pyramid.tests.test_config import dummyfactory as f + result = self._callFUT([ + (1, f, (2,), {}, ('x', 'y'), 'eek', 0), + (1, f, (3,), {}, ('x'), 'ack', 10), + ]) + self.assertRaises(ConfigurationConflictError, list, result) class TestGlobalRegistriesIntegration(unittest.TestCase): def setUp(self): -- cgit v1.2.3 From 0f9a5641444a78458f06c06ab279205c97ee4ebe Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 31 Aug 2016 03:51:26 -0500 Subject: re-enable support for resolving conflicts across orders This feature was silently dropped in Pyramid 1.4 when deferred discriminators were added. It is re-introduced such that it's possible to override an action in order X by defining an action in order Y where Y <= X with a non-conflicting includepath. This also takes special care to avoid undeferring the discriminator for an action until the execution engine is ready to start executing actions of the same order. This gives time for required actions to execute prior, allowing the discriminator to depend on earlier actions. fixes #2697 --- pyramid/config/__init__.py | 205 +++++++++++++++++++-------------- pyramid/tests/test_config/test_init.py | 34 +++--- 2 files changed, 133 insertions(+), 106 deletions(-) diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 553f32c9b..958526386 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -27,7 +27,6 @@ from pyramid.compat import ( text_, reraise, string_types, - zip_longest, ) from pyramid.events import ApplicationCreated @@ -1110,29 +1109,8 @@ class ActionState(object): try: all_actions = [] executed_actions = [] - pending_actions = iter([]) - - # resolve the new action list against what we have already - # executed -- if a new action appears intertwined in the list - # of already-executed actions then someone wrote a broken - # re-entrant action because it scheduled the action *after* it - # should have been executed (as defined by the action order) - def resume(actions): - for a, b in zip_longest(actions, executed_actions): - if b is None and a is not None: - # common case is that we are executing every action - yield a - elif b is not None and a != b: - raise ConfigurationError( - 'During execution a re-entrant action was added ' - 'that modified the planned execution order in a ' - 'way that is incompatible with what has already ' - 'been executed.') - else: - # resolved action is in the same location as before, - # so we are in good shape, but the action is already - # executed so we skip it - assert b is not None and a == b + action_iter = iter([]) + conflict_state = ConflictResolverState() while True: # We clear the actions list prior to execution so if there @@ -1141,26 +1119,14 @@ class ActionState(object): # ensures that the previously executed actions have no new # conflicts. if self.actions: - # Only resolve the new actions against executed_actions - # and pending_actions instead of everything to avoid - # redundant checks. - # Assume ``actions = resolveConflicts([A, B, C])`` which - # after conflict checks, resulted in ``actions == [A]`` - # then we know action A won out or a conflict would have - # been raised. Thus, when action D is added later, we only - # need to check the new action against A. - # ``actions = resolveConflicts([A, D]) should drop the - # number of redundant checks down from O(n^2) closer to - # O(n lg n). all_actions.extend(self.actions) - pending_actions = resume(resolveConflicts( - executed_actions + - list(pending_actions) + - self.actions - )) + action_iter = resolveConflicts( + self.actions, + state=conflict_state, + ) self.actions = [] - action = next(pending_actions, None) + action = next(action_iter, None) if action is None: # we are done! break @@ -1176,9 +1142,7 @@ class ActionState(object): try: if callable is not None: callable(*args, **kw) - except (KeyboardInterrupt, SystemExit): # pragma: no cover - raise - except: + except Exception: t, v, tb = sys.exc_info() try: reraise(ConfigurationExecutionError, @@ -1193,65 +1157,102 @@ class ActionState(object): executed_actions.append(action) + self.actions = all_actions + return executed_actions + finally: if clear: - del self.actions[:] - else: - self.actions = all_actions + self.actions = [] + + +class ConflictResolverState(object): + def __init__(self): + # keep a set of resolved discriminators to test against to ensure + # that a new action does not conflict with something already executed + self.resolved_ainfos = {} + + # actions left over from a previous iteration + self.remaining_actions = [] + + # after executing an action we memoize its order to avoid any new + # actions sending us backward + self.min_order = None + + # unique tracks the index of the action so we need it to increase + # monotonically across invocations to resolveConflicts + self.start = 0 + # this function is licensed under the ZPL (stolen from Zope) -def resolveConflicts(actions): +def resolveConflicts(actions, state=None): """Resolve conflicting actions Given an actions list, identify and try to resolve conflicting actions. Actions conflict if they have the same non-None discriminator. + Conflicting actions can be resolved if the include path of one of the actions is a prefix of the includepaths of the other conflicting actions and is unequal to the include paths in the other conflicting actions. + + Actions are resolved on a per-order basis because some discriminators + cannot be computed until earlier actions have executed. An action in an + earlier order may execute successfully only to find out later that it was + overridden by another action with a smaller include path. This will result + in a conflict as there is no way to revert the original action. + + ``state`` may be an instance of ``ConflictResolverState`` that + can be used to resume execution and resolve the new actions against the + list of executed actions from a previous call. + """ + if state is None: + state = ConflictResolverState() + + # pick up where we left off last time, but track the new actions as well + state.remaining_actions.extend(normalize_actions(actions)) + actions = state.remaining_actions def orderandpos(v): n, v = v - if not isinstance(v, dict): - # old-style tuple action - v = expand_action(*v) return (v['order'] or 0, n) - sactions = sorted(enumerate(actions), key=orderandpos) - def orderonly(v): n, v = v - if not isinstance(v, dict): - # old-style tuple action - v = expand_action(*v) return v['order'] or 0 + sactions = sorted(enumerate(actions, start=state.start), key=orderandpos) for order, actiongroup in itertools.groupby(sactions, orderonly): # "order" is an integer grouping. Actions in a lower order will be # executed before actions in a higher order. All of the actions in # one grouping will be executed (its callable, if any will be called) # before any of the actions in the next. - - unique = {} output = [] + unique = {} + + # error out if we went backward in order + if state.min_order is not None and order < state.min_order: + r = ['Actions were added to order={0} after execution had moved ' + 'on to order={1}. Conflicting actions: ' + .format(order, state.min_order)] + for i, action in actiongroup: + for line in str(action['info']).rstrip().split('\n'): + r.append(" " + line) + raise ConfigurationError('\n'.join(r)) for i, action in actiongroup: # Within an order, actions are executed sequentially based on # original action ordering ("i"). - if not isinstance(action, dict): - # old-style tuple action - action = expand_action(*action) - - # "ainfo" is a tuple of (order, i, action) where "order" is a - # user-supplied grouping, "i" is an integer expressing the relative - # position of this action in the action list being resolved, and - # "action" is an action dictionary. The purpose of an ainfo is to - # associate an "order" and an "i" with a particular action; "order" - # and "i" exist for sorting purposes after conflict resolution. - ainfo = (order, i, action) + # "ainfo" is a tuple of (i, action) where "i" is an integer + # expressing the relative position of this action in the action + # list being resolved, and "action" is an action dictionary. The + # purpose of an ainfo is to associate an "i" with a particular + # action; "i" exists for sorting after conflict resolution. + ainfo = (i, action) + # wait to defer discriminators until we are on their order because + # the discriminator may depend on state from a previous order discriminator = undefer(action['discriminator']) action['discriminator'] = discriminator @@ -1266,28 +1267,39 @@ def resolveConflicts(actions): # Check for conflicts conflicts = {} - for discriminator, ainfos in unique.items(): - # We use (includepath, order, i) as a sort key because we need to + # We use (includepath, i) as a sort key because we need to # sort the actions by the paths so that the shortest path with a # given prefix comes first. The "first" action is the one with the - # shortest include path. We break sorting ties using "order", then - # "i". + # shortest include path. We break sorting ties using "i". def bypath(ainfo): - path, order, i = ainfo[2]['includepath'], ainfo[0], ainfo[1] + path, i = ainfo[1]['includepath'], ainfo[0] return path, order, i ainfos.sort(key=bypath) ainfo, rest = ainfos[0], ainfos[1:] - output.append(ainfo) - _, _, action = ainfo - basepath, baseinfo, discriminator = ( - action['includepath'], - action['info'], - action['discriminator'], - ) + _, action = ainfo + + # ensure this new action does not conflict with a previously + # resolved action from an earlier order / invocation + prev_ainfo = state.resolved_ainfos.get(discriminator) + if prev_ainfo is not None: + _, paction = prev_ainfo + basepath, baseinfo = paction['includepath'], paction['info'] + includepath = action['includepath'] + # if the new action conflicts with the resolved action then + # note the conflict, otherwise drop the action as it's + # effectively overriden by the previous action + if (includepath[:len(basepath)] != basepath or + includepath == basepath): + L = conflicts.setdefault(discriminator, [baseinfo]) + L.append(action['info']) + + else: + output.append(ainfo) - for _, _, action in rest: + basepath, baseinfo = action['includepath'], action['info'] + for _, action in rest: includepath = action['includepath'] # Test whether path is a prefix of opath if (includepath[:len(basepath)] != basepath or # not a prefix @@ -1298,14 +1310,30 @@ def resolveConflicts(actions): if conflicts: raise ConfigurationConflictError(conflicts) - # sort conflict-resolved actions by (order, i) and yield them one - # by one - for a in [x[2] for x in sorted(output, key=operator.itemgetter(0, 1))]: - yield a + # sort resolved actions by "i" and yield them one by one + for i, action in sorted(output, key=operator.itemgetter(0)): + # do not memoize the order until we resolve an action inside it + state.min_order = action['order'] + state.start = i + 1 + state.remaining_actions.remove(action) + state.resolved_ainfos[action['discriminator']] = (i, action) + yield action -def expand_action(discriminator, callable=None, args=(), kw=None, - includepath=(), info=None, order=0, introspectables=()): +def normalize_actions(actions): + """Convert old-style tuple actions to new-style dicts.""" + result = [] + for v in actions: + if not isinstance(v, dict): + v = expand_action_tuple(*v) + result.append(v) + return result + + +def expand_action_tuple( + discriminator, callable=None, args=(), kw=None, includepath=(), + info=None, order=0, introspectables=(), +): if kw is None: kw = {} return dict( @@ -1319,4 +1347,5 @@ def expand_action(discriminator, callable=None, args=(), kw=None, introspectables=introspectables, ) + global_registries = WeakOrderedSet() diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 7e1a856cf..7078d7e26 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -1728,15 +1728,14 @@ class Test_resolveConflicts(unittest.TestCase): def test_it_success_dicts(self): from pyramid.tests.test_config import dummyfactory as f - from pyramid.config import expand_action result = self._callFUT([ - expand_action(None, f), - expand_action(1, f, (1,), {}, (), 'first'), - expand_action(1, f, (2,), {}, ('x',), 'second'), - expand_action(1, f, (3,), {}, ('y',), 'third'), - expand_action(4, f, (4,), {}, ('y',), 'should be last', 99999), - expand_action(3, f, (3,), {}, ('y',)), - expand_action(None, f, (5,), {}, ('y',)), + (None, f), + (1, f, (1,), {}, (), 'first'), + (1, f, (2,), {}, ('x',), 'second'), + (1, f, (3,), {}, ('y',), 'third'), + (4, f, (4,), {}, ('y',), 'should be last', 99999), + (3, f, (3,), {}, ('y',)), + (None, f, (5,), {}, ('y',)), ]) result = list(result) self.assertEqual( @@ -1802,17 +1801,16 @@ class Test_resolveConflicts(unittest.TestCase): def test_it_with_actions_grouped_by_order(self): from pyramid.tests.test_config import dummyfactory as f - from pyramid.config import expand_action result = self._callFUT([ - expand_action(None, f), # X - expand_action(1, f, (1,), {}, (), 'third', 10), # X - expand_action(1, f, (2,), {}, ('x',), 'fourth', 10), - expand_action(1, f, (3,), {}, ('y',), 'fifth', 10), - expand_action(2, f, (1,), {}, (), 'sixth', 10), # X - expand_action(3, f, (1,), {}, (), 'seventh', 10), # X - expand_action(5, f, (4,), {}, ('y',), 'eighth', 99999), # X - expand_action(4, f, (3,), {}, (), 'first', 5), # X - expand_action(4, f, (5,), {}, ('y',), 'second', 5), + (None, f), # X + (1, f, (1,), {}, (), 'third', 10), # X + (1, f, (2,), {}, ('x',), 'fourth', 10), + (1, f, (3,), {}, ('y',), 'fifth', 10), + (2, f, (1,), {}, (), 'sixth', 10), # X + (3, f, (1,), {}, (), 'seventh', 10), # X + (5, f, (4,), {}, ('y',), 'eighth', 99999), # X + (4, f, (3,), {}, (), 'first', 5), # X + (4, f, (5,), {}, ('y',), 'second', 5), ]) result = list(result) self.assertEqual(len(result), 6) -- cgit v1.2.3 From ab0be8ab6c6001db7ca7644e4d34bbdcb49d70e2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 31 Aug 2016 22:49:50 -0500 Subject: add changelog for #2714 --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 02e3271ce..198dfaab5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -24,6 +24,10 @@ Bug Fixes and `attr` is involved. See: https://github.com/Pylons/pyramid/pull/2687 +- Fix a ``FutureWarning`` in Python 3.5 when using ``re.split`` on the + ``format`` setting to the ``proutes`` script. + See https://github.com/Pylons/pyramid/pull/2714 + Deprecations ------------ -- cgit v1.2.3 From c8530bde0d2cd2f4d53c0ca6e45ac463dfef7397 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 31 Aug 2016 23:16:04 -0500 Subject: add changelog for #2715 --- CHANGES.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 198dfaab5..aa17f501b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -28,6 +28,14 @@ Bug Fixes ``format`` setting to the ``proutes`` script. See https://github.com/Pylons/pyramid/pull/2714 +- Fix a ``RuntimeWarning`` emitted by WebOb when using arbitrary objects + as the ``userid`` in the ``AuthTktAuthenticationPolicy``. This is now caught + by the policy and the object is serialized as a base64 string to avoid + the cryptic warning. Since the userid will be read back as a string on + subsequent requests a more useful warning is emitted encouraging you to + use a primitive type instead. + See https://github.com/Pylons/pyramid/pull/2715 + Deprecations ------------ -- cgit v1.2.3 From 5b7eeeb71165289d8affcb47ec2bb86ae00af393 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 31 Aug 2016 23:31:12 -0500 Subject: silence the warning generated by #2715 in the test suite --- pyramid/tests/test_authentication.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 0a22e5965..ce9e50719 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -1089,7 +1089,10 @@ class TestAuthTktCookieHelper(unittest.TestCase): helper = self._makeOne('secret') request = self._makeRequest() userid = object() - result = helper.remember(request, userid) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', RuntimeWarning) + result = helper.remember(request, userid) + self.assertTrue(str(w[-1].message).startswith('userid is of type')) values = self._parseHeaders(result) self.assertEqual(len(result), 3) value = values[0] -- cgit v1.2.3 From dd0a1589a3d6cbf4557ba4987fae48b715bf1714 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 31 Aug 2016 23:38:56 -0500 Subject: ref the pull request for #2615 in the changelog --- CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index aa17f501b..f679f9993 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -14,6 +14,8 @@ Backward Incompatibilities To run your server as a daemon you should use a process manager instead of pserve. + See https://github.com/Pylons/pyramid/pull/2615 + Features -------- -- cgit v1.2.3 From 0295ae858cb18bc280c6ace76fda44b4154a085c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 31 Aug 2016 23:46:45 -0500 Subject: rename the credentials class --- CHANGES.txt | 4 ++-- docs/api/authentication.rst | 2 ++ pyramid/authentication.py | 14 ++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b485ae59e..8fd6a8a91 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -20,9 +20,9 @@ Features -------- - The `_get_credentials` private method of `BasicAuthAuthenticationPolicy` - has been extracted into standalone function `extract_http_basic_credentials` + has been extracted into standalone function ``extract_http_basic_credentials` in `pyramid.authentication` module, this function extracts HTTP Basic - credentials from `request` object, and returns them as a named tuple. + credentials from a ``request`` object, and returns them as a named tuple. Bug Fixes --------- diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst index de2c73491..57f32327a 100644 --- a/docs/api/authentication.rst +++ b/docs/api/authentication.rst @@ -34,6 +34,8 @@ Helper Classes .. autoclass:: AuthTktCookieHelper :members: + .. autoclass:: HTTPBasicCredentials + :members: Helper Functions ~~~~~~~~~~~~~~~~ diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 7d766fd06..2ee5576d9 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -1131,19 +1131,17 @@ class _SimpleSerializer(object): return bytes_(appstruct) -http_basic_credentials = namedtuple('http_basic_credentials', - ['username', 'password']) +HTTPBasicCredentials = namedtuple( + 'HTTPBasicCredentials', ['username', 'password']) def extract_http_basic_credentials(request): """ A helper function for extraction of HTTP Basic credentials - from a given :term:`request`. Returned values: + from a given :term:`request`. - - ``None`` - when credentials couldn't be extracted - - ``namedtuple`` with extracted ``username`` and ``password`` attributes + Returns a :class:`.HTTPBasicCredentials` 2-tuple with ``username`` and + ``password`` attributes or ``None`` if no credentials could be found. - ``request`` - The :term:`request` object """ authorization = request.headers.get('Authorization') if not authorization: @@ -1174,4 +1172,4 @@ def extract_http_basic_credentials(request): except ValueError: # not enough values to unpack return None - return http_basic_credentials(username, password) + return HTTPBasicCredentials(username, password) -- cgit v1.2.3 From 44e0fca16dfa6e9e896a1dc254c85b79d2c8ae88 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 31 Aug 2016 23:53:13 -0500 Subject: reference pull request for #2662 --- CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.txt b/CHANGES.txt index 8fd6a8a91..3eb23b9ec 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -23,6 +23,7 @@ Features has been extracted into standalone function ``extract_http_basic_credentials` in `pyramid.authentication` module, this function extracts HTTP Basic credentials from a ``request`` object, and returns them as a named tuple. + See https://github.com/Pylons/pyramid/pull/2662 Bug Fixes --------- -- cgit v1.2.3 From a5c8100d780155643063742c716ac8d2f54e2e45 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 1 Sep 2016 00:30:13 -0500 Subject: drop support for python 3.3 and error on 2.6 closes #2476 --- .travis.yml | 2 -- HACKING.txt | 6 +++--- docs/narr/install.rst | 5 ++--- docs/narr/introduction.rst | 2 +- docs/quick_tutorial/requirements.rst | 2 +- setup.py | 10 ++++------ tox.ini | 9 +-------- 7 files changed, 12 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7528a948a..b46f677a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,6 @@ matrix: include: - python: 2.7 env: TOXENV=py27 - - python: 3.3 - env: TOXENV=py33 - python: 3.4 env: TOXENV=py34 - python: 3.5 diff --git a/HACKING.txt b/HACKING.txt index 5bbdce0c6..86ba5d0ca 100644 --- a/HACKING.txt +++ b/HACKING.txt @@ -124,10 +124,10 @@ In order to add a feature to Pyramid: - The feature must be documented in both the API and narrative documentation (in ``docs/``). -- The feature must work fully on the following CPython versions: 2.6, 2.7, 3.2, - 3.3, 3.4, and 3.5 on both UNIX and Windows. +- The feature must work fully on the following CPython versions: 2.7, 3.2, + 3.4, and 3.5 on both UNIX and Windows. -- The feature must work on the latest version of PyPy and PyPy3. +- The feature must work on the latest version of PyPy. - The feature must not cause installation or runtime failure on App Engine. If it doesn't cause installation or runtime failure, but doesn't actually diff --git a/docs/narr/install.rst b/docs/narr/install.rst index c59ced2a5..677c27e4a 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -21,9 +21,8 @@ the following sections. .. sidebar:: Python Versions - As of this writing, :app:`Pyramid` has been tested under Python 2.7, - Python 3.3, Python 3.4, Python 3.5, PyPy, and PyPy3. :app:`Pyramid` does - not run under any version of Python before 2.7. + As of this writing, :app:`Pyramid` is tested against Python 2.7, + Python 3.4, Python 3.5, PyPy. :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 diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index de6ac408b..47638579b 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -860,7 +860,7 @@ Every release of Pyramid has 100% statement coverage via unit and integration tests, as measured by the ``coverage`` tool available on PyPI. It also has greater than 95% decision/condition coverage as measured by the ``instrumental`` tool available on PyPI. It is automatically tested by Travis, -and Jenkins on Python 2.7, Python 3.3, Python 3.4, Python 3.5, PyPy, and PyPy3 +and Jenkins on Python 2.7, Python 3.4, Python 3.5, and PyPy after each commit to its GitHub repository. Official Pyramid add-ons are held to a similar testing standard. We still find bugs in Pyramid and its official add-ons, but we've noticed we find a lot more of them while working on other diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index 958347a90..1de9a8acf 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -19,7 +19,7 @@ virtual environment.) This *Quick Tutorial* is based on: -* **Python 3.5**. Pyramid fully supports Python 3.3+ and Python 2.7+. This +* **Python 3.5**. Pyramid fully supports Python 3.4+ and Python 2.7+. This tutorial uses **Python 3.5** but runs fine under Python 2.7. * **venv**. We believe in virtual environments. For this tutorial, we use diff --git a/setup.py b/setup.py index 021da2b5f..f738ee623 100644 --- a/setup.py +++ b/setup.py @@ -18,16 +18,15 @@ import sys from setuptools import setup, find_packages py_version = sys.version_info[:2] -is_pypy = '__pypy__' in sys.builtin_module_names PY3 = py_version[0] == 3 if PY3: - if py_version < (3, 3) and not is_pypy: # PyPy3 masquerades as Python 3.2... - raise RuntimeError('On Python 3, Pyramid requires Python 3.3 or better') + if py_version < (3, 4): + raise RuntimeError('On Python 3, Pyramid requires Python 3.4 or better') else: - if py_version < (2, 6): - raise RuntimeError('On Python 2, Pyramid requires Python 2.6 or better') + if py_version < (2, 7): + raise RuntimeError('On Python 2, Pyramid requires Python 2.7 or better') here = os.path.abspath(os.path.dirname(__file__)) try: @@ -81,7 +80,6 @@ setup(name='pyramid', "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: Implementation :: CPython", diff --git a/tox.ini b/tox.ini index a74aaadbc..8ceb142cb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py27,py33,py34,py35,py36,pypy, + py27,py34,py35,py36,pypy, docs,pep8, {py2,py3}-cover,coverage, skip-missing-interpreters = True @@ -10,7 +10,6 @@ skip-missing-interpreters = True # to defaults for others. basepython = py27: python2.7 - py33: python3.3 py34: python3.4 py35: python3.5 py36: python3.6 @@ -28,12 +27,6 @@ commands = python pyramid/scaffolds/tests.py deps = virtualenv -[testenv:py33-scaffolds] -basepython = python3.3 -commands = - python pyramid/scaffolds/tests.py -deps = virtualenv - [testenv:py34-scaffolds] basepython = python3.4 commands = -- cgit v1.2.3 From 8cf8bcc2aa30da8a4c8a8e3181176aa45a58792b Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 1 Sep 2016 00:58:11 -0700 Subject: clean up HACKING and RELEASING for 1.8 --- HACKING.txt | 6 +++--- RELEASING.txt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/HACKING.txt b/HACKING.txt index 86ba5d0ca..4b237b56c 100644 --- a/HACKING.txt +++ b/HACKING.txt @@ -124,8 +124,8 @@ In order to add a feature to Pyramid: - The feature must be documented in both the API and narrative documentation (in ``docs/``). -- The feature must work fully on the following CPython versions: 2.7, 3.2, - 3.4, and 3.5 on both UNIX and Windows. +- The feature must work fully on the following CPython versions: 2.7, 3.4, + and 3.5 on both UNIX and Windows. - The feature must work on the latest version of PyPy. @@ -199,7 +199,7 @@ Running Tests Alternately:: - $ tox -e{py26,py27,py32,py33,py34,py35,pypy,pypy3}-scaffolds, + $ tox -e{py27,py34,py35,pypy}-scaffolds, Test Coverage ------------- diff --git a/RELEASING.txt b/RELEASING.txt index 326dea993..4690fbd37 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -33,8 +33,8 @@ Prepare new release branch - Run tests on Windows if feasible. -- Make sure all scaffold tests pass (Py 2.7, 3.3, 3.4, 3.5, and pypy on UNIX; - this doesn't work on Windows): +- Make sure all scaffold tests pass (CPython 2.7, 3.4, and 3.5, and PyPy on + UNIX; this doesn't work on Windows): $ ./scaffoldtests.sh -- cgit v1.2.3 From bdd5440023f016f3b6597c66dc26cc30e22960eb Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 1 Sep 2016 01:06:37 -0700 Subject: One more for 2.6 --- docs/quick_tour.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 88cd69400..b2dec77e9 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -52,7 +52,7 @@ For Windows: # or for a specific released version c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ " -Of course Pyramid runs fine on Python 2.6+, as do the examples in this *Quick +Of course Pyramid runs fine on Python 2.7+, as do the examples in this *Quick Tour*. We're showing Python 3 for simplicity. (Pyramid had production support for Python 3 in October 2011.) Also for simplicity, the remaining examples will show only UNIX commands. -- cgit v1.2.3 From b36dd80c4400dbaacf9809d9bc1c9147631382f5 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 1 Sep 2016 01:07:49 -0500 Subject: allow prepare/bootstrap to be used as a context manager closes #1822 --- pyramid/paster.py | 14 ++++++++++++++ pyramid/scripting.py | 35 ++++++++++++++++++++++++++++++----- pyramid/tests/test_scripting.py | 21 +++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 3916be8f0..1b7afb5dc 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -129,8 +129,22 @@ def bootstrap(config_uri, request=None, options=None): {'http_port': 8080} and then use %(http_port)s in the config file. + This function may be used as a context manager to call the ``closer`` + automatically: + + .. code-block:: python + + with bootstrap('development.ini') as env: + request = env['request'] + # ... + See :ref:`writing_a_script` for more information about how to use this function. + + .. versionchanged:: 1.8 + + Added the ability to use the return value as a context manager. + """ app = get_app(config_uri, options=options) env = prepare(request) diff --git a/pyramid/scripting.py b/pyramid/scripting.py index d9587338f..7607d3ea3 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -56,12 +56,25 @@ def prepare(request=None, registry=None): ``root`` returned is the application's root resource object. The ``closer`` returned is a callable (accepting no arguments) that should be called when your scripting application is finished - using the root. ``registry`` is the registry object passed or - the last registry loaded into - :attr:`pyramid.config.global_registries` if no registry is passed. + using the root. ``registry`` is the resolved registry object. ``request`` is the request object passed or the constructed request if no request is passed. ``root_factory`` is the root factory used to construct the root. + + This function may be used as a context manager to call the ``closer`` + automatically: + + .. code-block:: python + + registry = config.registry + with prepare(registry) as env: + request = env['request'] + # ... + + .. versionchanged:: 1.8 + + Added the ability to use the return value as a context manager. + """ if registry is None: registry = getattr(request, 'registry', global_registries.last) @@ -85,8 +98,20 @@ def prepare(request=None, registry=None): root = root_factory(request) if getattr(request, 'context', None) is None: request.context = root - return {'root':root, 'closer':closer, 'registry':registry, - 'request':request, 'root_factory':root_factory} + return AppEnvironment( + root=root, + closer=closer, + registry=registry, + request=request, + root_factory=root_factory, + ) + +class AppEnvironment(dict): + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self['closer']() def _make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index 1e952062b..00f738e02 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -134,6 +134,27 @@ class Test_prepare(unittest.TestCase): root, closer = info['root'], info['closer'] closer() + def test_it_is_a_context_manager(self): + request = DummyRequest({}) + registry = request.registry = self._makeRegistry() + closer_called = [False] + with self._callFUT(request=request) as info: + root, request = info['root'], info['request'] + pushed = self.manager.get() + self.assertEqual(pushed['request'], request) + self.assertEqual(pushed['registry'], registry) + self.assertEqual(pushed['request'].registry, registry) + self.assertEqual(root.a, (request,)) + orig_closer = info['closer'] + def closer(): + orig_closer() + closer_called[0] = True + info['closer'] = closer + self.assertTrue(closer_called[0]) + self.assertEqual(self.default, self.manager.get()) + self.assertEqual(request.context, root) + self.assertEqual(request.registry, registry) + class Test__make_request(unittest.TestCase): def _callFUT(self, path='/', registry=None): from pyramid.scripting import _make_request -- cgit v1.2.3 From f6d001333354937c8e4798d5c9ba5a31fb5dd644 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 3 Sep 2016 22:23:47 -0500 Subject: changelog for #2757 --- CHANGES.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 3eb23b9ec..e96b03ab1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -25,6 +25,11 @@ Features credentials from a ``request`` object, and returns them as a named tuple. See https://github.com/Pylons/pyramid/pull/2662 +- Pyramid 1.4 silently dropped a feature of the configurator that has been + restored. It's again possible for action discriminators to conflict across + different action orders. + See https://github.com/Pylons/pyramid/pull/2757 + Bug Fixes --------- @@ -44,6 +49,17 @@ Bug Fixes use a primitive type instead. See https://github.com/Pylons/pyramid/pull/2715 +- Pyramid 1.6 introduced the ability for an action to invoke another action. + There was a bug in the way that ``config.add_view`` would interact with + custom view derivers introduced in Pyramid 1.7 because the view's + discriminator cannot be computed until view derivers and view predicates + have been created in earlier orders. Invoking an action from another action + would trigger an unrolling of the pipeline and would compute discriminators + before they were ready. The new behavior respects the ``order`` of the action + and ensures the discriminators are not computed until dependent actions + from previous orders have executed. + See https://github.com/Pylons/pyramid/pull/2757 + Deprecations ------------ -- cgit v1.2.3 From 10f34853225e18c1d57f95d2f9f52a496300498a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 3 Sep 2016 22:39:48 -0500 Subject: changelog for #2760 --- CHANGES.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index e96b03ab1..49613b242 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -30,6 +30,11 @@ Features different action orders. See https://github.com/Pylons/pyramid/pull/2757 +- ``pyramid.paster.bootstrap`` and its sibling ``pyramid.scripting.prepare`` + can now be used as context managers to automatically invoke the ``closer`` + and pop threadlocals off of the stack to prevent memory leaks. + See https://github.com/Pylons/pyramid/pull/2760 + Bug Fixes --------- -- cgit v1.2.3 From df0e20f3440bcf039f6d40822a1280efa26bfc87 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 3 Sep 2016 22:47:44 -0500 Subject: update docs to support the bootstrap context manager from #2760 --- docs/narr/commandline.rst | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 6cd90d42f..242bc7ec7 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -649,6 +649,10 @@ using the :func:`pyramid.paster.bootstrap` command in the body of your script. .. versionadded:: 1.1 :func:`pyramid.paster.bootstrap` +.. versionchanged:: 1.8 + Added the ability for ``bootstrap`` to cleanup automatically via the + ``with`` statement. + In the simplest case, :func:`pyramid.paster.bootstrap` can be used with a single argument, which accepts the :term:`PasteDeploy` ``.ini`` file representing your Pyramid application's configuration as a single argument: @@ -656,8 +660,9 @@ representing your Pyramid application's configuration as a single argument: .. code-block:: python from pyramid.paster import bootstrap - env = bootstrap('/path/to/my/development.ini') - print(env['request'].route_url('home')) + + with bootstrap('/path/to/my/development.ini') as env: + print(env['request'].route_url('home')) :func:`pyramid.paster.bootstrap` returns a dictionary containing framework-related information. This dictionary will always contain a @@ -723,8 +728,9 @@ load instead of ``main``: .. code-block:: python from pyramid.paster import bootstrap - env = bootstrap('/path/to/my/development.ini#another') - print(env['request'].route_url('home')) + + with bootstrap('/path/to/my/development.ini#another') as env: + print(env['request'].route_url('home')) The above example specifies the ``another`` ``app``, ``pipeline``, or ``composite`` section of your PasteDeploy configuration file. The ``app`` @@ -761,9 +767,9 @@ desired request and passing it into :func:`~pyramid.paster.bootstrap`: from pyramid.request import Request request = Request.blank('/', base_url='https://example.com/prefix') - env = bootstrap('/path/to/my/development.ini#another', request=request) - print(env['request'].application_url) - # will print 'https://example.com/prefix' + with bootstrap('/path/to/my/development.ini#another', request=request) as env: + print(env['request'].application_url) + # will print 'https://example.com/prefix' Now you can readily use Pyramid's APIs for generating URLs: @@ -776,7 +782,9 @@ Now you can readily use Pyramid's APIs for generating URLs: Cleanup ~~~~~~~ -When your scripting logic finishes, it's good manners to call the ``closer`` +If you're using the ``with``-statement variant then there's nothing to +worry about. However if you're using the returned environment directly then +when your scripting logic finishes, it's good manners to call the ``closer`` callback: .. code-block:: python @@ -891,15 +899,12 @@ contains the following code: omit = options.omit if omit is None: omit = [] - env = bootstrap(config_uri) - settings, closer = env['registry'].settings, env['closer'] - try: + with bootstrap(config_uri) as env: + settings = env['registry'].settings for k, v in settings.items(): if any([k.startswith(x) for x in omit]): continue print('%-40s %-20s' % (k, v)) - finally: - closer() This script uses the Python ``optparse`` module to allow us to make sense out of extra arguments passed to the script. It uses the -- cgit v1.2.3 From 35b0e34db3fb15d8e1eef10473df86abb9b90ad8 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 4 Sep 2016 16:17:52 -0500 Subject: refactor tween initialization to occur at config-time previously the EXCVIEW tween was only created in the router OR when another tween is added. Now the EXCVIEW tween is just added at startup similar to how view derivers and many other things operate. --- pyramid/config/__init__.py | 1 + pyramid/config/tweens.py | 15 +++------------ pyramid/router.py | 9 +++------ pyramid/testing.py | 1 + 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 958526386..d4064dc78 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -379,6 +379,7 @@ class Configurator( self.add_default_view_predicates() self.add_default_view_derivers() self.add_default_route_predicates() + self.add_default_tweens() if exceptionresponse_view is not None: exceptionresponse_view = self.maybe_dotted(exceptionresponse_view) diff --git a/pyramid/config/tweens.py b/pyramid/config/tweens.py index 8e1800f33..0aeb01fe3 100644 --- a/pyramid/config/tweens.py +++ b/pyramid/config/tweens.py @@ -10,7 +10,6 @@ from pyramid.compat import ( from pyramid.exceptions import ConfigurationError from pyramid.tweens import ( - excview_tween_factory, MAIN, INGRESS, EXCVIEW, @@ -107,6 +106,9 @@ class TweensConfiguratorMixin(object): return self._add_tween(tween_factory, under=under, over=over, explicit=False) + def add_default_tweens(self): + self.add_tween(EXCVIEW) + @action_method def _add_tween(self, tween_factory, under=None, over=None, explicit=False): @@ -142,17 +144,6 @@ class TweensConfiguratorMixin(object): if tweens is None: tweens = Tweens() registry.registerUtility(tweens, ITweens) - ex_intr = self.introspectable('tweens', - ('tween', EXCVIEW, False), - EXCVIEW, - 'implicit tween') - ex_intr['name'] = EXCVIEW - ex_intr['factory'] = excview_tween_factory - ex_intr['type'] = 'implicit' - ex_intr['under'] = None - ex_intr['over'] = MAIN - introspectables.append(ex_intr) - tweens.add_implicit(EXCVIEW, excview_tween_factory, over=MAIN) def register(): if explicit: diff --git a/pyramid/router.py b/pyramid/router.py index 19773cf62..fd11925e9 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -34,8 +34,6 @@ from pyramid.traversal import ( ResourceTreeTraverser, ) -from pyramid.tweens import excview_tween_factory - @implementer(IRouter) class Router(object): @@ -51,11 +49,10 @@ class Router(object): self.routes_mapper = q(IRoutesMapper) self.request_factory = q(IRequestFactory, default=Request) self.request_extensions = q(IRequestExtensions) - tweens = q(ITweens) - if tweens is None: - tweens = excview_tween_factory self.orig_handle_request = self.handle_request - self.handle_request = tweens(self.handle_request, registry) + tweens = q(ITweens) + if tweens is not None: + self.handle_request = tweens(self.handle_request, registry) self.root_policy = self.root_factory # b/w compat self.registry = registry settings = registry.settings diff --git a/pyramid/testing.py b/pyramid/testing.py index ec06fe379..877b351db 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -478,6 +478,7 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True, config.add_default_view_predicates() config.add_default_view_derivers() config.add_default_route_predicates() + config.add_default_tweens() config.commit() global have_zca try: -- cgit v1.2.3 From ad05d18fd74e2add5a1abc149f63ad389a908dc5 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Sep 2016 21:13:29 -0500 Subject: document more clearly the ``__call__`` method on route and view predicates fixes #1549 --- docs/narr/hooks.rst | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index c54b213f1..6d0a2a5a3 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1481,7 +1481,7 @@ method. For example: phash = text def __call__(self, context, request): - return getattr(context, 'content_type', None) == self.val + return request.content_type == self.val The constructor of a predicate factory takes two arguments: ``val`` and ``config``. The ``val`` argument will be the argument passed to @@ -1500,13 +1500,28 @@ with the name and the value serialized. The result of ``phash`` is not seen in output anywhere, it just informs the uniqueness constraints for view configuration. -The ``__call__`` method of a predicate factory must accept a resource -(``context``) and a request, and must return ``True`` or ``False``. It is the -"meat" of the predicate. +The ``__call__`` method differs depending on whether the predicate is used as +a :term:`view predicate` or a :term:`route predicate`: -You can use the same predicate factory as both a view predicate and as a route -predicate, but you'll need to call ``add_view_predicate`` and -``add_route_predicate`` separately with the same factory. +- When used as a route predicate, the ``__call__`` signature is + ``(info, request)``. The ``info`` object is a dictionary containing two + keys: ``match`` and ``route``. ``info['match']`` is the matchdict containing + the patterns matched in the route pattern. ``info['route']`` is the + :class:`pyramid.interfaces.IRoute` object for the current route. + +- When used as a view predicate, the ``__call__`` signature is + ``(context, request)``. The ``context`` is the result of :term:`traversal` + performed using either the route's :term:`root factory` or the app's + :term:`default root factory`. + +In both cases the ``__call__`` method is expected to return ``True`` or +``False``. + +It is possible to use the same predicate factory as both a view predicate and +as a route predicate, but they'll need to handle the ``info`` or ``context`` +argument specially (many predicates do not need this argument) and you'll need +to call ``add_view_predicate`` and ``add_route_predicate`` separately with +the same factory. .. _subscriber_predicates: -- cgit v1.2.3 From 4acd85dc98fb2a43eae54d2116cc4bf383157269 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Sep 2016 21:24:26 -0500 Subject: changelog for #2764 --- CHANGES.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 49613b242..f17a04f92 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -71,4 +71,8 @@ Deprecations Documentation Changes --------------------- - Updated Windows installation instructions and related bits. - See: https://github.com/Pylons/pyramid/issues/2661 + See https://github.com/Pylons/pyramid/issues/2661 + +- Fix an inconsistency in the documentation between view predicates and + route predicates and highlight the differences in their APIs. + See https://github.com/Pylons/pyramid/pull/2764 -- cgit v1.2.3 From 24e1b3b0bd5e15ab13d7c89ef9a291f1b464724c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Sep 2016 21:01:07 -0500 Subject: clarify #2750 a bit further --- pyramid/httpexceptions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index b44227fc5..054917dfa 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -98,9 +98,10 @@ be forwarded to its :class:`~pyramid.response.Response` superclass: a plain-text override of the default ``detail`` ``headers`` - a list of (k,v) header pairs or a dict to be added to the - response, use content_type='application/json' kwarg to change - the content type of the response. + a list of (k,v) header pairs, or a dict, to be added to the + response; use the content_type='application/json' kwarg and other + similar kwargs to to change properties of the response supported by the + :class:`pyramid.response.Response` superclass ``comment`` a plain-text additional information which is -- cgit v1.2.3 From 5b33ff66090e4f2b0249cc85341cce09d0b57fca Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Sep 2016 21:07:34 -0500 Subject: changelog for #2750 --- CHANGES.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index f17a04f92..85e71aef3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -76,3 +76,8 @@ Documentation Changes - Fix an inconsistency in the documentation between view predicates and route predicates and highlight the differences in their APIs. See https://github.com/Pylons/pyramid/pull/2764 + +- Clarify a possible misuse of the ``headers`` kwarg to subclasses of + :class:`pyramid.httpexceptions.HTTPException` in which more appropriate + kwargs from the parent class :class:`pyramid.response.Response` should be + used instead. See https://github.com/Pylons/pyramid/pull/2750 -- cgit v1.2.3 From 21f2b61272f83de110087344e68f32953cd7b42f Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Sep 2016 20:39:45 -0700 Subject: Why use venv/bin/pip instead of source bin/activate then pip? - Closes #2610 --- docs/narr/install.rst | 21 +++++++++++++++++++++ docs/quick_tour.rst | 4 +++- docs/quick_tutorial/requirements.rst | 3 +++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 677c27e4a..7b5d8361b 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -191,6 +191,24 @@ After installing Python as described previously in :ref:`for-mac-os-x-users` or $ $VENV/bin/pip install "pyramid==\ |release|\ " +.. index:: + single: $VENV/bin/pip vs. source bin/activate + +.. _venv-bin-pip-vs-source-bin-activate: + +.. note:: Why use ``$VENV/bin/pip`` instead of ``source bin/activate``, then + ``pip``? + + Although the latter method requires fewer key strokes to issue commands once + invoked, there are numerous reasons why one should avoid using ``activate`` + within a virtual environment. Michael F. Lamb (datagrok) presents a summary + in `Virtualenv's bin/activate is Doing It Wrong + `_, and proposes alternatives, + followed by comments from other developers. + + However, we prefer to keep things simple. ``$VENV/bin/pip`` is already + UNIX-y. The few extra key strokes are worth avoiding the mess altogether. + .. index:: single: installing on Windows @@ -227,6 +245,9 @@ After installing Python as described previously in c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ " +.. note:: See the note above for :ref:`Why use $VENV/bin/pip instead of source + bin/activate, then pip `. + What Gets Installed ------------------- diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index b2dec77e9..39b4cafb3 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -59,7 +59,9 @@ show only UNIX commands. .. seealso:: See also: :ref:`Quick Tutorial section on Requirements `, - :ref:`installing_unix`, :ref:`Before You Install `, and + :ref:`installing_unix`, :ref:`Before You Install `, + :ref:`Why use $VENV/bin/pip instead of source bin/activate, then pip + `, and :ref:`Installing Pyramid on a Windows System `. diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index 1de9a8acf..afa8ed104 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -179,6 +179,9 @@ time of its release. # Windows c:\> %VENV%\Scripts\pip install --upgrade pip setuptools +.. seealso:: See also :ref:`Why use $VENV/bin/pip instead of source + bin/activate, then pip `. + .. _install-pyramid: -- cgit v1.2.3 From b0576535de6d0e0cec4be22528d5df6947e2446d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 25 Sep 2016 11:30:54 -0400 Subject: clarify what changed in 1.6 with ``config.add_notfound_view`` --- pyramid/config/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 5cb3f5099..542f541f0 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1546,6 +1546,10 @@ class ViewsConfiguratorMixin(object): be used` for the redirect response if a slash-appended route is found. .. versionchanged:: 1.6 + The ``append_slash`` argument was modified to allow any object that + implements the ``IResponse`` interface to specify the response class + used when a redirect is performed. + .. versionadded:: 1.3 """ for arg in ( -- cgit v1.2.3 From 781cf1a5d685daf21ab7b885fda4ab21b05ce940 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 26 Sep 2016 00:18:34 -0700 Subject: Revise Why use venv/bin/pip instead of source bin/activate then pip? - Closes #2610 --- docs/narr/install.rst | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 7b5d8361b..570cb2285 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -199,15 +199,20 @@ After installing Python as described previously in :ref:`for-mac-os-x-users` or .. note:: Why use ``$VENV/bin/pip`` instead of ``source bin/activate``, then ``pip``? - Although the latter method requires fewer key strokes to issue commands once - invoked, there are numerous reasons why one should avoid using ``activate`` - within a virtual environment. Michael F. Lamb (datagrok) presents a summary - in `Virtualenv's bin/activate is Doing It Wrong - `_, and proposes alternatives, - followed by comments from other developers. - - However, we prefer to keep things simple. ``$VENV/bin/pip`` is already - UNIX-y. The few extra key strokes are worth avoiding the mess altogether. + ``$VENV/bin/pip`` clearly specifies that ``pip`` is run from within the + virtual environment and not at the system level. + + ``activate`` drops turds into the user's shell environment, leaving them + vulnerable to executing commands in the wrong context. ``deactivate`` might + not correctly restore previous shell environment variables. + + Although using ``source bin/activate``, then ``pip``, requires fewer key + strokes to issue commands once invoked, there are other things to consider. + Michael F. Lamb (datagrok) presents a summary in `Virtualenv's bin/activate + is Doing It Wrong `_. + + Ultimately we prefer to keep things clear and simple, so we use + ``$VENV/bin/pip``. .. index:: -- cgit v1.2.3