summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt229
-rw-r--r--CONTRIBUTORS.txt4
-rw-r--r--TODO.txt6
-rw-r--r--docs/api/paster.rst2
-rw-r--r--docs/conf.py2
-rw-r--r--docs/narr/hooks.rst13
-rw-r--r--docs/narr/install.rst15
-rw-r--r--docs/narr/introspector.rst17
-rw-r--r--docs/tutorials/wiki/basiclayout.rst2
-rw-r--r--docs/tutorials/wiki/src/authorization/development.ini28
-rw-r--r--docs/tutorials/wiki/src/authorization/production.ini17
-rw-r--r--docs/tutorials/wiki/src/authorization/setup.py7
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt6
-rw-r--r--docs/tutorials/wiki/src/basiclayout/development.ini30
-rw-r--r--docs/tutorials/wiki/src/basiclayout/production.ini17
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.py2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt4
-rw-r--r--docs/tutorials/wiki/src/models/development.ini28
-rw-r--r--docs/tutorials/wiki/src/models/production.ini17
-rw-r--r--docs/tutorials/wiki/src/models/setup.py2
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt6
-rw-r--r--docs/tutorials/wiki/src/tests/development.ini28
-rw-r--r--docs/tutorials/wiki/src/tests/production.ini17
-rw-r--r--docs/tutorials/wiki/src/tests/setup.py7
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt6
-rw-r--r--docs/tutorials/wiki/src/views/development.ini30
-rw-r--r--docs/tutorials/wiki/src/views/production.ini17
-rw-r--r--docs/tutorials/wiki/src/views/setup.py8
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt6
-rw-r--r--docs/tutorials/wiki/tests.rst2
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst6
-rw-r--r--docs/tutorials/wiki2/src/authorization/README.txt13
-rw-r--r--docs/tutorials/wiki2/src/authorization/development.ini20
-rw-r--r--docs/tutorials/wiki2/src/authorization/setup.py9
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/README.txt13
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/development.ini20
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/production.ini12
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/setup.py6
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/views.py25
-rw-r--r--docs/tutorials/wiki2/src/models/README.txt13
-rw-r--r--docs/tutorials/wiki2/src/models/development.ini20
-rw-r--r--docs/tutorials/wiki2/src/models/setup.py6
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/views.py25
-rw-r--r--docs/tutorials/wiki2/src/tests/README.txt13
-rw-r--r--docs/tutorials/wiki2/src/tests/development.ini20
-rw-r--r--docs/tutorials/wiki2/src/tests/setup.py7
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views.py13
-rw-r--r--docs/tutorials/wiki2/src/views/README.txt13
-rw-r--r--docs/tutorials/wiki2/src/views/development.ini20
-rw-r--r--docs/tutorials/wiki2/src/views/setup.py6
-rw-r--r--docs/whatsnew-1.3.rst12
-rw-r--r--docs/whatsnew-1.4.rst58
-rw-r--r--pyramid/config/__init__.py11
-rw-r--r--pyramid/config/adapters.py78
-rw-r--r--pyramid/config/assets.py2
-rw-r--r--pyramid/config/factories.py8
-rw-r--r--pyramid/config/i18n.py3
-rw-r--r--pyramid/config/predicates.py9
-rw-r--r--pyramid/config/rendering.py2
-rw-r--r--pyramid/config/security.py2
-rw-r--r--pyramid/config/testing.py2
-rw-r--r--pyramid/config/util.py119
-rw-r--r--pyramid/config/views.py143
-rw-r--r--pyramid/paster.py22
-rw-r--r--pyramid/request.py2
-rw-r--r--pyramid/router.py2
-rw-r--r--pyramid/scripts/__init__.py1
-rw-r--r--pyramid/scripts/common.py15
-rw-r--r--pyramid/scripts/prequest.py5
-rw-r--r--pyramid/scripts/proutes.py5
-rw-r--r--pyramid/scripts/pserve.py47
-rw-r--r--pyramid/scripts/pshell.py4
-rw-r--r--pyramid/scripts/ptweens.py3
-rw-r--r--pyramid/scripts/pviews.py9
-rw-r--r--pyramid/tests/pkgs/eventonly/__init__.py64
-rw-r--r--pyramid/tests/test_config/test_adapters.py9
-rw-r--r--pyramid/tests/test_config/test_init.py2
-rw-r--r--pyramid/tests/test_config/test_predicates.py12
-rw-r--r--pyramid/tests/test_config/test_util.py214
-rw-r--r--pyramid/tests/test_config/test_views.py213
-rw-r--r--pyramid/tests/test_integration.py12
-rw-r--r--pyramid/tests/test_paster.py35
-rw-r--r--pyramid/tests/test_request.py7
-rw-r--r--pyramid/tests/test_router.py17
-rw-r--r--pyramid/tests/test_scripts/test_common.py13
-rw-r--r--pyramid/tests/test_scripts/test_prequest.py4
-rw-r--r--pyramid/tests/test_scripts/test_proutes.py22
-rw-r--r--pyramid/tests/test_scripts/test_pserve.py43
-rw-r--r--pyramid/tests/test_scripts/test_pviews.py2
-rw-r--r--pyramid/tests/test_util.py31
-rw-r--r--pyramid/tests/test_view.py29
-rw-r--r--pyramid/util.py67
-rw-r--r--pyramid/view.py9
-rw-r--r--setup.py2
95 files changed, 1625 insertions, 603 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index d57444ad0..841d2ebec 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,12 +1,193 @@
-Next release
-============
+1.4b1 (2012-11-21)
+==================
+
+Features
+--------
+
+- Small microspeed enhancement which anticipates that a
+ ``pyramid.response.Response`` object is likely to be returned from a view.
+ Some code is shortcut if the class of the object returned by a view is this
+ class. A similar microoptimization was done to
+ ``pyramid.request.Request.is_response``.
+
+- Make it possible to use variable arguments on ``p*`` commands (``pserve``,
+ ``pshell``, ``pviews``, etc) in the form ``a=1 b=2`` so you can fill in
+ values in parameterized ``.ini`` file, e.g. ``pshell etc/development.ini
+ http_port=8080``. See https://github.com/Pylons/pyramid/pull/714
+
+- A somewhat advanced and obscure feature of Pyramid event handlers is their
+ ability to handle "multi-interface" notifications. These notifications have
+ traditionally presented multiple objects to the subscriber callable. For
+ instance, if an event was sent by code like this::
+
+ registry.notify(event, context)
+
+ In the past, in order to catch such an event, you were obligated to write and
+ register an event subscriber that mentioned both the event and the context in
+ its argument list::
+
+ @subscriber([SomeEvent, SomeContextType])
+ def asubscriber(event, context):
+ pass
+
+ In many subscriber callables registered this way, it was common for the logic
+ in the subscriber callable to completely ignore the second and following
+ arguments (e.g. ``context`` in the above example might be ignored), because
+ they usually existed as attributes of the event anyway. You could usually
+ get the same value by doing ``event.context`` or similar.
+
+ The fact that you needed to put an extra argument which you usually ignored
+ in the subscriber callable body was only a minor annoyance until we added
+ "subscriber predicates", used to narrow the set of circumstances under which
+ a subscriber will be executed, in a prior 1.4 alpha release. Once those were
+ added, the annoyance was escalated, because subscriber predicates needed to
+ accept the same argument list and arity as the subscriber callables that they
+ were configured against. So, for example, if you had these two subscriber
+ registrations in your code::
+
+ @subscriber([SomeEvent, SomeContextType])
+ def asubscriber(event, context):
+ pass
+
+ @subscriber(SomeOtherEvent)
+ def asubscriber(event):
+ pass
+
+ And you wanted to use a subscriber predicate::
+
+ @subscriber([SomeEvent, SomeContextType], mypredicate=True)
+ def asubscriber1(event, context):
+ pass
+
+ @subscriber(SomeOtherEvent, mypredicate=True)
+ def asubscriber2(event):
+ pass
+
+ If an existing ``mypredicate`` subscriber predicate had been written in such
+ a way that it accepted only one argument in its ``__call__``, you could not
+ use it against a subscription which named more than one interface in its
+ subscriber interface list. Similarly, if you had written a subscriber
+ predicate that accepted two arguments, you couldn't use it against a
+ registration that named only a single interface type.
+
+ For example, if you created this predicate::
+
+ class MyPredicate(object):
+ # portions elided...
+ def __call__(self, event):
+ return self.val == event.context.foo
+
+ It would not work against a multi-interface-registered subscription, so in
+ the above example, when you attempted to use it against ``asubscriber1``, it
+ would fail at runtime with a TypeError, claiming something was attempting to
+ call it with too many arguments.
+
+ To hack around this limitation, you were obligated to design the
+ ``mypredicate`` predicate to expect to receive in its ``__call__`` either a
+ single ``event`` argument (a SomeOtherEvent object) *or* a pair of arguments
+ (a SomeEvent object and a SomeContextType object), presumably by doing
+ something like this::
+
+ class MyPredicate(object):
+ # portions elided...
+ def __call__(self, event, context=None):
+ return self.val == event.context.foo
+
+ This was confusing and bad.
+
+ In order to allow people to ignore unused arguments to subscriber callables
+ and to normalize the relationship between event subscribers and subscriber
+ predicates, we now allow both subscribers and subscriber predicates to accept
+ only a single ``event`` argument even if they've been subscribed for
+ notifications that involve multiple interfaces. Subscribers and subscriber
+ predicates that accept only one argument will receive the first object passed
+ to ``notify``; this is typically (but not always) the event object. The
+ other objects involved in the subscription lookup will be discarded. You can
+ now write an event subscriber that accepts only ``event`` even if it
+ subscribes to multiple interfaces::
+
+ @subscriber([SomeEvent, SomeContextType])
+ def asubscriber(event):
+ # this will work!
+
+ This prevents you from needing to match the subscriber callable parameters to
+ the subscription type unnecessarily, especially when you don't make use of
+ any argument in your subscribers except for the event object itself.
+
+ Note, however, that if the event object is not the first
+ object in the call to ``notify``, you'll run into trouble. For example, if
+ notify is called with the context argument first::
+
+ registry.notify(context, event)
+
+ You won't be able to take advantage of the event-only feature. It will
+ "work", but the object received by your event handler won't be the event
+ object, it will be the context object, which won't be very useful::
+
+ @subscriber([SomeContextType, SomeEvent])
+ def asubscriber(event):
+ # bzzt! you'll be getting the context here as ``event``, and it'll
+ # be useless
+
+ Existing multiple-argument subscribers continue to work without issue, so you
+ should continue use those if your system notifies using multiple interfaces
+ and the first interface is not the event interface. For example::
+
+ @subscriber([SomeContextType, SomeEvent])
+ def asubscriber(context, event):
+ # this will still work!
+
+ The event-only feature makes it possible to use a subscriber predicate that
+ accepts only a request argument within both multiple-interface subscriber
+ registrations and single-interface subscriber registrations. You needn't
+ make slightly different variations of predicates depending on the
+ subscription type arguments. Instead, just write all your subscriber
+ predicates so they only accept ``event`` in their ``__call__`` and they'll be
+ useful across all registrations for subscriptions that use an event as their
+ first argument, even ones which accept more than just ``event``.
+
+ However, the same caveat applies to predicates as to subscriber callables: if
+ you're subscribing to a multi-interface event, and the first interface is not
+ the event interface, the predicate won't work properly. In such a case,
+ you'll need to match the predicate ``__call__`` argument ordering and
+ composition to the ordering of the interfaces. For example, if the
+ registration for the subscription uses ``[SomeContext, SomeEvent]``, you'll
+ need to reflect that in the ordering of the parameters of the predicate's
+ ``__call__`` method::
+
+ def __call__(self, context, event):
+ return event.request.path.startswith(self.val)
+
+ tl;dr: 1) When using multi-interface subscriptions, always use the event type
+ as the first subscription registration argument and 2) When 1 is true, use
+ only ``event`` in your subscriber and subscriber predicate parameter lists,
+ no matter how many interfaces the subscriber is notified with. This
+ combination will result in the maximum amount of reusability of subscriber
+ predicates and the least amount of thought on your part. Drink responsibly.
+
+Bug Fixes
+---------
+
+- A failure when trying to locate the attribute ``__text__`` on route and view
+ predicates existed when the ``debug_routematch`` setting was true or when the
+ ``pviews`` command was used. See https://github.com/Pylons/pyramid/pull/727
+
+Documentation
+-------------
+
+- Sync up tutorial source files with the files that are rendered by the
+ scaffold that each uses.
+
+1.4a4 (2012-11-14)
+==================
Features
--------
- ``pyramid.authentication.AuthTktAuthenticationPolicy`` has been updated to
support newer hashing algorithms such as ``sha512``. Existing applications
- should consider updating if possible.
+ should consider updating if possible for improved security over the default
+ md5 hashing.
- Added an ``effective_principals`` route and view predicate.
@@ -21,18 +202,11 @@ Features
- Slightly better debug logging from
``pyramid.authentication.RepozeWho1AuthenticationPolicy``.
-- ``pyramid.security.view_execution_permitted`` used to return `True` if no
+- ``pyramid.security.view_execution_permitted`` used to return ``True`` if no
view could be found. It now raises a ``TypeError`` exception in that case, as
it doesn't make sense to assert that a nonexistent view is
execution-permitted. See https://github.com/Pylons/pyramid/issues/299.
-- Get rid of shady monkeypatching of ``pyramid.request.Request`` and
- ``pyramid.response.Response`` done within the ``__init__.py`` of Pyramid.
- Webob no longer relies on this being done. Instead, the ResponseClass
- attribute of the Pyramid Request class is assigned to the Pyramid response
- class; that's enough to satisfy WebOb and behave as it did before with the
- monkeypatching.
-
- Allow a ``_depth`` argument to ``pyramid.view.view_config``, which will
permit limited composition reuse of the decorator by other software that
wants to provide custom decorators that are much like view_config.
@@ -57,14 +231,30 @@ Bug Fixes
attribute of the request. It no longer fails in this case. See
https://github.com/Pylons/pyramid/issues/700
+- Be more tolerant of potential error conditions in ``match_param`` and
+ ``physical_path`` predicate implementations; instead of raising an exception,
+ return False.
+
+- ``pyramid.view.render_view`` was not functioning properly under Python 3.x
+ due to a byte/unicode discrepancy. See
+ http://github.com/Pylons/pyramid/issues/721
+
Deprecations
------------
-- ``pyramid.authentication.AuthTktAuthenticationPolicy`` will emit a warning
- if an application is using the policy without explicitly setting the
- ``hashalg``. This is because the default is "md5" which is considered
- insecure. If you really want "md5" then you must specify it explicitly to
- get rid of the warning.
+- ``pyramid.authentication.AuthTktAuthenticationPolicy`` will emit a warning if
+ an application is using the policy without explicitly passing a ``hashalg``
+ argument. This is because the default is "md5" which is considered
+ theoretically subject to collision attacks. If you really want "md5" then you
+ must specify it explicitly to get rid of the warning.
+
+Documentation
+-------------
+
+- All of the tutorials that use
+ ``pyramid.authentication.AuthTktAuthenticationPolicy`` now explicitly pass
+ ``sha512`` as a ``hashalg`` argument.
+
Internals
---------
@@ -77,6 +267,13 @@ Internals
because that package should never be imported from non-Pyramid code.
TopologicalSorter is still not an API, but may become one.
+- Get rid of shady monkeypatching of ``pyramid.request.Request`` and
+ ``pyramid.response.Response`` done within the ``__init__.py`` of Pyramid.
+ Webob no longer relies on this being done. Instead, the ResponseClass
+ attribute of the Pyramid Request class is assigned to the Pyramid response
+ class; that's enough to satisfy WebOb and behave as it did before with the
+ monkeypatching.
+
1.4a3 (2012-10-26)
==================
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index d03da3e62..971c172f8 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -188,3 +188,7 @@ Contributors
- Domen Kozar, 2012/09/11
- David Gay, 2012/09/16
+
+- Robert Jackiewicz, 2012/11/12
+
+- John Anderson, 2012/11/14
diff --git a/TODO.txt b/TODO.txt
index 46edd8c6b..7a3561494 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -4,6 +4,9 @@ Pyramid TODOs
Nice-to-Have
------------
+- config.set_registry_attr with conflict detection... make sure the attr is
+ added before a commit, but register an action so a conflict can be detected.
+
- Provide the presumed renderer name to the called view as an attribute of
the request.
@@ -175,6 +178,3 @@ Probably Bad Ideas
- _fix_registry should dictify the registry being fixed.
-- config.set_registry_attr (with conflict detection)... bad idea because it
- won't take effect until after a commit and folks will be confused by that.
-
diff --git a/docs/api/paster.rst b/docs/api/paster.rst
index 3f7a1c364..bde128e05 100644
--- a/docs/api/paster.rst
+++ b/docs/api/paster.rst
@@ -7,7 +7,7 @@
.. autofunction:: bootstrap
- .. autofunction:: get_app(config_uri, name=None)
+ .. autofunction:: get_app(config_uri, name=None, options=None)
.. autofunction:: get_appsettings(config_uri, name=None)
diff --git a/docs/conf.py b/docs/conf.py
index 9bda4c798..3dc09d95d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -81,7 +81,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year
# other places throughout the built documents.
#
# The short X.Y version.
-version = '1.4a3'
+version = '1.4b1'
# The full version, including alpha/beta/rc tags.
release = version
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 96fa77a07..ea75e5fe4 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -180,11 +180,14 @@ as a forbidden view:
config.scan()
Like any other view, the forbidden view must accept at least a ``request``
-parameter, or both ``context`` and ``request``. The ``context`` (available
-as ``request.context`` if you're using the request-only view argument
-pattern) is the context found by the router when the view invocation was
-denied. The ``request`` is the current :term:`request` representing the
-denied action.
+parameter, or both ``context`` and ``request``. If a forbidden view
+callable accepts both ``context`` and ``request``, the HTTP Exception is passed
+as context. The ``context`` as found by the router when view was
+denied (that you normally would expect) is available as
+``request.context``. The ``request`` is the current :term:`request`
+representing the denied action.
+
+
Here's some sample code that implements a minimal forbidden view:
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index e8482a289..882e76f11 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -243,6 +243,21 @@ Once you've got setuptools or distribute installed, you should install the
:term:`virtualenv` package. To install the :term:`virtualenv` package into
your setuptools-enabled Python interpreter, use the ``easy_install`` command.
+.. warning::
+
+ Even though Python 3.3 and better comes with ``pyvenv`` out of the box,
+ which is similar to ``virtualenv``, we suggest using ``virtualenv`` instead.
+ ``virtualenv`` works on well Python 3.3. This isn't a recommendation made
+ for technical reasons, it's one made because it's not feasible for the
+ authors of this guide to explain setup using multiple virtual environment
+ systems. We are aiming to not need to make the installation documentation
+ Turing-complete.
+
+ ``pyvenv`` will work fine. However, if you use ``pyvenv`` instead of
+ ``virtualenv``, you'll need to understand how to install software such as
+ ``distribute`` into the virtual environment manually, which this guide does
+ not cover.
+
.. code-block:: text
$ easy_install virtualenv
diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst
index b88f3f0c8..bd81fb56a 100644
--- a/docs/narr/introspector.rst
+++ b/docs/narr/introspector.rst
@@ -130,6 +130,23 @@ introspectables in categories not described here.
A sequence of interfaces (or classes) that are subscribed to (the
resolution of the ``ifaces`` argument passed to ``add_subscriber``).
+ ``derived_subscriber``
+
+ A wrapper around the subscriber used internally by the system so it can
+ call it with more than one argument if your original subscriber accepts
+ only one.
+
+ ``predicates``
+
+ The predicate objects created as the result of passing predicate arguments
+ to ``add_susbcriber``
+
+ ``derived_predicates``
+
+ Wrappers around the predicate objects created as the result of passing
+ predicate arguments to ``add_susbcriber`` (to be used when predicates take
+ only one value but must be passed more than one).
+
``response adapters``
Each introspectable in the ``response adapters`` category represents a call
diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst
index f6050f391..033dcb28c 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -165,7 +165,7 @@ Configuration in ``development.ini``
The ``development.ini`` (in the tutorial :term:`project` directory, as
opposed to the tutorial :term:`package` directory) looks like this:
-.. literalinclude:: src/views/development.ini
+.. literalinclude:: src/basiclayout/development.ini
:language: ini
Note the existence of an ``[app:main]`` section which specifies our WSGI
diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini
index 996caa741..72bd22e54 100644
--- a/docs/tutorials/wiki/src/authorization/development.ini
+++ b/docs/tutorials/wiki/src/authorization/development.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -13,15 +19,26 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
-keys = root
+keys = root, tutorial
[handlers]
keys = console
@@ -33,6 +50,11 @@ keys = generic
level = INFO
handlers = console
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -41,5 +63,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini
index ca8107802..d9bf27c42 100644
--- a/docs/tutorials/wiki/src/authorization/production.ini
+++ b/docs/tutorials/wiki/src/authorization/production.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -12,12 +18,19 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial
@@ -45,5 +58,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py
index d83f14400..1d2d690fc 100644
--- a/docs/tutorials/wiki/src/authorization/setup.py
+++ b/docs/tutorials/wiki/src/authorization/setup.py
@@ -22,9 +22,8 @@ setup(name='tutorial',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Intended Audience :: Developers",
- "Framework :: Pylons",
"Programming Language :: Python",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
@@ -35,8 +34,8 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- install_requires=requires,
- tests_require=requires,
+ install_requires = requires,
+ tests_require= requires,
test_suite="tutorial",
entry_points = """\
[paste.app_factory]
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
index 3597c679b..84824f605 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
@@ -6,9 +6,9 @@
<meta name="keywords" content="python web application" />
<meta name="description" content="pyramid web application" />
<link rel="shortcut icon" href="/static/favicon.ico" />
+ <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" />
- <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<!--[if lte IE 6]>
<link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" />
<![endif]-->
@@ -41,7 +41,7 @@
<h2>Pyramid links</h2>
<ul class="links">
<li>
- <a href="http://pylonsproject.org">Pylons Website</a>
+ <a href="http://pylonsproject.org/">Pylons Website</a>
</li>
<li>
<a href="http://docs.pylonsproject.org/projects/pyramid/current/#narrative-documentation">Narrative Documentation</a>
@@ -70,7 +70,7 @@
</div>
</div>
<div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
+ <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
</div>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini
index f637ebaa6..72bd22e54 100644
--- a/docs/tutorials/wiki/src/basiclayout/development.ini
+++ b/docs/tutorials/wiki/src/basiclayout/development.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -13,15 +19,26 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
-keys = root
+keys = root, tutorial
[handlers]
keys = console
@@ -33,6 +50,11 @@ keys = generic
level = INFO
handlers = console
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -40,6 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini
index ca8107802..d9bf27c42 100644
--- a/docs/tutorials/wiki/src/basiclayout/production.ini
+++ b/docs/tutorials/wiki/src/basiclayout/production.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -12,12 +18,19 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial
@@ -45,5 +58,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py
index bd26f8efc..647c3a638 100644
--- a/docs/tutorials/wiki/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki/src/basiclayout/setup.py
@@ -22,7 +22,7 @@ setup(name='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
- "Framework :: Pylons",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
index 557e071ed..84824f605 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
@@ -6,9 +6,9 @@
<meta name="keywords" content="python web application" />
<meta name="description" content="pyramid web application" />
<link rel="shortcut icon" href="/static/favicon.ico" />
+ <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" />
- <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<!--[if lte IE 6]>
<link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" />
<![endif]-->
@@ -70,7 +70,7 @@
</div>
</div>
<div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
+ <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
</div>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini
index 996caa741..72bd22e54 100644
--- a/docs/tutorials/wiki/src/models/development.ini
+++ b/docs/tutorials/wiki/src/models/development.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -13,15 +19,26 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
-keys = root
+keys = root, tutorial
[handlers]
keys = console
@@ -33,6 +50,11 @@ keys = generic
level = INFO
handlers = console
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -41,5 +63,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini
index ca8107802..d9bf27c42 100644
--- a/docs/tutorials/wiki/src/models/production.ini
+++ b/docs/tutorials/wiki/src/models/production.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -12,12 +18,19 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial
@@ -45,5 +58,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py
index bd26f8efc..647c3a638 100644
--- a/docs/tutorials/wiki/src/models/setup.py
+++ b/docs/tutorials/wiki/src/models/setup.py
@@ -22,7 +22,7 @@ setup(name='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
- "Framework :: Pylons",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
index 3597c679b..84824f605 100644
--- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
@@ -6,9 +6,9 @@
<meta name="keywords" content="python web application" />
<meta name="description" content="pyramid web application" />
<link rel="shortcut icon" href="/static/favicon.ico" />
+ <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" />
- <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<!--[if lte IE 6]>
<link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" />
<![endif]-->
@@ -41,7 +41,7 @@
<h2>Pyramid links</h2>
<ul class="links">
<li>
- <a href="http://pylonsproject.org">Pylons Website</a>
+ <a href="http://pylonsproject.org/">Pylons Website</a>
</li>
<li>
<a href="http://docs.pylonsproject.org/projects/pyramid/current/#narrative-documentation">Narrative Documentation</a>
@@ -70,7 +70,7 @@
</div>
</div>
<div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
+ <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
</div>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini
index 996caa741..72bd22e54 100644
--- a/docs/tutorials/wiki/src/tests/development.ini
+++ b/docs/tutorials/wiki/src/tests/development.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -13,15 +19,26 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
-keys = root
+keys = root, tutorial
[handlers]
keys = console
@@ -33,6 +50,11 @@ keys = generic
level = INFO
handlers = console
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -41,5 +63,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki/src/tests/production.ini b/docs/tutorials/wiki/src/tests/production.ini
index ca8107802..d9bf27c42 100644
--- a/docs/tutorials/wiki/src/tests/production.ini
+++ b/docs/tutorials/wiki/src/tests/production.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -12,12 +18,19 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial
@@ -45,5 +58,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py
index 51c638638..a665611bd 100644
--- a/docs/tutorials/wiki/src/tests/setup.py
+++ b/docs/tutorials/wiki/src/tests/setup.py
@@ -23,9 +23,8 @@ setup(name='tutorial',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Intended Audience :: Developers",
- "Framework :: Pylons",
"Programming Language :: Python",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
@@ -36,8 +35,8 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- install_requires=requires,
- tests_require=requires,
+ install_requires = requires,
+ tests_require= requires,
test_suite="tutorial",
entry_points = """\
[paste.app_factory]
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
index 3597c679b..84824f605 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
@@ -6,9 +6,9 @@
<meta name="keywords" content="python web application" />
<meta name="description" content="pyramid web application" />
<link rel="shortcut icon" href="/static/favicon.ico" />
+ <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" />
- <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<!--[if lte IE 6]>
<link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" />
<![endif]-->
@@ -41,7 +41,7 @@
<h2>Pyramid links</h2>
<ul class="links">
<li>
- <a href="http://pylonsproject.org">Pylons Website</a>
+ <a href="http://pylonsproject.org/">Pylons Website</a>
</li>
<li>
<a href="http://docs.pylonsproject.org/projects/pyramid/current/#narrative-documentation">Narrative Documentation</a>
@@ -70,7 +70,7 @@
</div>
</div>
<div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
+ <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
</div>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini
index f637ebaa6..72bd22e54 100644
--- a/docs/tutorials/wiki/src/views/development.ini
+++ b/docs/tutorials/wiki/src/views/development.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -13,15 +19,26 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
-keys = root
+keys = root, tutorial
[handlers]
keys = console
@@ -33,6 +50,11 @@ keys = generic
level = INFO
handlers = console
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -40,6 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/views/production.ini b/docs/tutorials/wiki/src/views/production.ini
index ca8107802..d9bf27c42 100644
--- a/docs/tutorials/wiki/src/views/production.ini
+++ b/docs/tutorials/wiki/src/views/production.ini
@@ -1,5 +1,11 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
+
pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
@@ -12,12 +18,19 @@ pyramid.includes =
tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial
@@ -45,5 +58,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py
index 6f946582c..1d2d690fc 100644
--- a/docs/tutorials/wiki/src/views/setup.py
+++ b/docs/tutorials/wiki/src/views/setup.py
@@ -22,9 +22,8 @@ setup(name='tutorial',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Intended Audience :: Developers",
- "Framework :: Pylons",
"Programming Language :: Python",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
@@ -35,11 +34,12 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- install_requires=requires,
- tests_require=requires,
+ install_requires = requires,
+ tests_require= requires,
test_suite="tutorial",
entry_points = """\
[paste.app_factory]
main = tutorial:main
""",
)
+
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
index 3597c679b..84824f605 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
@@ -6,9 +6,9 @@
<meta name="keywords" content="python web application" />
<meta name="description" content="pyramid web application" />
<link rel="shortcut icon" href="/static/favicon.ico" />
+ <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" />
<link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" />
- <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
<!--[if lte IE 6]>
<link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" />
<![endif]-->
@@ -41,7 +41,7 @@
<h2>Pyramid links</h2>
<ul class="links">
<li>
- <a href="http://pylonsproject.org">Pylons Website</a>
+ <a href="http://pylonsproject.org/">Pylons Website</a>
</li>
<li>
<a href="http://docs.pylonsproject.org/projects/pyramid/current/#narrative-documentation">Narrative Documentation</a>
@@ -70,7 +70,7 @@
</div>
</div>
<div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
+ <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
</div>
</body>
</html>
diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst
index ebb4d93dc..46d009d0f 100644
--- a/docs/tutorials/wiki/tests.rst
+++ b/docs/tutorials/wiki/tests.rst
@@ -62,7 +62,7 @@ Change the ``requires`` list in ``setup.py`` to include ``WebTest``.
.. literalinclude:: src/tests/setup.py
:linenos:
:language: python
- :lines: 9-18
+ :lines: 9-19
After we've added a dependency on WebTest in ``setup.py``, we need to rerun
``setup.py develop`` to get WebTest installed into our virtualenv. Assuming
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index dbd130c36..4f73dc914 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -171,6 +171,12 @@ application. Without being processed by ``scan``, the decorator effectively
does nothing. ``@view_config`` is inert without being detected via a
:term:`scan`.
+The sample ``my_view()`` created by the scaffold uses a ``try:`` and ``except:``
+clause, to detect if there is a problem accessing the project database and
+provide an alternate error response. That response will include the text
+shown at the end of the file, which will be displayed in the browser to
+inform the user about possible actions to take to solve the problem.
+
Content Models with ``models.py``
---------------------------------
diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt
index 6f851e9b7..141851285 100644
--- a/docs/tutorials/wiki2/src/authorization/README.txt
+++ b/docs/tutorials/wiki2/src/authorization/README.txt
@@ -1 +1,14 @@
tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $venv/bin/python setup.py develop
+
+- $venv/bin/initialize_tutorial_db development.ini
+
+- $venv/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini
index eb2f878c5..a9d53b296 100644
--- a/docs/tutorials/wiki2/src/authorization/development.ini
+++ b/docs/tutorials/wiki2/src/authorization/development.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -12,12 +17,23 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -53,5 +69,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py
index 2fd051927..d658cae93 100644
--- a/docs/tutorials/wiki2/src/authorization/setup.py
+++ b/docs/tutorials/wiki2/src/authorization/setup.py
@@ -13,8 +13,8 @@ requires = [
'pyramid_tm',
'pyramid_debugtoolbar',
'zope.sqlalchemy',
- 'docutils',
'waitress',
+ 'docutils',
]
setup(name='tutorial',
@@ -23,7 +23,7 @@ setup(name='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
- "Framework :: Pylons",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
@@ -35,11 +35,12 @@ setup(name='tutorial',
include_package_data=True,
zip_safe=False,
test_suite='tutorial',
- install_requires = requires,
- entry_points = """\
+ install_requires=requires,
+ entry_points="""\
[paste.app_factory]
main = tutorial:main
[console_scripts]
initialize_tutorial_db = tutorial.scripts.initializedb:main
""",
)
+
diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt
index 6f851e9b7..141851285 100644
--- a/docs/tutorials/wiki2/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki2/src/basiclayout/README.txt
@@ -1 +1,14 @@
tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $venv/bin/python setup.py develop
+
+- $venv/bin/initialize_tutorial_db development.ini
+
+- $venv/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini
index eb2f878c5..a9d53b296 100644
--- a/docs/tutorials/wiki2/src/basiclayout/development.ini
+++ b/docs/tutorials/wiki2/src/basiclayout/development.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -12,12 +17,23 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -53,5 +69,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini
index 4684d2f7a..fa94c1b3e 100644
--- a/docs/tutorials/wiki2/src/basiclayout/production.ini
+++ b/docs/tutorials/wiki2/src/basiclayout/production.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -16,7 +21,10 @@ use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -52,5 +60,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py
index 050de7299..1a1ad78aa 100644
--- a/docs/tutorials/wiki2/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki2/src/basiclayout/setup.py
@@ -22,7 +22,7 @@ setup(name='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
- "Framework :: Pylons",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
@@ -34,8 +34,8 @@ setup(name='tutorial',
include_package_data=True,
zip_safe=False,
test_suite='tutorial',
- install_requires = requires,
- entry_points = """\
+ install_requires=requires,
+ entry_points="""\
[paste.app_factory]
main = tutorial:main
[console_scripts]
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt
index fbfa9870b..15ea6614f 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt
@@ -70,7 +70,7 @@
</div>
</div>
<div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
+ <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
</div>
</body>
</html>
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py
index 3e6abf2c2..daf21bb7b 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py
@@ -1,5 +1,8 @@
+from pyramid.response import Response
from pyramid.view import view_config
+from sqlalchemy.exc import DBAPIError
+
from .models import (
DBSession,
MyModel,
@@ -7,5 +10,25 @@ from .models import (
@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request):
- one = DBSession.query(MyModel).filter(MyModel.name=='one').first()
+ try:
+ one = DBSession.query(MyModel).filter(MyModel.name=='one').first()
+ except DBAPIError:
+ return Response(conn_err_msg, content_type='text/plain', status_int=500)
return {'one':one, 'project':'tutorial'}
+
+conn_err_msg = """\
+Pyramid is having a problem using your SQL database. The problem
+might be caused by one of the following things:
+
+1. You may need to run the "initialize_tutorial_db" script
+ to initialize your database tables. Check your virtual
+ environment's "bin" directory for this script and try to run it.
+
+2. Your database server may not be running. Check that the
+ database server referred to by the "sqlalchemy.url" setting in
+ your "development.ini" file is running.
+
+After you fix the problem, please restart the Pyramid application to
+try it again.
+"""
+
diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt
index 6f851e9b7..141851285 100644
--- a/docs/tutorials/wiki2/src/models/README.txt
+++ b/docs/tutorials/wiki2/src/models/README.txt
@@ -1 +1,14 @@
tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $venv/bin/python setup.py develop
+
+- $venv/bin/initialize_tutorial_db development.ini
+
+- $venv/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini
index eb2f878c5..a9d53b296 100644
--- a/docs/tutorials/wiki2/src/models/development.ini
+++ b/docs/tutorials/wiki2/src/models/development.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -12,12 +17,23 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -53,5 +69,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py
index 050de7299..1a1ad78aa 100644
--- a/docs/tutorials/wiki2/src/models/setup.py
+++ b/docs/tutorials/wiki2/src/models/setup.py
@@ -22,7 +22,7 @@ setup(name='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
- "Framework :: Pylons",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
@@ -34,8 +34,8 @@ setup(name='tutorial',
include_package_data=True,
zip_safe=False,
test_suite='tutorial',
- install_requires = requires,
- entry_points = """\
+ install_requires=requires,
+ entry_points="""\
[paste.app_factory]
main = tutorial:main
[console_scripts]
diff --git a/docs/tutorials/wiki2/src/models/tutorial/views.py b/docs/tutorials/wiki2/src/models/tutorial/views.py
index 3e6abf2c2..daf21bb7b 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/views.py
@@ -1,5 +1,8 @@
+from pyramid.response import Response
from pyramid.view import view_config
+from sqlalchemy.exc import DBAPIError
+
from .models import (
DBSession,
MyModel,
@@ -7,5 +10,25 @@ from .models import (
@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request):
- one = DBSession.query(MyModel).filter(MyModel.name=='one').first()
+ try:
+ one = DBSession.query(MyModel).filter(MyModel.name=='one').first()
+ except DBAPIError:
+ return Response(conn_err_msg, content_type='text/plain', status_int=500)
return {'one':one, 'project':'tutorial'}
+
+conn_err_msg = """\
+Pyramid is having a problem using your SQL database. The problem
+might be caused by one of the following things:
+
+1. You may need to run the "initialize_tutorial_db" script
+ to initialize your database tables. Check your virtual
+ environment's "bin" directory for this script and try to run it.
+
+2. Your database server may not be running. Check that the
+ database server referred to by the "sqlalchemy.url" setting in
+ your "development.ini" file is running.
+
+After you fix the problem, please restart the Pyramid application to
+try it again.
+"""
+
diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt
index 6f851e9b7..141851285 100644
--- a/docs/tutorials/wiki2/src/tests/README.txt
+++ b/docs/tutorials/wiki2/src/tests/README.txt
@@ -1 +1,14 @@
tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $venv/bin/python setup.py develop
+
+- $venv/bin/initialize_tutorial_db development.ini
+
+- $venv/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini
index eb2f878c5..a9d53b296 100644
--- a/docs/tutorials/wiki2/src/tests/development.ini
+++ b/docs/tutorials/wiki2/src/tests/development.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -12,12 +17,23 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -53,5 +69,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py
index 0f58d8a18..8a619d27b 100644
--- a/docs/tutorials/wiki2/src/tests/setup.py
+++ b/docs/tutorials/wiki2/src/tests/setup.py
@@ -24,7 +24,7 @@ setup(name='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
- "Framework :: Pylons",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
@@ -36,11 +36,12 @@ setup(name='tutorial',
include_package_data=True,
zip_safe=False,
test_suite='tutorial',
- install_requires = requires,
- entry_points = """\
+ install_requires=requires,
+ entry_points="""\
[paste.app_factory]
main = tutorial:main
[console_scripts]
initialize_tutorial_db = tutorial.scripts.initializedb:main
""",
)
+
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views.py
index 42ac0eb7f..0d085b0e2 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views.py
@@ -37,14 +37,13 @@ def view_wiki(request):
permission='view')
def view_page(request):
pagename = request.matchdict['pagename']
- session = DBSession()
- page = session.query(Page).filter_by(name=pagename).first()
+ page = DBSession.query(Page).filter_by(name=pagename).first()
if page is None:
return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
- exists = session.query(Page).filter_by(name=word).all()
+ exists = DBSession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
return '<a href="%s">%s</a>' % (view_url, word)
@@ -63,10 +62,9 @@ def view_page(request):
def add_page(request):
pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
- session = DBSession()
body = request.params['body']
page = Page(pagename, body)
- session.add(page)
+ DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
save_url = request.route_url('add_page', pagename=pagename)
@@ -78,11 +76,10 @@ def add_page(request):
permission='edit')
def edit_page(request):
pagename = request.matchdict['pagename']
- session = DBSession()
- page = session.query(Page).filter_by(name=pagename).one()
+ page = DBSession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
- session.add(page)
+ DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
return dict(
diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt
index 6f851e9b7..141851285 100644
--- a/docs/tutorials/wiki2/src/views/README.txt
+++ b/docs/tutorials/wiki2/src/views/README.txt
@@ -1 +1,14 @@
tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $venv/bin/python setup.py develop
+
+- $venv/bin/initialize_tutorial_db development.ini
+
+- $venv/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini
index eb2f878c5..a9d53b296 100644
--- a/docs/tutorials/wiki2/src/views/development.ini
+++ b/docs/tutorials/wiki2/src/views/development.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -12,12 +17,23 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -53,5 +69,3 @@ formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py
index 34b578e21..d658cae93 100644
--- a/docs/tutorials/wiki2/src/views/setup.py
+++ b/docs/tutorials/wiki2/src/views/setup.py
@@ -23,7 +23,7 @@ setup(name='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
- "Framework :: Pylons",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
@@ -35,8 +35,8 @@ setup(name='tutorial',
include_package_data=True,
zip_safe=False,
test_suite='tutorial',
- install_requires = requires,
- entry_points = """\
+ install_requires=requires,
+ entry_points="""\
[paste.app_factory]
main = tutorial:main
[console_scripts]
diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst
index d90897d16..f32053202 100644
--- a/docs/whatsnew-1.3.rst
+++ b/docs/whatsnew-1.3.rst
@@ -289,13 +289,6 @@ Minor Feature Additions
not a new feature, it just provides an API for adding a resource url
adapter without needing to use the ZCA API.
-- The :meth:`pyramid.config.Configurator.scan` method can now be passed an
- ``ignore`` argument, which can be a string, a callable, or a list
- consisting of strings and/or callables. This feature allows submodules,
- subpackages, and global objects from being scanned. See
- http://readthedocs.org/docs/venusian/en/latest/#ignore-scan-argument for
- more information about how to use the ``ignore`` argument to ``scan``.
-
- Better error messages when a view callable returns a value that cannot be
converted to a response (for example, when a view callable returns a
dictionary without a renderer defined, or doesn't return any value at all).
@@ -486,6 +479,11 @@ Deprecations
was designed to offer in Pylons. It will continue to exist "forever" but
it will not be recommended or mentioned in the docs.
+- Remove references to do-nothing ``pyramid.debug_templates`` setting in all
+ Pyramid-provided .ini files. This setting previously told Chameleon to render
+ better exceptions; now Chameleon always renders nice exceptions regardless of
+ the value of this setting.
+
Known Issues
------------
diff --git a/docs/whatsnew-1.4.rst b/docs/whatsnew-1.4.rst
index 59e1f7a96..34fda5f37 100644
--- a/docs/whatsnew-1.4.rst
+++ b/docs/whatsnew-1.4.rst
@@ -77,6 +77,11 @@ Subrequest Support
Minor Feature Additions
-----------------------
+- :class:`pyramid.authentication.AuthTktAuthenticationPolicy` has been updated
+ to support newer hashing algorithms such as ``sha512``. Existing applications
+ should consider updating if possible for improved security over the default
+ md5 hashing.
+
- :meth:`pyramid.config.Configurator.add_directive` now accepts arbitrary
callables like partials or objects implementing ``__call__`` which don't
have ``__name__`` and ``__doc__`` attributes. See
@@ -182,7 +187,6 @@ Minor Feature Additions
:meth:`pyramid.config.testing_securitypolicy` now sets a ``forgotten`` value
on the policy (the value ``True``) when its ``forget`` method is called.
-
- The DummySecurityPolicy created by
:meth:`pyramid.config.testing_securitypolicy` now sets a
``remembered`` value on the policy, which is the value of the ``principal``
@@ -196,6 +200,48 @@ Minor Feature Additions
view when some object is traversed to, but you can't be sure about what kind
of object it will be, so you can't use the ``context`` predicate.
+- Added an ``effective_principals`` route and view predicate.
+
+- Do not allow the userid returned from the
+ :func:`pyramid.security.authenticated_userid` or the userid that is one of the
+ list of principals returned by :func:`pyramid.security.effective_principals`
+ to be either of the strings ``system.Everyone`` or ``system.Authenticated``
+ when any of the built-in authorization policies that live in
+ :mod:`pyramid.authentication` are in use. These two strings are reserved for
+ internal usage by Pyramid and they will no longer be accepted as valid
+ userids.
+
+- Allow a ``_depth`` argument to :class:`pyramid.view.view_config`, which will
+ permit limited composition reuse of the decorator by other software that
+ wants to provide custom decorators that are much like view_config.
+
+- Allow an iterable of decorators to be passed to
+ :meth:`pyramid.config.Configurator.add_view`. This allows views to be wrapped
+ by more than one decorator without requiring combining the decorators
+ yourself.
+
+- :func:`pyramid.security.view_execution_permitted` used to return `True` if no
+ view could be found. It now raises a :exc:`TypeError` exception in that case,
+ as it doesn't make sense to assert that a nonexistent view is
+ execution-permitted. See https://github.com/Pylons/pyramid/issues/299.
+
+- Small microspeed enhancement which anticipates that a
+ :class:`pyramid.response.Response` object is likely to be returned from a
+ view. Some code is shortcut if the class of the object returned by a view is
+ this class. A similar microoptimization was done to
+ :func:`pyramid.request.Request.is_response`.
+
+- Make it possible to use variable arguments on all ``p*`` commands
+ (``pserve``, ``pshell``, ``pviews``, etc) in the form ``a=1 b=2`` so you can
+ fill in values in parameterized ``.ini`` file, e.g. ``pshell
+ etc/development.ini http_port=8080``.
+
+- In order to allow people to ignore unused arguments to subscriber callables
+ and to normalize the relationship between event subscribers and subscriber
+ predicates, we now allow both subscribers and subscriber predicates to accept
+ only a single ``event`` argument even if they've been subscribed for
+ notifications that involve multiple interfaces.
+
Backwards Incompatibilities
---------------------------
@@ -289,6 +335,12 @@ Deprecations
used in its place (it has all of the same capabilities but can also extend
the request object with methods).
+- :class:`pyramid.authentication.AuthTktAuthenticationPolicy` will emit a
+ deprecation warning if an application is using the policy without explicitly
+ passing a ``hashalg`` argument. This is because the default is "md5" which is
+ considered theoretically subject to collision attacks. If you really want
+ "md5" then you must specify it explicitly to get rid of the warning.
+
Documentation Enhancements
--------------------------
@@ -299,6 +351,10 @@ Documentation Enhancements
- Added a :ref:`subrequest_chapter` chapter to the narrative documentation.
+- All of the tutorials that use
+ :class:`pyramid.authentication.AuthTktAuthenticationPolicy` now explicitly
+ pass ``sha512`` as a ``hashalg`` argument.
+
- Many cleanups and improvements to narrative and API docs.
Dependency Changes
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 1dc438597..40edaa324 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -70,16 +70,17 @@ from pyramid.config.security import SecurityConfiguratorMixin
from pyramid.config.settings import SettingsConfiguratorMixin
from pyramid.config.testing import TestingConfiguratorMixin
from pyramid.config.tweens import TweensConfiguratorMixin
-from pyramid.config.util import (
- action_method,
- ActionInfo,
- PredicateList,
- )
+from pyramid.config.util import PredicateList
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
from pyramid.path import DottedNameResolver
+from pyramid.util import (
+ action_method,
+ ActionInfo,
+ )
+
empty = text_('')
_marker = object()
diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py
index 12c4de660..dad60660a 100644
--- a/pyramid/config/adapters.py
+++ b/pyramid/config/adapters.py
@@ -10,6 +10,7 @@ from pyramid.interfaces import (
from pyramid.config.util import (
action_method,
+ takes_one_arg,
)
@@ -51,8 +52,22 @@ class AdaptersConfiguratorMixin(object):
def register():
predlist = self.get_predlist('subscriber')
order, preds, phash = predlist.make(self, **predicates)
- intr.update({'phash':phash, 'order':order, 'predicates':preds})
- derived_subscriber = self._derive_subscriber(subscriber, preds)
+
+ derived_predicates = [ self._derive_predicate(p) for p in preds ]
+ derived_subscriber = self._derive_subscriber(
+ subscriber,
+ derived_predicates,
+ )
+
+ intr.update(
+ {'phash':phash,
+ 'order':order,
+ 'predicates':preds,
+ 'derived_predicates':derived_predicates,
+ 'derived_subscriber':derived_subscriber,
+ }
+ )
+
self.registry.registerHandler(derived_subscriber, iface)
intr = self.introspectable(
@@ -68,25 +83,54 @@ class AdaptersConfiguratorMixin(object):
self.action(None, register, introspectables=(intr,))
return subscriber
+ def _derive_predicate(self, predicate):
+ derived_predicate = predicate
+
+ if eventonly(predicate):
+ def derived_predicate(*arg):
+ return predicate(arg[0])
+ # seems pointless to try to fix __doc__, __module__, etc as
+ # predicate will invariably be an instance
+
+ return derived_predicate
+
def _derive_subscriber(self, subscriber, predicates):
+ derived_subscriber = subscriber
+
+ if eventonly(subscriber):
+ def derived_subscriber(*arg):
+ return subscriber(arg[0])
+ if hasattr(subscriber, '__name__'):
+ update_wrapper(derived_subscriber, subscriber)
+
if not predicates:
- return subscriber
+ return derived_subscriber
+
def subscriber_wrapper(*arg):
- # We need to accept *arg and pass it along because zope
- # subscribers are designed poorly. Notification will always call
- # an associated subscriber with all of the objects involved in
- # the subscription lookup, despite the fact that the event sender
- # always has the option to attach those objects to the event
- # object itself (and usually does). It would be much saner if the
- # registry just used extra args passed to notify to do the lookup
- # but only called event subscribers with the actual event object,
- # or if we had been smart enough early on to always wrap
- # subscribers in something that threw away the extra args, but
- # c'est la vie.
+ # We need to accept *arg and pass it along because zope subscribers
+ # are designed awkwardly. Notification via
+ # registry.adapter.subscribers will always call an associated
+ # subscriber with all of the objects involved in the subscription
+ # lookup, despite the fact that the event sender always has the
+ # option to attach those objects to the event object itself, and
+ # almost always does.
+ #
+ # The "eventonly" jazz sprinkled in this function and related
+ # functions allows users to define subscribers and predicates which
+ # accept only an event argument without needing to accept the rest
+ # of the adaptation arguments. Had I been smart enough early on to
+ # use .subscriptions to find the subscriber functions in order to
+ # call them manually with a single "event" argument instead of
+ # relying on .subscribers to both find and call them implicitly
+ # with all args, the eventonly hack would not have been required.
+ # At this point, though, using .subscriptions and manual execution
+ # is not possible without badly breaking backwards compatibility.
if all((predicate(*arg) for predicate in predicates)):
- return subscriber(*arg)
+ return derived_subscriber(*arg)
+
if hasattr(subscriber, '__name__'):
update_wrapper(subscriber_wrapper, subscriber)
+
return subscriber_wrapper
@action_method
@@ -266,7 +310,7 @@ class AdaptersConfiguratorMixin(object):
if resource_iface is None:
resource_iface = Interface
self.registry.registerAdapter(
- adapter,
+ adapter,
(resource_iface, Interface),
IResourceURL,
)
@@ -281,3 +325,5 @@ class AdaptersConfiguratorMixin(object):
intr['resource_iface'] = resource_iface
self.action(discriminator, register, introspectables=(intr,))
+def eventonly(callee):
+ return takes_one_arg(callee, argname='event')
diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py
index c93431987..5d4682349 100644
--- a/pyramid/config/assets.py
+++ b/pyramid/config/assets.py
@@ -8,7 +8,7 @@ from pyramid.interfaces import IPackageOverrides
from pyramid.exceptions import ConfigurationError
from pyramid.threadlocal import get_current_registry
-from pyramid.config.util import action_method
+from pyramid.util import action_method
class OverrideProvider(pkg_resources.DefaultProvider):
def __init__(self, module):
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index 01b1fb22e..ef7975d92 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -1,7 +1,5 @@
from zope.interface import implementer
-from pyramid.config.util import action_method
-
from pyramid.interfaces import (
IDefaultRootFactory,
IRequestFactory,
@@ -11,7 +9,11 @@ from pyramid.interfaces import (
)
from pyramid.traversal import DefaultRootFactory
-from pyramid.util import InstancePropertyMixin
+
+from pyramid.util import (
+ action_method,
+ InstancePropertyMixin,
+ )
class FactoriesConfiguratorMixin(object):
@action_method
diff --git a/pyramid/config/i18n.py b/pyramid/config/i18n.py
index 67a7e2018..9eb59e1c7 100644
--- a/pyramid/config/i18n.py
+++ b/pyramid/config/i18n.py
@@ -13,8 +13,7 @@ from pyramid.exceptions import ConfigurationError
from pyramid.i18n import get_localizer
from pyramid.path import package_path
from pyramid.threadlocal import get_current_request
-
-from pyramid.config.util import action_method
+from pyramid.util import action_method
class I18NConfiguratorMixin(object):
@action_method
diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py
index e31425899..ded8fbfbf 100644
--- a/pyramid/config/predicates.py
+++ b/pyramid/config/predicates.py
@@ -17,6 +17,8 @@ from pyramid.security import effective_principals
from .util import as_sorted_tuple
+_marker = object()
+
class XHRPredicate(object):
def __init__(self, val, config):
self.val = bool(val)
@@ -174,6 +176,9 @@ class MatchParamPredicate(object):
phash = text
def __call__(self, context, request):
+ if not request.matchdict:
+ # might be None
+ return False
for k, v in self.reqs:
if request.matchdict.get(k) != v:
return False
@@ -266,7 +271,9 @@ class PhysicalPathPredicate(object):
phash = text
def __call__(self, context, request):
- return resource_path_tuple(context) == self.val
+ if getattr(context, '__name__', _marker) is not _marker:
+ return resource_path_tuple(context) == self.val
+ return False
class EffectivePrincipalsPredicate(object):
def __init__(self, val, config):
diff --git a/pyramid/config/rendering.py b/pyramid/config/rendering.py
index 926511b7b..4f33b23d9 100644
--- a/pyramid/config/rendering.py
+++ b/pyramid/config/rendering.py
@@ -6,7 +6,7 @@ from pyramid.interfaces import (
PHASE1_CONFIG,
)
-from pyramid.config.util import action_method
+from pyramid.util import action_method
from pyramid import (
renderers,
diff --git a/pyramid/config/security.py b/pyramid/config/security.py
index 567999cc4..6a1257b6a 100644
--- a/pyramid/config/security.py
+++ b/pyramid/config/security.py
@@ -7,7 +7,7 @@ from pyramid.interfaces import (
)
from pyramid.exceptions import ConfigurationError
-from pyramid.config.util import action_method
+from pyramid.util import action_method
class SecurityConfiguratorMixin(object):
@action_method
diff --git a/pyramid/config/testing.py b/pyramid/config/testing.py
index abbbffc10..7141a5049 100644
--- a/pyramid/config/testing.py
+++ b/pyramid/config/testing.py
@@ -14,7 +14,7 @@ from pyramid.traversal import (
split_path_info,
)
-from pyramid.config.util import action_method
+from pyramid.util import action_method
class TestingConfiguratorMixin(object):
# testing API
diff --git a/pyramid/config/util.py b/pyramid/config/util.py
index 1c6e1ca15..af0dd1641 100644
--- a/pyramid/config/util.py
+++ b/pyramid/config/util.py
@@ -1,68 +1,27 @@
-import traceback
-
-from functools import update_wrapper
-
-from zope.interface import implementer
-
-from pyramid.interfaces import IActionInfo
+from hashlib import md5
+import inspect
from pyramid.compat import (
bytes_,
is_nonstr_iter,
)
+from pyramid.compat import im_func
from pyramid.exceptions import ConfigurationError
from pyramid.registry import predvalseq
-from pyramid.util import TopologicalSorter
-from hashlib import md5
+from pyramid.util import (
+ TopologicalSorter,
+ action_method,
+ ActionInfo,
+ )
+
+action_method = action_method # support bw compat imports
+ActionInfo = ActionInfo # support bw compat imports
MAX_ORDER = 1 << 30
DEFAULT_PHASH = md5().hexdigest()
-@implementer(IActionInfo)
-class ActionInfo(object):
- def __init__(self, file, line, function, src):
- self.file = file
- self.line = line
- self.function = function
- self.src = src
-
- def __str__(self):
- srclines = self.src.split('\n')
- src = '\n'.join(' %s' % x for x in srclines)
- return 'Line %s of file %s:\n%s' % (self.line, self.file, src)
-
-def action_method(wrapped):
- """ Wrapper to provide the right conflict info report data when a method
- that calls Configurator.action calls another that does the same"""
- def wrapper(self, *arg, **kw):
- if self._ainfo is None:
- self._ainfo = []
- info = kw.pop('_info', None)
- # backframes for outer decorators to actionmethods
- backframes = kw.pop('_backframes', 2)
- if is_nonstr_iter(info) and len(info) == 4:
- # _info permitted as extract_stack tuple
- info = ActionInfo(*info)
- if info is None:
- try:
- f = traceback.extract_stack(limit=3)
- info = ActionInfo(*f[-backframes])
- except: # pragma: no cover
- info = ActionInfo(None, 0, '', '')
- self._ainfo.append(info)
- try:
- result = wrapped(self, *arg, **kw)
- finally:
- self._ainfo.pop()
- return result
-
- if hasattr(wrapped, '__name__'):
- update_wrapper(wrapper, wrapped)
- wrapper.__docobj__ = wrapped
- return wrapper
-
def as_sorted_tuple(val):
if not is_nonstr_iter(val):
val = (val,)
@@ -85,8 +44,12 @@ class PredicateList(object):
## weighs_more_than = self.last_added or FIRST
## weighs_less_than = LAST
self.last_added = name
- self.sorter.add(name, factory, after=weighs_more_than,
- before=weighs_less_than)
+ self.sorter.add(
+ name,
+ factory,
+ after=weighs_more_than,
+ before=weighs_less_than,
+ )
def make(self, config, **kw):
# Given a configurator and a list of keywords, a predicate list is
@@ -149,3 +112,51 @@ class PredicateList(object):
order = (MAX_ORDER - score) / (len(preds) + 1)
return order, preds, phash.hexdigest()
+def takes_one_arg(callee, attr=None, argname=None):
+ ismethod = False
+ if attr is None:
+ attr = '__call__'
+ if inspect.isroutine(callee):
+ fn = callee
+ elif inspect.isclass(callee):
+ try:
+ fn = callee.__init__
+ except AttributeError:
+ return False
+ ismethod = hasattr(fn, '__call__')
+ else:
+ try:
+ fn = getattr(callee, attr)
+ except AttributeError:
+ return False
+
+ try:
+ argspec = inspect.getargspec(fn)
+ except TypeError:
+ return False
+
+ args = argspec[0]
+
+ if hasattr(fn, im_func) or ismethod:
+ # it's an instance method (or unbound method on py2)
+ if not args:
+ return False
+ args = args[1:]
+
+ if not args:
+ return False
+
+ if len(args) == 1:
+ return True
+
+ if argname:
+
+ defaults = argspec[3]
+ if defaults is None:
+ defaults = ()
+
+ if args[0] == argname:
+ if len(args) - len(defaults) == 1:
+ return True
+
+ return False
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 8a4db149e..d1b69566b 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -1,7 +1,6 @@
import inspect
import operator
import os
-from functools import wraps
from zope.interface import (
Interface,
@@ -60,6 +59,8 @@ from pyramid.registry import (
Deferred,
)
+from pyramid.response import Response
+
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
@@ -71,6 +72,8 @@ from pyramid.view import (
from pyramid.util import (
object_description,
+ viewdefaults,
+ action_method,
)
import pyramid.config.predicates
@@ -78,7 +81,7 @@ import pyramid.config.predicates
from pyramid.config.util import (
DEFAULT_PHASH,
MAX_ORDER,
- action_method,
+ takes_one_arg,
)
urljoin = urlparse.urljoin
@@ -341,25 +344,28 @@ class ViewDeriver(object):
def rendered_view(context, request):
renderer = view_renderer
result = view(context, request)
- registry = self.registry
- # this must adapt, it can't do a simple interface check
- # (avoid trying to render webob responses)
- response = registry.queryAdapterOrSelf(result, IResponse)
- if response is None:
- attrs = getattr(request, '__dict__', {})
- if 'override_renderer' in attrs:
- # renderer overridden by newrequest event or other
- renderer_name = attrs.pop('override_renderer')
- renderer = renderers.RendererHelper(
- name=renderer_name,
- package=self.kw.get('package'),
- registry = registry)
- if '__view__' in attrs:
- view_inst = attrs.pop('__view__')
- else:
- view_inst = getattr(view, '__original_view__', view)
- response = renderer.render_view(request, result, view_inst,
- context)
+ if result.__class__ is Response: # potential common case
+ response = result
+ else:
+ registry = self.registry
+ # this must adapt, it can't do a simple interface check
+ # (avoid trying to render webob responses)
+ response = registry.queryAdapterOrSelf(result, IResponse)
+ if response is None:
+ attrs = getattr(request, '__dict__', {})
+ if 'override_renderer' in attrs:
+ # renderer overridden by newrequest event or other
+ renderer_name = attrs.pop('override_renderer')
+ renderer = renderers.RendererHelper(
+ name=renderer_name,
+ package=self.kw.get('package'),
+ registry = registry)
+ if '__view__' in attrs:
+ view_inst = attrs.pop('__view__')
+ else:
+ view_inst = getattr(view, '__original_view__', view)
+ response = renderer.render_view(request, result, view_inst,
+ context)
return response
return rendered_view
@@ -368,21 +374,26 @@ class ViewDeriver(object):
registry = self.registry
def viewresult_to_response(context, request):
result = view(context, request)
- response = registry.queryAdapterOrSelf(result, IResponse)
- if response is None:
- if result is None:
- append = (' You may have forgotten to return a value from '
- 'the view callable.')
- elif isinstance(result, dict):
- append = (' You may have forgotten to define a renderer in '
- 'the view configuration.')
- else:
- append = ''
- msg = ('Could not convert return value of the view callable %s '
- 'into a response object. '
- 'The value returned was %r.' + append)
-
- raise ValueError(msg % (view_description(view), result))
+ if result.__class__ is Response: # common case
+ response = result
+ else:
+ response = registry.queryAdapterOrSelf(result, IResponse)
+ if response is None:
+ if result is None:
+ append = (' You may have forgotten to return a value '
+ 'from the view callable.')
+ elif isinstance(result, dict):
+ append = (' You may have forgotten to define a '
+ 'renderer in the view configuration.')
+ else:
+ append = ''
+
+ msg = ('Could not convert return value of the view '
+ 'callable %s into a response object. '
+ 'The value returned was %r.' + append)
+
+ raise ValueError(msg % (view_description(view), result))
+
return response
return viewresult_to_response
@@ -493,50 +504,7 @@ class DefaultViewMapper(object):
return _attr_view
def requestonly(view, attr=None):
- ismethod = False
- if attr is None:
- attr = '__call__'
- if inspect.isroutine(view):
- fn = view
- elif inspect.isclass(view):
- try:
- fn = view.__init__
- except AttributeError:
- return False
- ismethod = hasattr(fn, '__call__')
- else:
- try:
- fn = getattr(view, attr)
- except AttributeError:
- return False
-
- try:
- argspec = inspect.getargspec(fn)
- except TypeError:
- return False
-
- args = argspec[0]
-
- if hasattr(fn, im_func) or ismethod:
- # it's an instance method (or unbound method on py2)
- if not args:
- return False
- args = args[1:]
- if not args:
- return False
-
- if len(args) == 1:
- return True
-
- defaults = argspec[3]
- if defaults is None:
- defaults = ()
-
- if args[0] == 'request':
- if len(args) - len(defaults) == 1:
- return True
-
- return False
+ return takes_one_arg(view, attr=attr, argname='request')
@implementer(IMultiView)
class MultiView(object):
@@ -621,21 +589,6 @@ class MultiView(object):
continue
raise PredicateMismatch(self.name)
-def viewdefaults(wrapped):
- def wrapper(self, *arg, **kw):
- defaults = {}
- if arg:
- view = arg[0]
- else:
- view = kw.get('view')
- view = self.maybe_dotted(view)
- if inspect.isclass(view):
- defaults = getattr(view, '__view_defaults__', {}).copy()
- defaults.update(kw)
- defaults['_backframes'] = 3 # for action_method
- return wrapped(self, *arg, **defaults)
- return wraps(wrapped)(wrapper)
-
class ViewsConfiguratorMixin(object):
@viewdefaults
@action_method
diff --git a/pyramid/paster.py b/pyramid/paster.py
index b0e4d7933..ce07d1fe0 100644
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -9,17 +9,27 @@ from pyramid.compat import configparser
from logging.config import fileConfig
from pyramid.scripting import prepare
-def get_app(config_uri, name=None, loadapp=loadapp):
+def get_app(config_uri, name=None, options=None, loadapp=loadapp):
""" Return the WSGI application named ``name`` in the PasteDeploy
config file specified by ``config_uri``.
+ ``options``, if passed, should be a dictionary used as variable assignments
+ like ``{'http_port': 8080}``. This is useful if e.g. ``%(http_port)s`` is
+ used in the config file.
+
If the ``name`` is None, this will attempt to parse the name from
the ``config_uri`` string expecting the format ``inifile#name``.
If no name is found, the name will default to "main"."""
path, section = _getpathsec(config_uri, name)
config_name = 'config:%s' % path
here_dir = os.getcwd()
- app = loadapp(config_name, name=section, relative_to=here_dir)
+ if options:
+ kw = {'global_conf': options}
+ else:
+ kw = {}
+
+ app = loadapp(config_name, name=section, relative_to=here_dir, **kw)
+
return app
def get_appsettings(config_uri, name=None, appconfig=appconfig):
@@ -63,7 +73,7 @@ def _getpathsec(config_uri, name):
section = name
return path, section
-def bootstrap(config_uri, request=None):
+def bootstrap(config_uri, request=None, options=None):
""" Load a WSGI application from the PasteDeploy config file specified
by ``config_uri``. The environment will be configured as if it is
currently serving ``request``, leaving a natural environment in place
@@ -103,10 +113,14 @@ def bootstrap(config_uri, request=None):
for you if none is provided. You can mutate the request's ``environ``
later to setup a specific host/port/scheme/etc.
+ ``options`` Is passed to get_app for use as variable assignments like
+ {'http_port': 8080} and then use %(http_port)s in the
+ config file.
+
See :ref:`writing_a_script` for more information about how to use this
function.
"""
- app = get_app(config_uri)
+ app = get_app(config_uri, options=options)
env = prepare(request)
env['app'] = app
return env
diff --git a/pyramid/request.py b/pyramid/request.py
index 9e275c2c0..27ab337de 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -372,6 +372,8 @@ class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin,
def is_response(self, ob):
""" Return ``True`` if the object passed as ``ob`` is a valid
response object, ``False`` otherwise."""
+ if ob.__class__ is Response:
+ return True
registry = self.registry
adapted = registry.queryAdapterOrSelf(ob, IResponse)
if adapted is None:
diff --git a/pyramid/router.py b/pyramid/router.py
index 0c7f61071..9b6138ea9 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -103,7 +103,7 @@ class Router(object):
request.path_info,
route.pattern,
match,
- ', '.join([p.__text__ for p in route.predicates]))
+ ', '.join([p.text() for p in route.predicates]))
)
logger and logger.debug(msg)
diff --git a/pyramid/scripts/__init__.py b/pyramid/scripts/__init__.py
index ed88d78b4..5bb534f79 100644
--- a/pyramid/scripts/__init__.py
+++ b/pyramid/scripts/__init__.py
@@ -1,2 +1 @@
# package
-
diff --git a/pyramid/scripts/common.py b/pyramid/scripts/common.py
index dfff26449..cbc172e9b 100644
--- a/pyramid/scripts/common.py
+++ b/pyramid/scripts/common.py
@@ -2,6 +2,21 @@ import os
from pyramid.compat import configparser
from logging.config import fileConfig
+def parse_vars(args):
+ """
+ Given variables like ``['a=b', 'c=d']`` turns it into ``{'a':
+ 'b', 'c': 'd'}``
+ """
+ result = {}
+ for arg in args:
+ if '=' not in arg:
+ raise ValueError(
+ 'Variable assignment %r invalid (no "=")'
+ % arg)
+ name, value = arg.split('=', 1)
+ result[name] = value
+ return result
+
def logging_file_config(config_file, fileConfig=fileConfig,
configparser=configparser):
"""
diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py
index 13406372a..da6b8cc14 100644
--- a/pyramid/scripts/prequest.py
+++ b/pyramid/scripts/prequest.py
@@ -5,6 +5,7 @@ import textwrap
from pyramid.compat import url_unquote
from pyramid.request import Request
from pyramid.paster import get_app
+from pyramid.scripts.common import parse_vars
def main(argv=sys.argv, quiet=False):
command = PRequestCommand(argv, quiet)
@@ -101,7 +102,9 @@ class PRequestCommand(object):
name, value = item.split(':', 1)
headers[name] = value.strip()
- app = self.get_app(app_spec, self.options.app_name)
+ app = self.get_app(app_spec, self.options.app_name,
+ options=parse_vars(self.args[2:]))
+
request_method = (self.options.method or 'GET').upper()
environ = {
diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py
index f64107d2b..49e19deca 100644
--- a/pyramid/scripts/proutes.py
+++ b/pyramid/scripts/proutes.py
@@ -3,6 +3,7 @@ import sys
import textwrap
from pyramid.paster import bootstrap
+from pyramid.scripts.common import parse_vars
def main(argv=sys.argv, quiet=False):
command = PRoutesCommand(argv, quiet)
@@ -47,12 +48,14 @@ class PRoutesCommand(object):
if not self.args:
self.out('requires a config file argument')
return 2
+
from pyramid.interfaces import IRouteRequest
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IView
from zope.interface import Interface
config_uri = self.args[0]
- env = self.bootstrap[0](config_uri)
+
+ env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
registry = env['registry']
mapper = self._get_mapper(registry)
if mapper is not None:
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
index 9fbf0729a..6c7e1d654 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -22,12 +22,15 @@ import threading
import time
import traceback
-from paste.deploy import loadapp, loadserver
+from paste.deploy import loadserver
+from paste.deploy import loadapp
from pyramid.compat import WIN
from pyramid.paster import setup_logging
+from pyramid.scripts.common import parse_vars
+
MAXFD = 1024
if WIN and not hasattr(os, 'kill'): # pragma: no cover
@@ -160,6 +163,15 @@ class PServeCommand(object):
if not self.quiet:
print(msg)
+ def get_options(self):
+ if (len(self.args) > 1
+ and self.args[1] in self.possible_subcommands):
+ restvars = self.args[2:]
+ else:
+ restvars = self.args[1:]
+
+ return parse_vars(restvars)
+
def run(self): # pragma: no cover
if self.options.stop_daemon:
return self.stop_daemon()
@@ -176,13 +188,12 @@ class PServeCommand(object):
self.out('You must give a config file')
return 2
app_spec = self.args[0]
+
if (len(self.args) > 1
and self.args[1] in self.possible_subcommands):
cmd = self.args[1]
- restvars = self.args[2:]
else:
cmd = None
- restvars = self.args[1:]
if self.options.reload:
if os.environ.get(self._reloader_environ_key):
@@ -218,7 +229,9 @@ class PServeCommand(object):
self.options.daemon = True
app_name = self.options.app_name
- vars = self.parse_vars(restvars)
+
+ vars = self.get_options()
+
if not self._scheme_re.search(app_spec):
app_spec = 'config:' + app_spec
server_name = self.options.server_name
@@ -286,8 +299,9 @@ class PServeCommand(object):
server = self.loadserver(server_spec, name=server_name,
relative_to=base, global_conf=vars)
- app = self.loadapp(app_spec, name=app_name,
- relative_to=base, global_conf=vars)
+
+ app = self.loadapp(app_spec, name=app_name, relative_to=base,
+ global_conf=vars)
if self.verbose > 0:
if hasattr(os, 'getpid'):
@@ -310,27 +324,12 @@ class PServeCommand(object):
serve()
- def loadserver(self, server_spec, name, relative_to, **kw):# pragma:no cover
- return loadserver(
- server_spec, name=name, relative_to=relative_to, **kw)
-
def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover
return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
- def parse_vars(self, args):
- """
- Given variables like ``['a=b', 'c=d']`` turns it into ``{'a':
- 'b', 'c': 'd'}``
- """
- result = {}
- for arg in args:
- if '=' not in arg:
- raise ValueError(
- 'Variable assignment %r invalid (no "=")'
- % arg)
- name, value = arg.split('=', 1)
- result[name] = value
- return result
+ def loadserver(self, server_spec, name, relative_to, **kw):# pragma:no cover
+ return loadserver(
+ server_spec, name=name, relative_to=relative_to, **kw)
def quote_first_command_arg(self, arg): # pragma: no cover
"""
diff --git a/pyramid/scripts/pshell.py b/pyramid/scripts/pshell.py
index 7a21eaf98..f74402928 100644
--- a/pyramid/scripts/pshell.py
+++ b/pyramid/scripts/pshell.py
@@ -9,6 +9,8 @@ from pyramid.paster import bootstrap
from pyramid.paster import setup_logging
+from pyramid.scripts.common import parse_vars
+
def main(argv=sys.argv, quiet=False):
command = PShellCommand(argv, quiet)
return command.run()
@@ -87,7 +89,7 @@ class PShellCommand(object):
self.pshell_file_config(config_file)
# bootstrap the environ
- env = self.bootstrap[0](config_uri)
+ env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
# remove the closer from the env
closer = env.pop('closer')
diff --git a/pyramid/scripts/ptweens.py b/pyramid/scripts/ptweens.py
index d3e17db58..5fe2fa120 100644
--- a/pyramid/scripts/ptweens.py
+++ b/pyramid/scripts/ptweens.py
@@ -7,6 +7,7 @@ from pyramid.interfaces import ITweens
from pyramid.tweens import MAIN
from pyramid.tweens import INGRESS
from pyramid.paster import bootstrap
+from pyramid.scripts.common import parse_vars
def main(argv=sys.argv, quiet=False):
command = PTweensCommand(argv, quiet)
@@ -62,7 +63,7 @@ class PTweensCommand(object):
self.out('Requires a config file argument')
return 2
config_uri = self.args[0]
- env = self.bootstrap[0](config_uri)
+ env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
registry = env['registry']
tweens = self._get_tweens(registry)
if tweens is not None:
diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py
index a9db59dc1..081c13e9d 100644
--- a/pyramid/scripts/pviews.py
+++ b/pyramid/scripts/pviews.py
@@ -4,6 +4,7 @@ import textwrap
from pyramid.interfaces import IMultiView
from pyramid.paster import bootstrap
+from pyramid.scripts.common import parse_vars
def main(argv=sys.argv, quiet=False):
command = PViewsCommand(argv, quiet)
@@ -187,7 +188,7 @@ class PViewsCommand(object):
self.out("%sroute pattern: %s" % (indent, route.pattern))
self.out("%sroute path: %s" % (indent, route.path))
self.out("%ssubpath: %s" % (indent, '/'.join(attrs['subpath'])))
- predicates = ', '.join([p.__text__ for p in route.predicates])
+ predicates = ', '.join([p.text() for p in route.predicates])
if predicates != '':
self.out("%sroute predicates (%s)" % (indent, predicates))
@@ -230,10 +231,12 @@ class PViewsCommand(object):
if len(self.args) < 2:
self.out('Command requires a config file arg and a url arg')
return 2
- config_uri, url = self.args
+ config_uri = self.args[0]
+ url = self.args[1]
+
if not url.startswith('/'):
url = '/%s' % url
- env = self.bootstrap[0](config_uri)
+ env = self.bootstrap[0](config_uri, options=parse_vars(self.args[2:]))
registry = env['registry']
view = self._find_view(url, registry)
self.out('')
diff --git a/pyramid/tests/pkgs/eventonly/__init__.py b/pyramid/tests/pkgs/eventonly/__init__.py
new file mode 100644
index 000000000..7ae93ada6
--- /dev/null
+++ b/pyramid/tests/pkgs/eventonly/__init__.py
@@ -0,0 +1,64 @@
+from pyramid.view import view_config
+from pyramid.events import subscriber
+
+class Yup(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'path_startswith = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, event):
+ return getattr(event.response, 'yup', False)
+
+class Foo(object):
+ def __init__(self, response):
+ self.response = response
+
+class Bar(object):
+ pass
+
+@subscriber(Foo)
+def foo(event):
+ event.response.text += 'foo '
+
+@subscriber(Foo, yup=True)
+def fooyup(event):
+ event.response.text += 'fooyup '
+
+@subscriber([Foo, Bar])
+def foobar(event):
+ event.response.text += 'foobar '
+
+@subscriber([Foo, Bar])
+def foobar2(event, context):
+ event.response.text += 'foobar2 '
+
+@subscriber([Foo, Bar], yup=True)
+def foobaryup(event):
+ event.response.text += 'foobaryup '
+
+@subscriber([Foo, Bar], yup=True)
+def foobaryup2(event, context):
+ event.response.text += 'foobaryup2 '
+
+@view_config(name='sendfoo')
+def sendfoo(request):
+ response = request.response
+ response.yup = True
+ request.registry.notify(Foo(response))
+ return response
+
+@view_config(name='sendfoobar')
+def sendfoobar(request):
+ response = request.response
+ response.yup = True
+ request.registry.notify(Foo(response), Bar())
+ return response
+
+def includeme(config):
+ config.add_subscriber_predicate('yup', Yup)
+ config.scan('pyramid.tests.pkgs.eventonly')
+
diff --git a/pyramid/tests/test_config/test_adapters.py b/pyramid/tests/test_config/test_adapters.py
index d47e012dc..4cbb1bf80 100644
--- a/pyramid/tests/test_config/test_adapters.py
+++ b/pyramid/tests/test_config/test_adapters.py
@@ -331,6 +331,15 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase):
self.assertEqual(intr['adapter'], DummyResourceURL)
self.assertEqual(intr['resource_iface'], DummyIface)
+class Test_eventonly(unittest.TestCase):
+ def _callFUT(self, callee):
+ from pyramid.config.adapters import eventonly
+ return eventonly(callee)
+
+ def test_defaults(self):
+ def acallable(event, a=1, b=2): pass
+ self.assertTrue(self._callFUT(acallable))
+
class DummyTraverser(object):
def __init__(self, root):
self.root = root
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index 2cf9a269a..7c2880a18 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -772,7 +772,7 @@ pyramid.tests.test_config.dummy_include2""",
self.assertEqual(config.action('discrim', kw={'a':1}), None)
def test_action_autocommit_with_introspectables(self):
- from pyramid.config.util import ActionInfo
+ from pyramid.util import ActionInfo
config = self._makeOne(autocommit=True)
intr = DummyIntrospectable()
config.action('discrim', introspectables=(intr,))
diff --git a/pyramid/tests/test_config/test_predicates.py b/pyramid/tests/test_config/test_predicates.py
index 91dfb0fb6..1cd6050bf 100644
--- a/pyramid/tests/test_config/test_predicates.py
+++ b/pyramid/tests/test_config/test_predicates.py
@@ -187,6 +187,13 @@ class TestMatchParamPredicate(unittest.TestCase):
result = inst(None, request)
self.assertFalse(result)
+ def test___call___matchdict_is_None(self):
+ inst = self._makeOne('abc=1')
+ request = Dummy()
+ request.matchdict = None
+ result = inst(None, request)
+ self.assertFalse(result)
+
def test_text(self):
inst = self._makeOne(('def= 1', 'abc =2'))
self.assertEqual(inst.text(), 'match_param abc=2,def=1')
@@ -436,6 +443,11 @@ class Test_PhysicalPathPredicate(unittest.TestCase):
context.__parent__ = root
self.assertFalse(inst(context, None))
+ def test_it_call_context_has_no_name(self):
+ inst = self._makeOne('/', None)
+ context = Dummy()
+ self.assertFalse(inst(context, None))
+
class Test_EffectivePrincipalsPredicate(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py
index 8c3cd7455..a984acfd0 100644
--- a/pyramid/tests/test_config/test_util.py
+++ b/pyramid/tests/test_config/test_util.py
@@ -365,36 +365,192 @@ class TestPredicateList(unittest.TestCase):
from pyramid.exceptions import ConfigurationError
self.assertRaises(ConfigurationError, self._callFUT, unknown=1)
+class Test_takes_one_arg(unittest.TestCase):
+ def _callFUT(self, view, attr=None, argname=None):
+ from pyramid.config.util import takes_one_arg
+ return takes_one_arg(view, attr=attr, argname=argname)
+
+ def test_requestonly_newstyle_class_no_init(self):
+ class foo(object):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_requestonly_newstyle_class_init_toomanyargs(self):
+ class foo(object):
+ def __init__(self, context, request):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_requestonly_newstyle_class_init_onearg_named_request(self):
+ class foo(object):
+ def __init__(self, request):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_newstyle_class_init_onearg_named_somethingelse(self):
+ class foo(object):
+ def __init__(self, req):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_newstyle_class_init_defaultargs_firstname_not_request(self):
+ class foo(object):
+ def __init__(self, context, request=None):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_newstyle_class_init_defaultargs_firstname_request(self):
+ class foo(object):
+ def __init__(self, request, foo=1, bar=2):
+ """ """
+ self.assertTrue(self._callFUT(foo, argname='request'))
+
+ def test_newstyle_class_init_firstname_request_with_secondname(self):
+ class foo(object):
+ def __init__(self, request, two):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_newstyle_class_init_noargs(self):
+ class foo(object):
+ def __init__():
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_oldstyle_class_no_init(self):
+ class foo:
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_oldstyle_class_init_toomanyargs(self):
+ class foo:
+ def __init__(self, context, request):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_oldstyle_class_init_onearg_named_request(self):
+ class foo:
+ def __init__(self, request):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_oldstyle_class_init_onearg_named_somethingelse(self):
+ class foo:
+ def __init__(self, req):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_oldstyle_class_init_defaultargs_firstname_not_request(self):
+ class foo:
+ def __init__(self, context, request=None):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_oldstyle_class_init_defaultargs_firstname_request(self):
+ class foo:
+ def __init__(self, request, foo=1, bar=2):
+ """ """
+ self.assertTrue(self._callFUT(foo, argname='request'), True)
+
+ def test_oldstyle_class_init_noargs(self):
+ class foo:
+ def __init__():
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_function_toomanyargs(self):
+ def foo(context, request):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_function_with_attr_false(self):
+ def bar(context, request):
+ """ """
+ def foo(context, request):
+ """ """
+ foo.bar = bar
+ self.assertFalse(self._callFUT(foo, 'bar'))
+
+ def test_function_with_attr_true(self):
+ def bar(context, request):
+ """ """
+ def foo(request):
+ """ """
+ foo.bar = bar
+ self.assertTrue(self._callFUT(foo, 'bar'))
+
+ def test_function_onearg_named_request(self):
+ def foo(request):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_function_onearg_named_somethingelse(self):
+ def foo(req):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_function_defaultargs_firstname_not_request(self):
+ def foo(context, request=None):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_function_defaultargs_firstname_request(self):
+ def foo(request, foo=1, bar=2):
+ """ """
+ self.assertTrue(self._callFUT(foo, argname='request'))
+
+ def test_function_noargs(self):
+ def foo():
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_instance_toomanyargs(self):
+ class Foo:
+ def __call__(self, context, request):
+ """ """
+ foo = Foo()
+ self.assertFalse(self._callFUT(foo))
+
+ def test_instance_defaultargs_onearg_named_request(self):
+ class Foo:
+ def __call__(self, request):
+ """ """
+ foo = Foo()
+ self.assertTrue(self._callFUT(foo))
+
+ def test_instance_defaultargs_onearg_named_somethingelse(self):
+ class Foo:
+ def __call__(self, req):
+ """ """
+ foo = Foo()
+ self.assertTrue(self._callFUT(foo))
+
+ def test_instance_defaultargs_firstname_not_request(self):
+ class Foo:
+ def __call__(self, context, request=None):
+ """ """
+ foo = Foo()
+ self.assertFalse(self._callFUT(foo))
+
+ def test_instance_defaultargs_firstname_request(self):
+ class Foo:
+ def __call__(self, request, foo=1, bar=2):
+ """ """
+ foo = Foo()
+ self.assertTrue(self._callFUT(foo, argname='request'), True)
+
+ def test_instance_nocall(self):
+ class Foo: pass
+ foo = Foo()
+ self.assertFalse(self._callFUT(foo))
+
+ def test_method_onearg_named_request(self):
+ class Foo:
+ def method(self, request):
+ """ """
+ foo = Foo()
+ self.assertTrue(self._callFUT(foo.method))
-class TestActionInfo(unittest.TestCase):
- def _getTargetClass(self):
- from pyramid.config.util import ActionInfo
- return ActionInfo
-
- def _makeOne(self, filename, lineno, function, linerepr):
- return self._getTargetClass()(filename, lineno, function, linerepr)
-
- def test_class_conforms(self):
- from zope.interface.verify import verifyClass
- from pyramid.interfaces import IActionInfo
- verifyClass(IActionInfo, self._getTargetClass())
-
- def test_instance_conforms(self):
- from zope.interface.verify import verifyObject
- from pyramid.interfaces import IActionInfo
- verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f'))
-
- def test_ctor(self):
- inst = self._makeOne('filename', 10, 'function', 'src')
- self.assertEqual(inst.file, 'filename')
- self.assertEqual(inst.line, 10)
- self.assertEqual(inst.function, 'function')
- self.assertEqual(inst.src, 'src')
-
- def test___str__(self):
- inst = self._makeOne('filename', 0, 'function', ' linerepr ')
- self.assertEqual(str(inst),
- "Line 0 of file filename:\n linerepr ")
class DummyCustomPredicate(object):
def __init__(self):
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 8324eb2b9..4cebdce8a 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -1896,192 +1896,20 @@ class TestViewsConfigurationMixin(unittest.TestCase):
from pyramid.tests import test_config
self.assertEqual(result, test_config)
-
class Test_requestonly(unittest.TestCase):
def _callFUT(self, view, attr=None):
from pyramid.config.views import requestonly
- return requestonly(view, attr)
-
- def test_requestonly_newstyle_class_no_init(self):
- class foo(object):
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_requestonly_newstyle_class_init_toomanyargs(self):
- class foo(object):
- def __init__(self, context, request):
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_requestonly_newstyle_class_init_onearg_named_request(self):
- class foo(object):
- def __init__(self, request):
- """ """
- self.assertTrue(self._callFUT(foo))
-
- def test_newstyle_class_init_onearg_named_somethingelse(self):
- class foo(object):
- def __init__(self, req):
- """ """
- self.assertTrue(self._callFUT(foo))
-
- def test_newstyle_class_init_defaultargs_firstname_not_request(self):
- class foo(object):
- def __init__(self, context, request=None):
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_newstyle_class_init_defaultargs_firstname_request(self):
- class foo(object):
- def __init__(self, request, foo=1, bar=2):
- """ """
- self.assertTrue(self._callFUT(foo))
-
- def test_newstyle_class_init_firstname_request_with_secondname(self):
- class foo(object):
- def __init__(self, request, two):
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_newstyle_class_init_noargs(self):
- class foo(object):
- def __init__():
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_oldstyle_class_no_init(self):
- class foo:
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_oldstyle_class_init_toomanyargs(self):
- class foo:
- def __init__(self, context, request):
- """ """
- self.assertFalse(self._callFUT(foo))
+ return requestonly(view, attr=attr)
- def test_oldstyle_class_init_onearg_named_request(self):
- class foo:
- def __init__(self, request):
- """ """
- self.assertTrue(self._callFUT(foo))
-
- def test_oldstyle_class_init_onearg_named_somethingelse(self):
- class foo:
- def __init__(self, req):
- """ """
- self.assertTrue(self._callFUT(foo))
-
- def test_oldstyle_class_init_defaultargs_firstname_not_request(self):
- class foo:
- def __init__(self, context, request=None):
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_oldstyle_class_init_defaultargs_firstname_request(self):
- class foo:
- def __init__(self, request, foo=1, bar=2):
- """ """
- self.assertTrue(self._callFUT(foo), True)
-
- def test_oldstyle_class_init_noargs(self):
- class foo:
- def __init__():
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_function_toomanyargs(self):
- def foo(context, request):
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_function_with_attr_false(self):
- def bar(context, request):
- """ """
- def foo(context, request):
- """ """
- foo.bar = bar
- self.assertFalse(self._callFUT(foo, 'bar'))
-
- def test_function_with_attr_true(self):
- def bar(context, request):
- """ """
- def foo(request):
- """ """
- foo.bar = bar
- self.assertTrue(self._callFUT(foo, 'bar'))
+ def test_defaults(self):
+ def aview(request, a=1, b=2): pass
+ self.assertTrue(self._callFUT(aview))
- def test_function_onearg_named_request(self):
- def foo(request):
- """ """
- self.assertTrue(self._callFUT(foo))
-
- def test_function_onearg_named_somethingelse(self):
- def foo(req):
- """ """
- self.assertTrue(self._callFUT(foo))
-
- def test_function_defaultargs_firstname_not_request(self):
- def foo(context, request=None):
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_function_defaultargs_firstname_request(self):
- def foo(request, foo=1, bar=2):
- """ """
- self.assertTrue(self._callFUT(foo))
-
- def test_function_noargs(self):
- def foo():
- """ """
- self.assertFalse(self._callFUT(foo))
-
- def test_instance_toomanyargs(self):
- class Foo:
- def __call__(self, context, request):
- """ """
- foo = Foo()
- self.assertFalse(self._callFUT(foo))
-
- def test_instance_defaultargs_onearg_named_request(self):
- class Foo:
- def __call__(self, request):
- """ """
- foo = Foo()
- self.assertTrue(self._callFUT(foo))
-
- def test_instance_defaultargs_onearg_named_somethingelse(self):
- class Foo:
- def __call__(self, req):
- """ """
- foo = Foo()
- self.assertTrue(self._callFUT(foo))
-
- def test_instance_defaultargs_firstname_not_request(self):
- class Foo:
- def __call__(self, context, request=None):
- """ """
- foo = Foo()
- self.assertFalse(self._callFUT(foo))
-
- def test_instance_defaultargs_firstname_request(self):
- class Foo:
- def __call__(self, request, foo=1, bar=2):
- """ """
- foo = Foo()
- self.assertTrue(self._callFUT(foo), True)
-
- def test_instance_nocall(self):
- class Foo: pass
- foo = Foo()
- self.assertFalse(self._callFUT(foo))
-
- def test_method_onearg_named_request(self):
- class Foo:
- def method(self, request):
- """ """
- foo = Foo()
- self.assertTrue(self._callFUT(foo.method))
+ def test_otherattr(self):
+ class AView(object):
+ def __init__(self, request, a=1, b=2): pass
+ def bleh(self): pass
+ self.assertTrue(self._callFUT(AView, 'bleh'))
class Test_isexception(unittest.TestCase):
def _callFUT(self, ob):
@@ -2483,6 +2311,29 @@ class TestViewDeriver(unittest.TestCase):
else: # pragma: no cover
raise AssertionError
+ def test_function_returns_true_Response_no_renderer(self):
+ from pyramid.response import Response
+ r = Response('Hello')
+ def view(request):
+ return r
+ deriver = self._makeOne()
+ result = deriver(view)
+ self.assertFalse(result is view)
+ response = result(None, None)
+ self.assertEqual(response, r)
+
+ def test_function_returns_true_Response_with_renderer(self):
+ from pyramid.response import Response
+ r = Response('Hello')
+ def view(request):
+ return r
+ renderer = object()
+ deriver = self._makeOne(renderer=renderer)
+ result = deriver(view)
+ self.assertFalse(result is view)
+ response = result(None, None)
+ self.assertEqual(response, r)
+
def test_requestonly_default_method_returns_non_adaptable(self):
request = DummyRequest()
class AView(object):
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index 9a8f842aa..bf3960b2d 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -174,6 +174,18 @@ class TestStaticAppBase(IntegrationBase):
def test_oob_slash(self):
self.testapp.get('/%2F/test_integration.py', status=404)
+class TestEventOnlySubscribers(IntegrationBase, unittest.TestCase):
+ package = 'pyramid.tests.pkgs.eventonly'
+
+ def test_sendfoo(self):
+ res = self.testapp.get('/sendfoo', status=200)
+ self.assertEqual(sorted(res.body.split()), [b'foo', b'fooyup'])
+
+ def test_sendfoobar(self):
+ res = self.testapp.get('/sendfoobar', status=200)
+ self.assertEqual(sorted(res.body.split()),
+ [b'foobar', b'foobar2', b'foobaryup', b'foobaryup2'])
+
class TestStaticAppUsingAbsPath(TestStaticAppBase, unittest.TestCase):
package = 'pyramid.tests.pkgs.static_abspath'
diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py
index d94b46a9f..b72e0e6b6 100644
--- a/pyramid/tests/test_paster.py
+++ b/pyramid/tests/test_paster.py
@@ -2,14 +2,16 @@ import os
import unittest
class Test_get_app(unittest.TestCase):
- def _callFUT(self, config_file, section_name, loadapp):
+ def _callFUT(self, config_file, section_name, options=None, loadapp=None):
from pyramid.paster import get_app
- return get_app(config_file, section_name, loadapp)
+ return get_app(
+ config_file, section_name, options=options, loadapp=loadapp
+ )
def test_it(self):
app = DummyApp()
loadapp = DummyLoadWSGI(app)
- result = self._callFUT('/foo/bar/myapp.ini', 'myapp', loadapp)
+ result = self._callFUT('/foo/bar/myapp.ini', 'myapp', loadapp=loadapp)
self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini')
self.assertEqual(loadapp.section_name, 'myapp')
self.assertEqual(loadapp.relative_to, os.getcwd())
@@ -18,7 +20,9 @@ class Test_get_app(unittest.TestCase):
def test_it_with_hash(self):
app = DummyApp()
loadapp = DummyLoadWSGI(app)
- result = self._callFUT('/foo/bar/myapp.ini#myapp', None, loadapp)
+ result = self._callFUT(
+ '/foo/bar/myapp.ini#myapp', None, loadapp=loadapp
+ )
self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini')
self.assertEqual(loadapp.section_name, 'myapp')
self.assertEqual(loadapp.relative_to, os.getcwd())
@@ -27,12 +31,30 @@ class Test_get_app(unittest.TestCase):
def test_it_with_hash_and_name_override(self):
app = DummyApp()
loadapp = DummyLoadWSGI(app)
- result = self._callFUT('/foo/bar/myapp.ini#myapp', 'yourapp', loadapp)
+ result = self._callFUT(
+ '/foo/bar/myapp.ini#myapp', 'yourapp', loadapp=loadapp
+ )
self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini')
self.assertEqual(loadapp.section_name, 'yourapp')
self.assertEqual(loadapp.relative_to, os.getcwd())
self.assertEqual(result, app)
+ def test_it_with_options(self):
+ app = DummyApp()
+ loadapp = DummyLoadWSGI(app)
+ options = {'a':1}
+ result = self._callFUT(
+ '/foo/bar/myapp.ini#myapp',
+ 'yourapp',
+ loadapp=loadapp,
+ options=options,
+ )
+ self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini')
+ self.assertEqual(loadapp.section_name, 'yourapp')
+ self.assertEqual(loadapp.relative_to, os.getcwd())
+ self.assertEqual(loadapp.kw, {'global_conf':options})
+ self.assertEqual(result, app)
+
class Test_get_appsettings(unittest.TestCase):
def _callFUT(self, config_file, section_name, appconfig):
from pyramid.paster import get_appsettings
@@ -132,10 +154,11 @@ class DummyLoadWSGI:
def __init__(self, result):
self.result = result
- def __call__(self, config_name, name=None, relative_to=None):
+ def __call__(self, config_name, name=None, relative_to=None, **kw):
self.config_name = config_name
self.section_name = name
self.relative_to = relative_to
+ self.kw = kw
return self.result
class DummyApp:
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index 945e36a7f..565c6377e 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -230,6 +230,13 @@ class TestRequest(unittest.TestCase):
request.registry = self.config.registry
self.assertEqual(request.is_response('abc'), False)
+ def test_is_response_true_ob_is_pyramid_response(self):
+ from pyramid.response import Response
+ r = Response('hello')
+ request = self._makeOne()
+ request.registry = self.config.registry
+ self.assertEqual(request.is_response(r), True)
+
def test_is_response_false_adapter_is_not_self(self):
from pyramid.interfaces import IResponse
request = self._makeOne()
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index 778b27473..65152ca05 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -24,7 +24,7 @@ class TestRouter(unittest.TestCase):
if mapper is None:
mapper = RoutesMapper()
self.registry.registerUtility(mapper, IRoutesMapper)
- mapper.connect(name, path, factory)
+ return mapper.connect(name, path, factory)
def _registerLogger(self):
from pyramid.interfaces import IDebugLogger
@@ -657,7 +657,8 @@ class TestRouter(unittest.TestCase):
root = object()
def factory(request):
return root
- self._connectRoute('foo', 'archives/:action/:article', factory)
+ route = self._connectRoute('foo', 'archives/:action/:article', factory)
+ route.predicates = [DummyPredicate()]
context = DummyContext()
self._registerTraverserFactory(context)
response = DummyResponse()
@@ -686,7 +687,11 @@ class TestRouter(unittest.TestCase):
"route matched for url http://localhost:8080"
"/archives/action1/article1; "
"route_name: 'foo', "
- "path_info: "))
+ "path_info: ")
+ )
+ self.assertTrue(
+ "predicates: 'predicate'" in logger.messages[0]
+ )
def test_call_route_match_miss_debug_routematch(self):
from pyramid.httpexceptions import HTTPNotFound
@@ -1159,6 +1164,12 @@ class TestRouter(unittest.TestCase):
start_response = DummyStartResponse()
self.assertRaises(RuntimeError, router, environ, start_response)
+class DummyPredicate(object):
+ def __call__(self, info, request):
+ return True
+ def text(self):
+ return 'predicate'
+
class DummyContext:
pass
diff --git a/pyramid/tests/test_scripts/test_common.py b/pyramid/tests/test_scripts/test_common.py
index c3c792ca4..13ab0ae6a 100644
--- a/pyramid/tests/test_scripts/test_common.py
+++ b/pyramid/tests/test_scripts/test_common.py
@@ -17,6 +17,19 @@ class Test_logging_file_config(unittest.TestCase):
def fileConfig(self, config_file, dict):
return config_file, dict
+class TestParseVars(unittest.TestCase):
+ def test_parse_vars_good(self):
+ from pyramid.scripts.common import parse_vars
+ vars = ['a=1', 'b=2']
+ result = parse_vars(vars)
+ self.assertEqual(result, {'a': '1', 'b': '2'})
+
+ def test_parse_vars_bad(self):
+ from pyramid.scripts.common import parse_vars
+ vars = ['a']
+ self.assertRaises(ValueError, parse_vars, vars)
+
+
class DummyConfigParser(object):
def read(self, x):
pass
diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py
index cf7af4218..91d2b322a 100644
--- a/pyramid/tests/test_scripts/test_prequest.py
+++ b/pyramid/tests/test_scripts/test_prequest.py
@@ -13,9 +13,11 @@ class TestPRequestCommand(unittest.TestCase):
cmd.out = self.out
return cmd
- def get_app(self, spec, app_name=None):
+ def get_app(self, spec, app_name=None, options=None):
self._spec = spec
self._app_name = app_name
+ self._options = options or {}
+
def helloworld(environ, start_request):
self._environ = environ
self._path_info = environ['PATH_INFO']
diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py
index fad8c1895..25a3cd2e3 100644
--- a/pyramid/tests/test_scripts/test_proutes.py
+++ b/pyramid/tests/test_scripts/test_proutes.py
@@ -12,6 +12,28 @@ class TestPRoutesCommand(unittest.TestCase):
cmd.args = ('/foo/bar/myapp.ini#myapp',)
return cmd
+ def test_good_args(self):
+ cmd = self._getTargetClass()([])
+ cmd.bootstrap = (dummy.DummyBootstrap(),)
+ cmd.args = ('/foo/bar/myapp.ini#myapp', 'a=1')
+ route = dummy.DummyRoute('a', '/a')
+ mapper = dummy.DummyMapper(route)
+ cmd._get_mapper = lambda *arg: mapper
+ L = []
+ cmd.out = lambda msg: L.append(msg)
+ cmd.run()
+ self.assertTrue('<unknown>' in ''.join(L))
+
+ def test_bad_args(self):
+ cmd = self._getTargetClass()([])
+ cmd.bootstrap = (dummy.DummyBootstrap(),)
+ cmd.args = ('/foo/bar/myapp.ini#myapp', 'a')
+ route = dummy.DummyRoute('a', '/a')
+ mapper = dummy.DummyMapper(route)
+ cmd._get_mapper = lambda *arg: mapper
+
+ self.assertRaises(ValueError, cmd.run)
+
def test_no_routes(self):
command = self._makeOne()
mapper = dummy.DummyMapper()
diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py
index d7b252d92..6e4b0f17d 100644
--- a/pyramid/tests/test_scripts/test_pserve.py
+++ b/pyramid/tests/test_scripts/test_pserve.py
@@ -22,6 +22,12 @@ class TestPServeCommand(unittest.TestCase):
def out(self, msg):
self.out_.write(msg)
+ def _get_server(*args, **kwargs):
+ def server(app):
+ return ''
+
+ return server
+
def _getTargetClass(self):
from pyramid.scripts.pserve import PServeCommand
return PServeCommand
@@ -193,16 +199,39 @@ class TestPServeCommand(unittest.TestCase):
msg = 'PID in %s is not valid (deleting)' % fn
self.assertEqual(self.out_.getvalue(), msg)
- def test_parse_vars_good(self):
- vars = ['a=1', 'b=2']
- inst = self._makeOne('development.ini')
- result = inst.parse_vars(vars)
+ def test_get_options_with_command(self):
+ inst = self._makeOne()
+ inst.args = ['foo', 'stop', 'a=1', 'b=2']
+ result = inst.get_options()
self.assertEqual(result, {'a': '1', 'b': '2'})
+ def test_get_options_no_command(self):
+ inst = self._makeOne()
+ inst.args = ['foo', 'a=1', 'b=2']
+ result = inst.get_options()
+ self.assertEqual(result, {'a': '1', 'b': '2'})
+
+ def test_parse_vars_good(self):
+ from pyramid.tests.test_scripts.dummy import DummyApp
+
+ inst = self._makeOne('development.ini', 'a=1', 'b=2')
+ inst.loadserver = self._get_server
+
+
+ app = DummyApp()
+
+ def get_app(*args, **kwargs):
+ app.global_conf = kwargs.get('global_conf', None)
+
+ inst.loadapp = get_app
+ inst.run()
+
+ self.assertEqual(app.global_conf, {'a': '1', 'b': '2'})
+
def test_parse_vars_bad(self):
- vars = ['a']
- inst = self._makeOne('development.ini')
- self.assertRaises(ValueError, inst.parse_vars, vars)
+ inst = self._makeOne('development.ini', 'a')
+ inst.loadserver = self._get_server
+ self.assertRaises(ValueError, inst.run)
class Test_read_pidfile(unittest.TestCase):
def _callFUT(self, filename):
diff --git a/pyramid/tests/test_scripts/test_pviews.py b/pyramid/tests/test_scripts/test_pviews.py
index 6a919c31b..266d1ec90 100644
--- a/pyramid/tests/test_scripts/test_pviews.py
+++ b/pyramid/tests/test_scripts/test_pviews.py
@@ -379,7 +379,7 @@ class TestPViewsCommand(unittest.TestCase):
L = []
command.out = L.append
def predicate(): pass
- predicate.__text__ = "predicate = x"
+ predicate.text = lambda *arg: "predicate = x"
route = dummy.DummyRoute('a', '/a', matchdict={}, predicate=predicate)
view = dummy.DummyView(context='context', view_name='a',
matched_route=route, subpath='')
diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py
index 785950230..2ca4c4a66 100644
--- a/pyramid/tests/test_util.py
+++ b/pyramid/tests/test_util.py
@@ -545,6 +545,37 @@ class TestSentinel(unittest.TestCase):
r = repr(Sentinel('ABC'))
self.assertEqual(r, 'ABC')
+class TestActionInfo(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.util import ActionInfo
+ return ActionInfo
+
+ def _makeOne(self, filename, lineno, function, linerepr):
+ return self._getTargetClass()(filename, lineno, function, linerepr)
+
+ def test_class_conforms(self):
+ from zope.interface.verify import verifyClass
+ from pyramid.interfaces import IActionInfo
+ verifyClass(IActionInfo, self._getTargetClass())
+
+ def test_instance_conforms(self):
+ from zope.interface.verify import verifyObject
+ from pyramid.interfaces import IActionInfo
+ verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f'))
+
+ def test_ctor(self):
+ inst = self._makeOne('filename', 10, 'function', 'src')
+ self.assertEqual(inst.file, 'filename')
+ self.assertEqual(inst.line, 10)
+ self.assertEqual(inst.function, 'function')
+ self.assertEqual(inst.src, 'src')
+
+ def test___str__(self):
+ inst = self._makeOne('filename', 0, 'function', ' linerepr ')
+ self.assertEqual(str(inst),
+ "Line 0 of file filename:\n linerepr ")
+
+
def dummyfunc(): pass
class Dummy(object):
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
index 0af941e0d..a78b0cbab 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -224,12 +224,29 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase):
response = DummyResponse()
view = make_view(response)
def anotherview(context, request):
- return DummyResponse('anotherview')
+ return DummyResponse(b'anotherview')
view.__call_permissive__ = anotherview
self._registerView(request.registry, view, 'registered')
iterable = self._callFUT(context, request, name='registered',
secure=False)
- self.assertEqual(iterable, ['anotherview'])
+ self.assertEqual(iterable, [b'anotherview'])
+
+ def test_verify_output_bytestring(self):
+ from pyramid.request import Request
+ from pyramid.config import Configurator
+ from pyramid.view import render_view
+ from webob.compat import text_type
+ config = Configurator(settings={})
+ def view(request):
+ request.response.text = text_type('<body></body>')
+ return request.response
+
+ config.add_view(name='test', view=view)
+ config.commit()
+
+ r = Request({})
+ r.registry = config.registry
+ self.assertEqual(render_view(object(), r, 'test'), b'<body></body>')
def test_call_request_has_no_registry(self):
request = self._makeRequest()
@@ -261,7 +278,7 @@ class RenderViewTests(BaseTest, unittest.TestCase):
view = make_view(response)
self._registerView(request.registry, view, 'registered')
s = self._callFUT(context, request, name='registered', secure=True)
- self.assertEqual(s, '')
+ self.assertEqual(s, b'')
def test_call_view_registered_insecure_no_call_permissive(self):
context = self._makeContext()
@@ -270,7 +287,7 @@ class RenderViewTests(BaseTest, unittest.TestCase):
view = make_view(response)
self._registerView(request.registry, view, 'registered')
s = self._callFUT(context, request, name='registered', secure=False)
- self.assertEqual(s, '')
+ self.assertEqual(s, b'')
def test_call_view_registered_insecure_with_call_permissive(self):
context = self._makeContext()
@@ -278,11 +295,11 @@ class RenderViewTests(BaseTest, unittest.TestCase):
response = DummyResponse()
view = make_view(response)
def anotherview(context, request):
- return DummyResponse('anotherview')
+ return DummyResponse(b'anotherview')
view.__call_permissive__ = anotherview
self._registerView(request.registry, view, 'registered')
s = self._callFUT(context, request, name='registered', secure=False)
- self.assertEqual(s, 'anotherview')
+ self.assertEqual(s, b'anotherview')
class TestIsResponse(unittest.TestCase):
def setUp(self):
diff --git a/pyramid/util.py b/pyramid/util.py
index d83837322..ca7f5951c 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -1,6 +1,10 @@
+import functools
import inspect
+import traceback
import weakref
+from zope.interface import implementer
+
from pyramid.exceptions import (
ConfigurationError,
CyclicDependencyError,
@@ -15,6 +19,7 @@ from pyramid.compat import (
PY3,
)
+from pyramid.interfaces import IActionInfo
from pyramid.path import DottedNameResolver as _DottedNameResolver
class DottedNameResolver(_DottedNameResolver):
@@ -453,3 +458,65 @@ class TopologicalSorter(object):
return result
+def viewdefaults(wrapped):
+ """ Decorator for add_view-like methods which takes into account
+ __view_defaults__ attached to view it is passed. Not a documented API but
+ used by some external systems."""
+ def wrapper(self, *arg, **kw):
+ defaults = {}
+ if arg:
+ view = arg[0]
+ else:
+ view = kw.get('view')
+ view = self.maybe_dotted(view)
+ if inspect.isclass(view):
+ defaults = getattr(view, '__view_defaults__', {}).copy()
+ defaults.update(kw)
+ defaults['_backframes'] = 3 # for action_method
+ return wrapped(self, *arg, **defaults)
+ return functools.wraps(wrapped)(wrapper)
+
+@implementer(IActionInfo)
+class ActionInfo(object):
+ def __init__(self, file, line, function, src):
+ self.file = file
+ self.line = line
+ self.function = function
+ self.src = src
+
+ def __str__(self):
+ srclines = self.src.split('\n')
+ src = '\n'.join(' %s' % x for x in srclines)
+ return 'Line %s of file %s:\n%s' % (self.line, self.file, src)
+
+def action_method(wrapped):
+ """ Wrapper to provide the right conflict info report data when a method
+ that calls Configurator.action calls another that does the same. Not a
+ documented API but used by some external systems."""
+ def wrapper(self, *arg, **kw):
+ if self._ainfo is None:
+ self._ainfo = []
+ info = kw.pop('_info', None)
+ # backframes for outer decorators to actionmethods
+ backframes = kw.pop('_backframes', 2)
+ if is_nonstr_iter(info) and len(info) == 4:
+ # _info permitted as extract_stack tuple
+ info = ActionInfo(*info)
+ if info is None:
+ try:
+ f = traceback.extract_stack(limit=3)
+ info = ActionInfo(*f[-backframes])
+ except: # pragma: no cover
+ info = ActionInfo(None, 0, '', '')
+ self._ainfo.append(info)
+ try:
+ result = wrapped(self, *arg, **kw)
+ finally:
+ self._ainfo.pop()
+ return result
+
+ if hasattr(wrapped, '__name__'):
+ functools.update_wrapper(wrapper, wrapped)
+ wrapper.__docobj__ = wrapped
+ return wrapper
+
diff --git a/pyramid/view.py b/pyramid/view.py
index 835982e79..1a66c9e9c 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -3,6 +3,7 @@ import venusian
from zope.interface import providedBy
from zope.deprecation import deprecated
+
from pyramid.interfaces import (
IRoutesMapper,
IView,
@@ -93,8 +94,8 @@ def render_view_to_iterable(context, request, name='', secure=True):
:exc:`ValueError` if a view function is found and called but the
view function's result does not have an ``app_iter`` attribute.
- You can usually get the string representation of the return value
- of this function by calling ``''.join(iterable)``, or just use
+ You can usually get the bytestring representation of the return value of
+ this function by calling ``b''.join(iterable)``, or just use
:func:`pyramid.view.render_view` instead.
If ``secure`` is ``True``, and the view is protected by a permission, the
@@ -116,7 +117,7 @@ def render_view(context, request, name='', secure=True):
configuration` that matches the :term:`view name` ``name``
registered against the specified ``context`` and ``request``
and unwind the view response's ``app_iter`` (see
- :ref:`the_response`) into a single string. This function will
+ :ref:`the_response`) into a single bytestring. This function will
return ``None`` if a corresponding :term:`view callable` cannot be
found (when no :term:`view configuration` matches the combination
of ``name`` / ``context`` / and ``request``). Additionally, this
@@ -136,7 +137,7 @@ def render_view(context, request, name='', secure=True):
iterable = render_view_to_iterable(context, request, name, secure)
if iterable is None:
return None
- return ''.join(iterable)
+ return b''.join(iterable)
class view_config(object):
""" A function, class or method :term:`decorator` which allows a
diff --git a/setup.py b/setup.py
index 4ea63a3ee..55da3378d 100644
--- a/setup.py
+++ b/setup.py
@@ -68,7 +68,7 @@ testing_extras = tests_require + [
]
setup(name='pyramid',
- version='1.4a3',
+ version='1.4b1',
description=('The Pyramid web application development framework, a '
'Pylons project'),
long_description=README + '\n\n' + CHANGES,