diff options
Diffstat (limited to 'docs')
38 files changed, 267 insertions, 107 deletions
diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst index 19d08618b..57f32327a 100644 --- a/docs/api/authentication.rst +++ b/docs/api/authentication.rst @@ -34,5 +34,10 @@ Helper Classes .. autoclass:: AuthTktCookieHelper :members: + .. autoclass:: HTTPBasicCredentials + :members: +Helper Functions +~~~~~~~~~~~~~~~~ + .. autofunction:: extract_http_basic_credentials 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), } 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 diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 49ef29d3f..b22b31bf9 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() @@ -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: @@ -1639,7 +1654,8 @@ the user-defined :term:`view callable`: Enforce the ``permission`` defined on the view. This element is a no-op if no permission is defined. Note there will always be a permission defined if a default permission was assigned via - :meth:`pyramid.config.Configurator.set_default_permission`. + :meth:`pyramid.config.Configurator.set_default_permission` unless the + view is an :term:`exception view`. This element will also output useful debugging information when ``pyramid.debug_authorization`` is enabled. @@ -1649,7 +1665,8 @@ the user-defined :term:`view callable`: Used to check the CSRF token provided in the request. This element is a no-op if ``require_csrf`` view option is not ``True``. Note there will always be a ``require_csrf`` option if a default value was assigned via - :meth:`pyramid.config.Configurator.set_default_csrf_options`. + :meth:`pyramid.config.Configurator.set_default_csrf_options` unless + the view is an :term:`exception view`. ``owrapped_view`` @@ -1695,6 +1712,8 @@ around monitoring and security. In order to register a custom :term:`view deriver`, you should create a callable that conforms to the :class:`pyramid.interfaces.IViewDeriver` interface, and then register it with your application using :meth:`pyramid.config.Configurator.add_view_deriver`. +The callable should accept the ``view`` to be wrapped and the ``info`` object +which is an instance of :class:`pyramid.interfaces.IViewDeriverInfo`. For example, below is a callable that can provide timing information for the view pipeline: @@ -1745,6 +1764,21 @@ View derivers are unique in that they have access to most of the options passed to :meth:`pyramid.config.Configurator.add_view` in order to decide what to do, and they have a chance to affect every view in the application. +.. _exception_view_derivers: + +Exception Views and View Derivers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A :term:`view deriver` has the opportunity to wrap any view, including +an :term:`exception view`. In general this is fine, but certain view derivers +may wish to avoid doing certain things when handling exceptions. For example, +the ``csrf_view`` and ``secured_view`` built-in view derivers will not perform +security checks on exception views unless explicitly told to do so. + +You can check for ``info.exception_only`` on the +:class:`pyramid.interfaces.IViewDeriverInfo` object when wrapping the view +to determine whether you are wrapping an exception view or a normal view. + Ordering View Derivers ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/narr/install.rst b/docs/narr/install.rst index c59ced2a5..570cb2285 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 @@ -192,6 +191,29 @@ 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``? + + ``$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 <https://gist.github.com/datagrok/2199506>`_. + + Ultimately we prefer to keep things clear and simple, so we use + ``$VENV/bin/pip``. + .. index:: single: installing on Windows @@ -228,6 +250,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 <venv-bin-pip-vs-source-bin-activate>`. + What Gets Installed ------------------- 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/narr/sessions.rst b/docs/narr/sessions.rst index a1319e45f..5b24201a9 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -157,6 +157,12 @@ The following session factories exist at the time of this writing. ======================= ======= ============================= Session Factory Backend Description ======================= ======= ============================= +pyramid_nacl_session_ PyNaCl_ Defines an encrypting, + pickle-based cookie + serializer, using PyNaCl to + generate the symmetric + encryption for the cookie + state. pyramid_redis_sessions_ Redis_ Server-side session library for Pyramid, using Redis for storage. @@ -165,6 +171,9 @@ pyramid_beaker_ Beaker_ Session factory for Pyramid sessioning system. ======================= ======= ============================= +.. _pyramid_nacl_session: https://pypi.python.org/pypi/pyramid_nacl_session +.. _PyNaCl: https://pynacl.readthedocs.io/en/latest/secret/ + .. _pyramid_redis_sessions: https://pypi.python.org/pypi/pyramid_redis_sessions .. _Redis: http://redis.io/ 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/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index cd5b8feb0..7cb8e0306 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -34,7 +34,7 @@ determine the set of circumstances which must be true for the view callable to be invoked. A view configuration statement is made about information present in the -:term:`context` resource and the :term:`request`. +:term:`context` resource (or exception) and the :term:`request`. View configuration is performed in one of two ways: @@ -306,9 +306,26 @@ configured view. represented class or if the :term:`context` resource provides the represented interface; it is otherwise false. + It is possible to pass an exception class as the context if your context may + subclass an exception. In this case *two* views will be registered. One + will match normal incoming requests, and the other will match as an + :term:`exception view` which only occurs when an exception is raised during + the normal request processing pipeline. + If ``context`` is not supplied, the value ``None``, which matches any resource, is used. +``exception_only`` + + When this value is ``True``, the ``context`` argument must be a subclass of + ``Exception``. This flag indicates that only an :term:`exception view` should + be created, and that this view should not match if the traversal + :term:`context` matches the ``context`` argument. If the ``context`` is a + subclass of ``Exception`` and this value is ``False`` (the default), then a + view will be registered to match the traversal :term:`context` as well. + + .. versionadded:: 1.8 + ``route_name`` If ``route_name`` is supplied, the view callable will be invoked only when the named route has matched. diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 770d27919..ab139ea19 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -262,10 +262,16 @@ specialized views as described in :ref:`special_exceptions_in_callables` can also be used by application developers to convert arbitrary exceptions to responses. -To register a view that should be called whenever a particular exception is -raised from within :app:`Pyramid` view code, use the exception class (or one of -its superclasses) as the :term:`context` of a view configuration which points -at a view callable for which you'd like to generate a response. +To register an :term:`exception view` that should be called whenever a +particular exception is raised from within :app:`Pyramid` view code, use +:meth:`pyramid.config.Configurator.add_exception_view` to register a view +configuration which matches the exception (or a subclass of the exception) and +points at a view callable for which you'd like to generate a response. The +exception will be passed as the ``context`` argument to any +:term:`view predicate` registered with the view, as well as to the view itself. +For convenience a new decorator exists, +:class:`pyramid.views.exception_view_config`, which may be used to easily +register exception views. For example, given the following exception class in a module named ``helloworld.exceptions``: @@ -277,17 +283,16 @@ For example, given the following exception class in a module named def __init__(self, msg): self.msg = msg - You can wire a view callable to be called whenever any of your *other* code raises a ``helloworld.exceptions.ValidationFailure`` exception: .. code-block:: python :linenos: - from pyramid.view import view_config + from pyramid.view import exception_view_config from helloworld.exceptions import ValidationFailure - @view_config(context=ValidationFailure) + @exception_view_config(ValidationFailure) def failed_validation(exc, request): response = Response('Failed validation: %s' % exc.msg) response.status_int = 500 @@ -308,7 +313,7 @@ view registration: from pyramid.view import view_config from helloworld.exceptions import ValidationFailure - @view_config(context=ValidationFailure, route_name='home') + @exception_view_config(ValidationFailure, route_name='home') def failed_validation(exc, request): response = Response('Failed validation: %s' % exc.msg) response.status_int = 500 @@ -327,14 +332,21 @@ which have a name will be ignored. .. note:: - Normal (i.e., non-exception) views registered against a context resource type - which inherits from :exc:`Exception` will work normally. When an exception - view configuration is processed, *two* views are registered. One as a - "normal" view, the other as an "exception" view. This means that you can use - an exception as ``context`` for a normal view. + In most cases, you should register an :term:`exception view` by using + :meth:`pyramid.config.Configurator.add_exception_view`. However, it is + possible to register "normal" (i.e., non-exception) views against a context + resource type which inherits from :exc:`Exception` (i.e., + ``config.add_view(context=Exception)``). When the view configuration is + processed, *two* views are registered. One as a "normal" view, the other + as an :term:`exception view`. This means that you can use an exception as + ``context`` for a normal view. + + The view derivers that wrap these two views may behave differently. + See :ref:`exception_view_derivers` for more information about this. Exception views can be configured with any view registration mechanism: -``@view_config`` decorator or imperative ``add_view`` styles. +``@exception_view_config`` decorator or imperative ``add_exception_view`` +styles. .. note:: diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index fb957be1c..451830687 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -52,14 +52,16 @@ 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. .. seealso:: See also: :ref:`Quick Tutorial section on Requirements <qtut_requirements>`, - :ref:`installing_unix`, :ref:`Before You Install <installing_chapter>`, and + :ref:`installing_unix`, :ref:`Before You Install <installing_chapter>`, + :ref:`Why use $VENV/bin/pip instead of source bin/activate, then pip + <venv-bin-pip-vs-source-bin-activate>`, and :ref:`Installing Pyramid on a Windows System <installing_windows>`. @@ -502,12 +504,12 @@ Pyramid's ``pcreate`` command can list the available scaffolds: .. code-block:: bash - $ pcreate --list + $ $VENV/bin/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 @@ -515,7 +517,7 @@ that scaffold to make our project: .. code-block:: bash - $ pcreate --scaffold pyramid_jinja2_starter hello_world + $ $VENV/bin/pcreate --scaffold pyramid_jinja2_starter hello_world We next use the normal Python command to set up our package for development: 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/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst index acff97f3b..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. @@ -34,6 +34,18 @@ Steps .. code-block:: bash $ cd ..; cp -r view_classes authentication; cd authentication + +#. Add ``bcrypt`` as a dependency in ``authentication/setup.py``: + + .. literalinclude:: authentication/setup.py + :language: python + :emphasize-lines: 5-6 + :linenos: + +#. We can now install our project in development mode: + + .. code-block:: bash + $ $VENV/bin/pip install -e . #. Put the security hash in the ``authentication/development.ini`` @@ -96,8 +108,8 @@ 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 <authentication_module>` policy. We enabled it in our configuration and @@ -108,6 +120,20 @@ 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 @@ -125,4 +151,5 @@ Extra credit request? Use ``import pdb; pdb.set_trace()`` to answer this. .. seealso:: See also :ref:`security_chapter`, - :ref:`AuthTktAuthenticationPolicy <authentication_module>`. + :ref:`AuthTktAuthenticationPolicy <authentication_module>`, `bcrypt + <https://pypi.python.org/pypi/bcrypt>`_ 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) 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: diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index 958347a90..afa8ed104 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 @@ -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 <venv-bin-pip-vs-source-bin-activate>`. + .. _install-pyramid: 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 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: 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 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: 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): 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/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/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/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/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/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/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/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/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", 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 |
