diff options
| author | cguardia <carlos.delaguardia@gmail.com> | 2011-04-25 00:37:33 -0500 |
|---|---|---|
| committer | cguardia <carlos.delaguardia@gmail.com> | 2011-04-25 00:37:33 -0500 |
| commit | b61680ad87abcb524ae3fe846cd591c6ec1d54ef (patch) | |
| tree | e2fcd960d9119efde4a1b7eecec3880f98397d2f | |
| parent | 1b395e6d43b9450ea652d9addb675936b507ea52 (diff) | |
| parent | bb825910b3b68b4b167572fd5252cc99be88e17e (diff) | |
| download | pyramid-b61680ad87abcb524ae3fe846cd591c6ec1d54ef.tar.gz pyramid-b61680ad87abcb524ae3fe846cd591c6ec1d54ef.tar.bz2 pyramid-b61680ad87abcb524ae3fe846cd591c6ec1d54ef.zip | |
Merge branch 'master' of https://github.com/Pylons/pyramid
68 files changed, 2420 insertions, 1223 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 0bd19572a..d329c260d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -25,9 +25,25 @@ Documentation ``pyramid.config.Configurator.set_view_mapper`` and refer to it within Hooks chapter section named "Using a View Mapper". +- Added section to the "Environment Variables and ``.ini`` File Settings" + chapter in the narrative documentation section entitled "Adding a Custom + Setting". + Features -------- +- Accessing the ``response`` attribute of a ``pyramid.request.Request`` + object (e.g. ``request.response`` within a view) now produces a new + ``pyramid.response.Response`` object. This feature is meant to be used + mainly when a view configured with a renderer needs to set response + attributes: all renderers will use the Response object implied by + ``request.response`` as the response object returned to the router. + + ``request.response`` can also be used by code in a view that does not use a + renderer, however the response object that is produced by + ``request.response`` must be returned when a renderer is not in play (it is + not a "global" response). + - Integers and longs passed as ``elements`` to ``pyramid.url.resource_url`` or ``pyramid.request.Request.resource_url`` e.g. ``resource_url(context, request, 1, 2)`` (``1`` and ``2`` are the ``elements``) will now be @@ -64,6 +80,16 @@ Features Bug Fixes --------- +- URL pattern markers used in URL dispatch are permitted to specify a custom + regex. For example, the pattern ``/{foo:\d+}`` means to match ``/12345`` + (foo==12345 in the match dictionary) but not ``/abc``. However, custom + regexes in a pattern marker which used squiggly brackets did not work. For + example, ``/{foo:\d{4}}`` would fail to match ``/1234`` and + ``/{foo:\d{1,2}}`` would fail to match ``/1`` or ``/11``. One level of + inner squiggly brackets is now recognized so that the prior two patterns + given as examples now work. See also + https://github.com/Pylons/pyramid/issues/#issue/123. + - Don't send port numbers along with domain information in cookies set by AuthTktCookieHelper (see https://github.com/Pylons/pyramid/issues/131). @@ -104,6 +130,95 @@ Bug Fixes DummyRequest instead of eagerly assigning an attribute. See also https://github.com/Pylons/pyramid/issues/165 +- When visiting a URL that represented a static view which resolved to a + subdirectory, the ``index.html`` of that subdirectory would not be served + properly. Instead, a redirect to ``/subdir`` would be issued. This has + been fixed, and now visiting a subdirectory that contains an ``index.html`` + within a static view returns the index.html properly. See also + https://github.com/Pylons/pyramid/issues/67. + +- Redirects issued by a static view did not take into account any existing + ``SCRIPT_NAME`` (such as one set by a url mapping composite). Now they do. + +- The ``pyramid.wsgi.wsgiapp2`` decorator did not take into account the + ``SCRIPT_NAME`` in the origin request. + +- The ``pyramid.wsgi.wsgiapp2`` decorator effectively only worked when it + decorated a view found via traversal; it ignored the ``PATH_INFO`` that was + part of a url-dispatch-matched view. + +Deprecations +------------ + +- Deprecated all assignments to ``request.response_*`` attributes (for + example ``request.response_content_type = 'foo'`` is now deprecated). + Assignments and mutations of assignable request attributes that were + considered by the framework for response influence are now deprecated: + ``response_content_type``, ``response_headerlist``, ``response_status``, + ``response_charset``, and ``response_cache_for``. Instead of assigning + these to the request object for later detection by the rendering machinery, + users should use the appropriate API of the Response object created by + accessing ``request.response`` (e.g. code which does + ``request.response_content_type = 'abc'`` should be changed to + ``request.response.content_type = 'abc'``). + +- Passing view-related parameters to + ``pyramid.config.Configurator.add_route`` is now deprecated. Previously, a + view was permitted to be connected to a route using a set of ``view*`` + parameters passed to the ``add_route`` method of the Configurator. This + was a shorthand which replaced the need to perform a subsequent call to + ``add_view``. For example, it was valid (and often recommended) to do:: + + config.add_route('home', '/', view='mypackage.views.myview', + view_renderer='some/renderer.pt') + + Passing ``view*`` arguments to ``add_route`` is now deprecated in favor of + connecting a view to a predefined route via ``Configurator.add_view`` using + the route's ``route_name`` parameter. As a result, the above example + should now be spelled:: + + config.add_route('home', '/') + config.add_view('mypackage.views.myview', route_name='home') + renderer='some/renderer.pt') + + This deprecation was done to reduce confusion observed in IRC, as well as + to (eventually) reduce documentation burden (see also + https://github.com/Pylons/pyramid/issues/164). A deprecation warning is + now issued when any view-related parameter is passed to + ``Configurator.add_route``. + +Behavior Changes +---------------- + +- A custom request factory is now required to return a response object that + has a ``response`` attribute (or "reified"/lazy property) if they the + request is meant to be used in a view that uses a renderer. This + ``response`` attribute should be an instance of the class + ``pyramid.response.Response``. + +- The JSON and string renderer factories now assign to + ``request.response.content_type`` rather than + ``request.response_content_type``. Each renderer factory determines + whether it should change the content type of the response by comparing the + response's content type against the response's default content type; if the + content type is not the default content type (usually ``text/html``), the + renderer changes the content type (to ``application/json`` or + ``text/plain`` for JSON and string renderers respectively). + +- The ``pyramid.wsgi.wsgiapp2`` now uses a slightly different method of + figuring out how to "fix" ``SCRIPT_NAME`` and ``PATH_INFO`` for the + downstream application. As a result, those values may differ slightly from + the perspective of the downstream application (for example, ``SCRIPT_NAME`` + will now never possess a trailing slash). + +Dependencies +------------ + +- Pyramid now depends on WebOb >= 1.0.2 as tests depend on the bugfix in that + release: "Fix handling of WSGI environs with missing ``SCRIPT_NAME``". + (Note that in reality, everyone should probably be using 1.0.4 or better + though, as WebOb 1.0.2 and 1.0.3 were effectively brownbag releases.) + 1.0 (2011-01-30) ================ diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 1ef785c3d..5a72f242e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -131,3 +131,7 @@ Contributors - Malthe Borch, 2011/02/28 +- Joel Bohman, 2011/04/16 + +- Juliusz Gonera, 2011/04/17 + @@ -6,9 +6,6 @@ Should-Have - MultiDict documentation. -- https://github.com/Pylons/pyramid/issues#issue/67 (fixing would make it - possible to render a static site from a static dir). - - Speed up startup time (defer _bootstrap and registerCommonDirectives() until needed by ZCML, as well as unfound speedups). @@ -27,11 +24,6 @@ Should-Have - translationdir ZCML directive use of ``path_spec`` should maybe die. -- Provide a response_set_cookie method on the request for rendered responses - that can be used as input to response.set_cookie? Or maybe accessing - ``request.response`` creates a Response, and you do - ``request.response.set_cookie(...)``. - - Fix message catalog extraction / compilation documentation. - Review http://alexmarandon.com/articles/zodb_bfg_pyramid_notes . @@ -39,6 +31,9 @@ Should-Have Nice-to-Have ------------ +- Response.RequestClass should probably be pyramid.request.Request but this + may imply actually subclassing webob.Response + - Consider per-form_id CSRF instead of per-session global CSRF token. - Better "Extending" chapter. @@ -94,9 +89,6 @@ Nice-to-Have action = '^foo$' mypackage.views.MyView.foo_GET -- Raise an exception when a value in response_headerlist is not a - string or decide to encode. - - Update App engine chapter with less creaky directions. - Add functionality that mocks the behavior of ``repoze.browserid``. diff --git a/docs/api/request.rst b/docs/api/request.rst index d17441c0a..639d0fcd8 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -85,6 +85,41 @@ of ``request.exception`` will be ``None`` within response and finished callbacks. + .. attribute:: response + + This attribute is actually a "reified" property which returns an + instance of the :class:`pyramid.response.Response` class. The response + object returned does not exist until this attribute is accessed. Once + it is accessed, subsequent accesses to this request object will return + the same :class:`~pyramid.response.Response` object. + + The ``request.response`` API can is used by renderers. A render obtains + the response object it will return from a view that uses that renderer + by accessing ``request.response``. Therefore, it's possible to use the + ``request.response`` API to set up a response object with "the right" + attributes (e.g. by calling ``request.response.set_cookie(...)`` or + ``request.response.content_type = 'text/plain'``, etc) within a view + that uses a renderer. For example, within a view that uses a + :term:`renderer`:: + + response = request.response + response.set_cookie('mycookie', 'mine, all mine!') + return {'text':'Value that will be used by the renderer'} + + Mutations to this response object will be preserved in the response sent + to the client after rendering. + + Non-renderer code can also make use of request.response instead of + creating a response "by hand". For example, in view code:: + + response = request.response + response.body = 'Hello!' + response.content_type = 'text/plain' + return response + + Note that the response in this circumstance is not "global"; it still + must be returned from the view code if a renderer is not used. + .. attribute:: session If a :term:`session factory` has been configured, this attribute @@ -127,10 +162,18 @@ .. attribute:: response_* + .. warning:: As of Pyramid 1.1, assignment to ``response_*`` attrs are + deprecated. Assigning to one will cause a deprecation warning to be + emitted. Instead of assigning ``response_*`` attributes to the + request, use API of the the :attr:`pyramid.request.Request.response` + object (exposed to view code as ``request.response``) to influence + response behavior. + You can set attributes on a :class:`pyramid.request.Request` which will influence the behavor of *rendered* responses (views which use a :term:`renderer` and which don't directly return a response). These attributes begin with ``response_``, such as ``response_headerlist``. If you need to influence response values from a view that uses a renderer (such as the status code, a header, the content type, etc) see, - :ref:`response_request_attrs`. + :ref:`response_prefixed_attrs`. + diff --git a/docs/conf.py b/docs/conf.py index a987106d4..a610351ff 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.0' +version = '1.1a0' # The full version, including alpha/beta/rc tags. release = version diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index 099bce35f..5ee554284 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -295,15 +295,9 @@ These are the methods of the configurator which provide conflict detection: :meth:`~pyramid.config.Configurator.set_locale_negotiator` and :meth:`~pyramid.config.Configurator.set_default_permission`. -Some other methods of the configurator also indirectly provide conflict -detection, because they're implemented in terms of conflict-aware methods: - -- :meth:`~pyramid.config.Configurator.add_route` does a second type of - conflict detection when a ``view`` parameter is passed (it calls - ``add_view``). - -- :meth:`~pyramid.config.Configurator.static_view`, a frontend for - ``add_route`` and ``add_view``. +:meth:`~pyramid.config.Configurator.add_static_view` also indirectly +provides conflict detection, because it's implemented in terms of the +conflict-aware ``add_route`` and ``add_view`` methods. .. _including_configuration: diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index bbb673ecc..8d0e7058c 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -341,7 +341,8 @@ application's startup code. # .. every other add_route declaration should come # before this one, as it will, by default, catch all requests - config.add_route('catchall_static', '/*subpath', 'myapp.static.static_view') + config.add_route('catchall_static', '/*subpath') + config.add_view('myapp.static.static_view', route_name='catchall_static') The special name ``*subpath`` above is used by the :class:`~pyramid.view.static` view callable to signify the path of the file @@ -384,8 +385,8 @@ Or you might register it to be the view callable for a particular route: .. code-block:: python :linenos: - config.add_route('favicon', '/favicon.ico', - view='myapp.views.favicon_view') + config.add_route('favicon', '/favicon.ico') + config.add_view('myapp.views.favicon_view', route_name='favicon') Because this is a simple view callable, it can be protected with a :term:`permission` or can be configured to respond under different diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index 7b7946aae..8c299f3a3 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -381,3 +381,66 @@ around in overridden asset directories. ``reload_assets`` makes the system *very slow* when templates are in use. Never set ``reload_assets`` to ``True`` on a production system. +Adding A Custom Setting +----------------------- + +From time to time, you may need to add a custom setting to your application. +Here's how: + +- If you're using an ``.ini`` file, change the ``.ini`` file, adding the + setting to the ``[app:foo]`` section representing your Pyramid application. + For example: + + .. code-block:: ini + + [app:myapp] + # .. other settings + debug_frobnosticator = True + +- In the ``main()`` function that represents the place that your Pyramid WSGI + application is created, anticipate that you'll be getting this key/value + pair as a setting and do any type conversion necessary. + + If you've done any type conversion of your custom value, reset the + converted values into the ``settings`` dictionary *before* you pass the + dictionary as ``settings`` to the :term:`Configurator`. For example: + + .. code-block:: python + + def main(global_config, **settings): + # ... + from pyramid.settings import asbool + debug_frobnosticator = asbool(settings.get( + 'debug_frobnosticator', 'false')) + settings['debug_frobnosticator'] = debug_frobnosticator + config = Configurator(settings=settings) + + .. note:: It's especially important that you mutate the ``settings`` + dictionary with the converted version of the variable *before* passing + it to the Configurator: the configurator makes a *copy* of ``settings``, + it doesn't use the one you pass directly. + +- In the runtime code that you need to access the new settings value, find + the value in the ``registry.settings`` dictionary and use it. In + :term:`view` code (or any other code that has access to the request), the + easiest way to do this is via ``request.registry.settings``. For example: + + .. code-block:: python + + registry = request.registry.settings + debug_frobnosticator = settings['debug_frobnosticator'] + + If you wish to use the value in code that does not have access to the + request and you wish to use the value, you'll need to use the + :func:`pyramid.threadlocal.get_current_registry` API to obtain the current + registry, then ask for its ``settings`` attribute. For example: + + .. code-block:: python + + registry = pyramid.threadlocal.get_current_registry() + settings = registry.settings + debug_frobnosticator = settings['debug_frobnosticator'] + + + + diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index 780cb0975..f8ed743fb 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -33,7 +33,7 @@ URL Dispatch Only ~~~~~~~~~~~~~~~~~ An application that uses :term:`url dispatch` exclusively to map URLs to code -will often have statements like this within your application startup +will often have statements like this within application startup configuration: .. code-block:: python @@ -41,15 +41,20 @@ configuration: # config is an instance of pyramid.config.Configurator - config.add_route('foobar', '{foo}/{bar}', view='myproject.views.foobar') - config.add_route('bazbuz', '{baz}/{buz}', view='myproject.views.bazbuz') + config.add_route('foobar', '{foo}/{bar}') + config.add_route('bazbuz', '{baz}/{buz}') -Each :term:`route` typically corresponds to a single view callable, -and when that route is matched during a request, the view callable -named by the ``view`` attribute is invoked. + config.add_view('myproject.views.foobar', route_name='foobar') + config.add_view('myproject.views.bazbuz', route_name='bazbuz') -Typically, an application that uses only URL dispatch won't perform any calls -to :meth:`pyramid.config.Configurator.add_view` in its startup code. +Each :term:`route` corresponds to one or more view callables. Each view +callable is associated with a route by passing a ``route_name`` parameter +that matches its name during a call to +:meth:`~pyramid.config.Configurator.add_view`. When a route is matched +during a request, :term:`view lookup` is used to match the request to its +associated view callable. The presence of calls to +:meth:`~pyramid.config.Configurator.add_route` signify that an application is +using URL dispatch. Traversal Only ~~~~~~~~~~~~~~ @@ -196,12 +201,9 @@ remainder becomes the path used to perform traversal. The ``*remainder`` route pattern syntax is explained in more detail within :ref:`route_pattern_syntax`. -Note that unlike the examples provided within :ref:`urldispatch_chapter`, the -``add_route`` configuration statement named previously does not pass a -``view`` argument. This is because a hybrid mode application relies on -:term:`traversal` to do :term:`resource location` and :term:`view lookup` -instead of invariably invoking a specific view callable named directly within -the matched route's configuration. +A hybrid mode application relies more heavily on :term:`traversal` to do +:term:`resource location` and :term:`view lookup` than most examples indicate +within :ref:`urldispatch_chapter`. Because the pattern of the above route ends with ``*traverse``, when this route configuration is matched during a request, :app:`Pyramid` will attempt @@ -426,13 +428,11 @@ attribute. Using ``*subpath`` in a Route Pattern ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -There are certain extremely rare cases when you'd like to influence -the traversal :term:`subpath` when a route matches without actually -performing traversal. For instance, the -:func:`pyramid.wsgi.wsgiapp2` decorator and the -:class:`pyramid.view.static` helper attempt to compute -``PATH_INFO`` from the request's subpath, so it's useful to be able to -influence this value. +There are certain extremely rare cases when you'd like to influence the +traversal :term:`subpath` when a route matches without actually performing +traversal. For instance, the :func:`pyramid.wsgi.wsgiapp2` decorator and the +:class:`pyramid.view.static` helper attempt to compute ``PATH_INFO`` from the +request's subpath, so it's useful to be able to influence this value. When ``*subpath`` exists in a pattern, no path is actually traversed, but the traversal algorithm will return a :term:`subpath` list implied @@ -442,8 +442,8 @@ commonly in route declarations that look like this: .. code-block:: python :linenos: - config.add_route('static', '/static/*subpath', - view='mypackage.views.static_view') + config.add_route('static', '/static/*subpath') + config.add_view('mypackage.views.static_view', route_name='static') Where ``mypackage.views.static_view`` is an instance of :class:`pyramid.view.static`. This effectively tells the static helper to @@ -458,11 +458,16 @@ application. We'll detail them here. Registering a Default View for a Route That Has a ``view`` Attribute ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. warning:: As of :app:`Pyramid` 1.1 this section is slated to be removed in + a later documentation release because the the ability to add views + directly to the :term:`route configuration` by passing a ``view`` argument + to ``add_route`` has been deprecated. + It is an error to provide *both* a ``view`` argument to a :term:`route configuration` *and* a :term:`view configuration` which names a ``route_name`` that has no ``name`` value or the empty ``name`` value. For -example, this pair of declarations will generate a "conflict" error at -startup time. +example, this pair of declarations will generate a conflict error at startup +time. .. code-block:: python :linenos: @@ -490,8 +495,8 @@ Can also be spelled like so: config.add_route('home', '{foo}/{bar}/*traverse') config.add_view('myproject.views.home', route_name='home') -The two spellings are logically equivalent. In fact, the former is -just a syntactical shortcut for the latter. +The two spellings are logically equivalent. In fact, the former is just a +syntactical shortcut for the latter. Binding Extra Views Against a Route Configuration that Doesn't Have a ``*traverse`` Element In Its Pattern ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 0b7cdb834..c3533648b 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -92,8 +92,8 @@ will be employed. return HTTPFound(location='http://example.com') # any renderer avoided Views which use a renderer can vary non-body response attributes (such as -headers and the HTTP status code) by attaching properties to the request. -See :ref:`response_request_attrs`. +headers and the HTTP status code) by attaching a property to the +``request.response`` attribute See :ref:`request_response_attr`. Additional renderers can be added by developers to the system as necessary (see :ref:`adding_and_overriding_renderers`). @@ -147,7 +147,8 @@ representing the ``str()`` serialization of the return value: {'content': 'Hello!'} Views which use the string renderer can vary non-body response attributes by -attaching properties to the request. See :ref:`response_request_attrs`. +using the API of the ``request.response`` attribute. See +:ref:`request_response_attr`. .. index:: pair: renderer; JSON @@ -199,7 +200,8 @@ You can configure a view to use the JSON renderer by naming ``json`` as the Views which use the JSON renderer can vary non-body response attributes by -attaching properties to the request. See :ref:`response_request_attrs`. +using the api of the ``request.response`` attribute. See +:ref:`request_response_attr`. .. index:: pair: renderer; chameleon @@ -269,8 +271,9 @@ Here's an example view configuration which uses a Chameleon text renderer: context='myproject.resources.Hello', renderer='myproject:templates/foo.txt') -Views which use a Chameleon renderer can vary response attributes by -attaching properties to the request. See :ref:`response_request_attrs`. +Views which use a Chameleon renderer can vary response attributes by using +the API of the ``request.response`` attribute. See +:ref:`request_response_attr`. .. index:: pair: renderer; mako @@ -333,7 +336,7 @@ additional :ref:`mako_template_renderer_settings`. single: response headers (from a renderer) single: renderer response headers -.. _response_request_attrs: +.. _request_response_attr: Varying Attributes of Rendered Responses ---------------------------------------- @@ -342,9 +345,43 @@ Before a response constructed by a :term:`renderer` is returned to :app:`Pyramid`, several attributes of the request are examined which have the potential to influence response behavior. -View callables that don't directly return a response should set these -attributes on the ``request`` object via ``setattr`` during their execution, -to influence associated response attributes. +View callables that don't directly return a response should use the API of +the :class:`pyramid.response.Response` attribute available as +``request.response`` during their execution, to influence associated response +behavior. + +For example, if you need to change the response status from within a view +callable that uses a renderer, assign the ``status`` attribute to the +``response`` attribute of the request before returning a result: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + + @view_config(name='gone', renderer='templates/gone.pt') + def myview(request): + request.response.status = '404 Not Found' + return {'URL':request.URL} + +For more information on attributes of the request, see the API documentation +in :ref:`request_module`. For more information on the API of +``request.response``, see :class:`pyramid.response.Response`. + +.. _response_prefixed_attrs: + +Deprecated Mechanism to Vary Attributes of Rendered Responses +------------------------------------------------------------- + +.. warning:: This section describes behavior deprecated in Pyramid 1.1. + +In previous releases of Pyramid (1.0 and before), the ``request.response`` +attribute did not exist. Instead, Pyramid required users to set special +``response_`` -prefixed attributes of the request to influence response +behavior. As of Pyramid 1.1, those request attributes are deprecated and +their use will cause a deprecation warning to be issued when used. Until +their existence is removed completely, we document them below, for benefit of +people with older code bases. ``response_content_type`` Defines the content-type of the resulting response, @@ -367,23 +404,6 @@ to influence associated response attributes. returning various values in the ``response_headerlist``, this is purely a convenience. -For example, if you need to change the response status from within a view -callable that uses a renderer, assign the ``response_status`` attribute to -the request before returning a result: - -.. code-block:: python - :linenos: - - from pyramid.view import view_config - - @view_config(name='gone', renderer='templates/gone.pt') - def myview(request): - request.response_status = '404 Not Found' - return {'URL':request.URL} - -For more information on attributes of the request, see the API -documentation in :ref:`request_module`. - .. index:: single: renderer (adding) diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 426ec229b..150b173e3 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -367,13 +367,13 @@ templates as renderers. See :ref:`available_template_system_bindings`. render a view without needing to fork your code to do so. See :ref:`extending_chapter` for more information. -By default, views rendered via a template renderer return a -:term:`Response` object which has a *status code* of ``200 OK``, and a -*content-type* of ``text/html``. To vary attributes of the response -of a view that uses a renderer, such as the content-type, headers, or -status attributes, you must set attributes on the *request* object -within the view before returning the dictionary. See -:ref:`response_request_attrs` for more information. +By default, views rendered via a template renderer return a :term:`Response` +object which has a *status code* of ``200 OK``, and a *content-type* of +``text/html``. To vary attributes of the response of a view that uses a +renderer, such as the content-type, headers, or status attributes, you must +use the API of the :class:`pyramid.response.Response` object exposed as +``request.response`` within the view before returning the dictionary. See +:ref:`request_response_attr` for more information. The same set of system values are provided to templates rendered via a renderer view configuration as those provided to templates rendered diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 219753882..1024dd188 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -54,17 +54,19 @@ Route Configuration ------------------- :term:`Route configuration` is the act of adding a new :term:`route` to an -application. A route has a *pattern*, representing a pattern meant to match +application. A route has a *name*, which acts as an identifier to be used +for URL generation. The name also allows developers to associate a view +configuration with the route. A route also has a *pattern*, meant to match against the ``PATH_INFO`` portion of a URL (the portion following the scheme -and port, e.g. ``/foo/bar`` in the URL ``http://localhost:8080/foo/bar``), -and a *route name*, which is used by developers within a :app:`Pyramid` -application to uniquely identify a particular route when generating a URL. -It also optionally has a ``factory``, a set of :term:`route predicate` -parameters, and a set of :term:`view` parameters. +and port, e.g. ``/foo/bar`` in the URL ``http://localhost:8080/foo/bar``). It +also optionally has a ``factory`` and a set of :term:`route predicate` +attributes. .. index:: single: add_route +.. _config-add-route: + Configuring a Route via The ``add_route`` Configurator Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -79,7 +81,8 @@ example: # pyramid.config.Configurator class; "myview" is assumed # to be a "view callable" function from views import myview - config.add_route('myroute', '/prefix/{one}/{two}', view=myview) + config.add_route('myroute', '/prefix/{one}/{two}') + config.add_view(myview, route_name='myroute') .. versionchanged:: 1.0a4 Prior to 1.0a4, routes allow for a marker starting with a ``:``, for @@ -89,9 +92,47 @@ example: .. index:: single: route configuration; view callable +.. _add_route_view_config: + Route Configuration That Names a View Callable ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. warning:: This section describes a feature which has been deprecated in + Pyramid 1.1 and higher. In order to reduce confusion and documentation + burden, passing view-related parameters to + :meth:`~pyramid.config.Configurator.add_route` is deprecated. + + In versions earlier than 1.1, a view was permitted to be connected to a + route using a set of ``view*`` parameters passed to the + :meth:`~pyramid.config.Configurator.add_route`. This was a shorthand + which replaced the need to perform a subsequent call to + :meth:`~pyramid.config.Configurator.add_view` as described in + :ref:`config-add-route`. For example, it was valid (and often recommended) + to do: + + .. code-block:: python + + config.add_route('home', '/', view='mypackage.views.myview', + view_renderer='some/renderer.pt') + + Instead of the equivalent: + + .. code-block:: python + + config.add_route('home', '/') + config.add_view('mypackage.views.myview', route_name='home') + renderer='some/renderer.pt') + + Passing ``view*`` arguments to ``add_route`` as shown in the first + example above is now deprecated in favor of connecting a view to a + predefined route via :meth:`~pyramid.config.Configurator.add_view` using + the route's ``route_name`` parameter, as shown in the second example + above. + + A deprecation warning is now issued when any view-related parameter is + passed to ``Configurator.add_route``. The recommended way to associate a + view with a route is documented in :ref:`config-add-route`. + When a route configuration declaration names a ``view`` attribute, the value of the attribute will reference a :term:`view callable`. This view callable will be invoked when the route matches. A view callable, as described in @@ -125,6 +166,9 @@ When a route configuration names a ``view`` attribute, the :term:`view callable` named as that ``view`` attribute will always be found and invoked when the associated route pattern matches during a request. +See :meth:`pyramid.config.Configurator.add_route` for a description of +view-related arguments. + .. index:: single: route path pattern syntax @@ -363,8 +407,9 @@ resource of the view callable ultimately found via :term:`view lookup`. .. code-block:: python :linenos: - config.add_route('abc', '/abc', view='myproject.views.theview', + config.add_route('abc', '/abc', factory='myproject.resources.root_factory') + config.add_view('myproject.views.theview', route_name='abc') The factory can either be a Python object or a :term:`dotted Python name` (a string) which points to such a Python object, as it is above. @@ -395,7 +440,8 @@ process. Examples of route predicate arguments are ``pattern``, ``xhr``, and ``request_method``. Other arguments are view configuration related arguments. These only have an -effect when the route configuration names a ``view``. +effect when the route configuration names a ``view``. These arguments have +been deprecated as of :app:`Pyramid` 1.1 (see :ref:`add_route_view_config`). Other arguments are ``name`` and ``factory``. These arguments represent neither predicates nor view configuration information. @@ -547,8 +593,8 @@ If any route matches, the route matching process stops. The :term:`request` is decorated with a special :term:`interface` which describes it as a "route request", the :term:`context` resource is generated, and the context and the resulting request are handed off to :term:`view lookup`. During view lookup, -if any ``view`` argument was provided within the matched route configuration, -the :term:`view callable` it points to is called. +if a :term:`view callable` associated with the matched route is found, that +view is called. When a route configuration is declared, it may contain :term:`route predicate` arguments. All route predicates associated with a route @@ -621,7 +667,8 @@ result in a particular view callable being invoked: .. code-block:: python :linenos: - config.add_route('idea', 'site/{id}', view='mypackage.views.site_view') + config.add_route('idea', 'site/{id}') + config.add_view('mypackage.views.site_view', route_name='idea') When a route configuration with a ``view`` attribute is added to the system, and an incoming request matches the *pattern* of the route configuration, the @@ -665,9 +712,13 @@ add to your application: .. code-block:: python :linenos: - config.add_route('idea', 'ideas/{idea}', view='mypackage.views.idea_view') - config.add_route('user', 'users/{user}', view='mypackage.views.user_view') - config.add_route('tag', 'tags/{tags}', view='mypackage.views.tag_view') + config.add_route('idea', 'ideas/{idea}') + config.add_route('user', 'users/{user}') + config.add_route('tag', 'tags/{tags}') + + config.add_view('mypackage.views.idea_view', route_name='idea') + config.add_view('mypackage.views.user_view', route_name='user') + config.add_view('mypackage.views.tag_view', route_name='tag') The above configuration will allow :app:`Pyramid` to service URLs in these forms: @@ -717,9 +768,8 @@ An example of using a route with a factory: .. code-block:: python :linenos: - config.add_route('idea', 'ideas/{idea}', - view='myproject.views.idea_view', - factory='myproject.resources.Idea') + config.add_route('idea', 'ideas/{idea}', factory='myproject.resources.Idea') + config.add_view('myproject.views.idea_view', route_name='idea') The above route will manufacture an ``Idea`` resource as a :term:`context`, assuming that ``mypackage.resources.Idea`` resolves to a class that accepts a @@ -735,34 +785,6 @@ request in its ``__init__``. For example: In a more complicated application, this root factory might be a class representing a :term:`SQLAlchemy` model. -Example 4 -~~~~~~~~~ - -It is possible to create a route declaration without a ``view`` attribute, -but associate the route with a :term:`view callable` using a ``view`` -declaration. - -.. code-block:: python - :linenos: - - config.add_route('idea', 'site/{id}') - config.add_view(route_name='idea', view='mypackage.views.site_view') - -This set of configuration parameters creates a configuration completely -equivalent to this example provided in :ref:`urldispatch_example1`: - -.. code-block:: python - :linenos: - - config.add_route('idea', 'site/{id}', view='mypackage.views.site_view') - -In fact, the spelling which names a ``view`` attribute is just syntactic -sugar for the more verbose spelling which contains separate view and route -registrations. - -More uses for this style of associating views with routes are explored in -:ref:`hybrid_chapter`. - .. index:: single: matching the root URL single: root url (matching) @@ -777,14 +799,14 @@ It's not entirely obvious how to use a route pattern to match the root URL .. code-block:: python :linenos: - config.add_route('root', '', view='mypackage.views.root_view') + config.add_route('root', '') Or provide the literal string ``/`` as the pattern: .. code-block:: python :linenos: - config.add_route('root', '/', view='mypackage.views.root_view') + config.add_route('root', '/') .. index:: single: generating route URLs @@ -834,10 +856,11 @@ route configuration looks like so: .. code-block:: python :linenos: - config.add_route('noslash', 'no_slash', - view='myproject.views.no_slash') - config.add_route('hasslash', 'has_slash/', - view='myproject.views.has_slash') + config.add_route('noslash', 'no_slash') + config.add_route('hasslash', 'has_slash/') + + config.add_view('myproject.views.no_slash', route_name='noslash') + config.add_view('myproject.views.has_slash', route_name='hasslash') If a request enters the application with the ``PATH_INFO`` value of ``/has_slash/``, the second route will match. If a request enters the @@ -864,8 +887,8 @@ the application's startup configuration, adding the following stanza: .. code-block:: python :linenos: - config.add_view(context='pyramid.exceptions.NotFound', - view='pyramid.view.append_slash_notfound_view') + config.add_view('pyramid.view.append_slash_notfound_view', + context='pyramid.exceptions.NotFound') See :ref:`view_module` and :ref:`changing_the_notfound_view` for more information about the slash-appending not found view and for a more general @@ -1063,30 +1086,25 @@ is executed. Route View Callable Registration and Lookup Details --------------------------------------------------- -The purpose of making it possible to specify a view callable within a route -configuration is to prevent developers from needing to deeply understand the -details of :term:`resource location` and :term:`view lookup`. When a route -names a view callable as a ``view`` argument, and a request enters the system -which matches the pattern of the route, the result is simple: the view -callable associated with the route is invoked with the request that caused -the invocation. +When a request enters the system which matches the pattern of the route, the +usual result is simple: the view callable associated with the route is +invoked with the request that caused the invocation. For most usage, you needn't understand more than this; how it works is an implementation detail. In the interest of completeness, however, we'll explain how it *does* work in the this section. You can skip it if you're uninterested. -When a ``view`` attribute is attached to a route configuration, -:app:`Pyramid` ensures that a :term:`view configuration` is registered that -will always be found when the route pattern is matched during a request. To -do so: +When a view is associated with a route configuration, :app:`Pyramid` ensures +that a :term:`view configuration` is registered that will always be found +when the route pattern is matched during a request. To do so: - A special route-specific :term:`interface` is created at startup time for each route configuration declaration. -- When a route configuration declaration mentions a ``view`` attribute, a +- When an ``add_view`` statement mentions a ``route name`` attribute, a :term:`view configuration` is registered at startup time. This view - configuration uses the route-specific interface as a :term:`request` type. + configuration uses a route-specific interface as a :term:`request` type. - At runtime, when a request causes any route to match, the :term:`request` object is decorated with the route-specific interface. diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 9b2500a2b..743cc016e 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -59,7 +59,8 @@ View configuration is performed in one of these ways: - By specifying a view within a :term:`route configuration`. View configuration via a route configuration is performed by using the :meth:`pyramid.config.Configurator.add_route` method, passing a ``view`` - argument specifying a view callable. + argument specifying a view callable. This pattern of view configuration is + deprecated as of :app:`Pyramid` 1.1. .. note:: A package named ``pyramid_handlers`` (available from PyPI) provides an analogue of :term:`Pylons` -style "controllers", which are a special diff --git a/docs/narr/zca.rst b/docs/narr/zca.rst index fcab0653e..19c52d0c9 100644 --- a/docs/narr/zca.rst +++ b/docs/narr/zca.rst @@ -66,15 +66,15 @@ more than a single application per process. For example, use of a :term:`Paste` "composite" allows you to run separate individual WSGI applications in the same process, each answering requests for some URL prefix. This makes it possible to run, for example, a TurboGears -application at ``/turbogears`` and a BFG application at ``/bfg``, both -served up using the same :term:`WSGI` server within a single Python -process. +application at ``/turbogears`` and a :app:`Pyramid` application at +``/pyramid``, both served up using the same :term:`WSGI` server +within a single Python process. Most production Zope applications are relatively large, making it impractical due to memory constraints to run more than one Zope -application per Python process. However, a :app:`Pyramid` -application may be very small and consume very little memory, so it's -a reasonable goal to be able to run more than one BFG application per +application per Python process. However, a :app:`Pyramid` application +may be very small and consume very little memory, so it's a reasonable +goal to be able to run more than one :app:`Pyramid` application per process. In order to make it possible to run more than one :app:`Pyramid` @@ -182,10 +182,10 @@ global ZCA API. Without special treatment, the ZCA global APIs will always return the global ZCA registry (the one in ``zope.component.globalregistry.base``). -To "fix" this and make the ZCA global APIs use the "current" BFG -registry, you need to call -:meth:`~pyramid.config.Configurator.hook_zca` within your -setup code. For example: +To "fix" this and make the ZCA global APIs use the "current" +:app:`Pyramid` registry, you need to call +:meth:`~pyramid.config.Configurator.hook_zca` within your setup code. +For example: .. code-block:: python :linenos: @@ -253,7 +253,7 @@ Lines 5, 6, and 7 above are the interesting ones. Line 5 retrieves the global ZCA component registry. Line 6 creates a :term:`Configurator`, passing the global ZCA registry into its constructor as the ``registry`` argument. Line 7 "sets up" the global -registry with BFG-specific registrations; this is code that is +registry with Pyramid-specific registrations; this is code that is normally executed when a registry is constructed rather than created, but we must call it "by hand" when we pass an explicit registry. diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index ee86eb543..e4480d6d9 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -7,21 +7,25 @@ edit, and add pages to our wiki. For purposes of demonstration we'll change our application to allow people whom are members of a *group* named ``group:editors`` to add and edit wiki pages but we'll continue allowing anyone with access to the server to view pages. :app:`Pyramid` provides -facilities for *authorization* and *authentication*. We'll make use of both -features to provide security to our application. +facilities for :term:`authorization` and :term:`authentication`. We'll make +use of both features to provide security to our application. -The source code for this tutorial stage can be browsed via -`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/ -<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/>`_. +We will add an :term:`authentication policy` and an +:term:`authorization policy` to our :term:`application +registry`, add a ``security.py`` module and give our :term:`root` +resource an :term:`ACL`. +Then we will add ``login`` and ``logout`` views, and modify the +existing views to make them return a ``logged_in`` flag to the +renderer and add :term:`permission` declarations to their ``view_config`` +decorators. -Configuring a ``pyramid`` Authentication Policy --------------------------------------------------- +Finally, we will add a ``login.pt`` template and change the existing +``view.pt`` and ``edit.pt`` to show a "Logout" link when not logged in. -For any :app:`Pyramid` application to perform authorization, we need to add a -``security.py`` module and we'll need to change our :term:`application -registry` to add an :term:`authentication policy` and a :term:`authorization -policy`. +The source code for this tutorial stage can be browsed via +`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/ +<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/>`_. Adding Authentication and Authorization Policies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -64,6 +68,43 @@ user and groups sources. Note that the ``editor`` user is a member of the ``group:editors`` group in our dummy group data (the ``GROUPS`` data structure). +Giving Our Root Resource an ACL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We need to give our root resource object an :term:`ACL`. This ACL will be +sufficient to provide enough information to the :app:`Pyramid` security +machinery to challenge a user who doesn't have appropriate credentials when +he attempts to invoke the ``add_page`` or ``edit_page`` views. + +We need to perform some imports at module scope in our ``models.py`` file: + +.. code-block:: python + :linenos: + + from pyramid.security import Allow + from pyramid.security import Everyone + +Our root resource object is a ``Wiki`` instance. We'll add the following +line at class scope to our ``Wiki`` class: + +.. code-block:: python + :linenos: + + __acl__ = [ (Allow, Everyone, 'view'), + (Allow, 'group:editors', 'edit') ] + +It's only happenstance that we're assigning this ACL at class scope. An ACL +can be attached to an object *instance* too; this is how "row level security" +can be achieved in :app:`Pyramid` applications. We actually only need *one* +ACL for the entire system, however, because our security requirements are +simple, so this feature is not demonstrated. + +Our resulting ``models.py`` file will now look like so: + +.. literalinclude:: src/authorization/tutorial/models.py + :linenos: + :language: python + Adding Login and Logout Views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -129,6 +170,38 @@ template. For example: logged_in = logged_in, edit_url = edit_url) +Adding ``permission`` Declarations to our ``view_config`` Decorators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To protect each of our views with a particular permission, we need to pass a +``permission`` argument to each of our :class:`pyramid.view.view_config` +decorators. To do so, within ``views.py``: + +- We add ``permission='view'`` to the decorator attached to the + ``view_wiki`` and ``view_page`` view functions. This makes the + assertion that only users who possess the ``view`` permission + against the context resource at the time of the request may + invoke these views. We've granted + :data:`pyramid.security.Everyone` the view permission at the + root model via its ACL, so everyone will be able to invoke the + ``view_wiki`` and ``view_page`` views. + +- We add ``permission='edit'`` to the decorator attached to the + ``add_page`` and ``edit_page`` view functions. This makes the + assertion that only users who possess the effective ``edit`` + permission against the context resource at the time of the + request may invoke these views. We've granted the + ``group:editors`` principal the ``edit`` permission at the + root model via its ACL, so only a user whom is a member of + the group named ``group:editors`` will able to invoke the + ``add_page`` or ``edit_page`` views. We've likewise given + the ``editor`` user membership to this group via the + ``security.py`` file by mapping him to the ``group:editors`` + group in the ``GROUPS`` data structure (``GROUPS + = {'editor':['group:editors']}``); the ``groupfinder`` + function consults the ``GROUPS`` data structure. This means + that the ``editor`` user can add and edit pages. + Adding the ``login.pt`` Template ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -154,92 +227,29 @@ class="app-welcome align-right">`` div: <a href="${request.application_url}/logout">Logout</a> </span> -Giving Our Root Resource an ACL -------------------------------- - -We need to give our root resource object an :term:`ACL`. This ACL will be -sufficient to provide enough information to the :app:`Pyramid` security -machinery to challenge a user who doesn't have appropriate credentials when -he attempts to invoke the ``add_page`` or ``edit_page`` views. +Seeing Our Changes To ``views.py`` and our Templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We need to perform some imports at module scope in our ``models.py`` file: +Our ``views.py`` module will look something like this when we're done: -.. code-block:: python +.. literalinclude:: src/authorization/tutorial/views.py :linenos: + :language: python - from pyramid.security import Allow - from pyramid.security import Everyone - -Our root resource object is a ``Wiki`` instance. We'll add the following -line at class scope to our ``Wiki`` class: +Our ``edit.pt`` template will look something like this when we're done: -.. code-block:: python +.. literalinclude:: src/authorization/tutorial/templates/edit.pt :linenos: + :language: xml - __acl__ = [ (Allow, Everyone, 'view'), - (Allow, 'group:editors', 'edit') ] - -It's only happenstance that we're assigning this ACL at class scope. An ACL -can be attached to an object *instance* too; this is how "row level security" -can be achieved in :app:`Pyramid` applications. We actually only need *one* -ACL for the entire system, however, because our security requirements are -simple, so this feature is not demonstrated. - -Our resulting ``models.py`` file will now look like so: +Our ``view.pt`` template will look something like this when we're done: -.. literalinclude:: src/authorization/tutorial/models.py +.. literalinclude:: src/authorization/tutorial/templates/view.pt :linenos: - :language: python - -Adding ``permission`` Declarations to our ``view_config`` Decorators --------------------------------------------------------------------- - -To protect each of our views with a particular permission, we need to pass a -``permission`` argument to each of our :class:`pyramid.view.view_config` -decorators. To do so, within ``views.py``: - -- We add ``permission='view'`` to the decorator attached to the ``view_wiki`` - view function. This makes the assertion that only users who possess the - ``view`` permission against the context resource at the time of the request - may invoke this view. We've granted :data:`pyramid.security.Everyone` the - view permission at the root model via its ACL, so everyone will be able to - invoke the ``view_wiki`` view. - -- We add ``permission='view'`` to the decorator attached to the ``view_page`` - view function. This makes the assertion that only users who possess the - effective ``view`` permission against the context resource at the time of - the request may invoke this view. We've granted - :data:`pyramid.security.Everyone` the view permission at the root model via - its ACL, so everyone will be able to invoke the ``view_page`` view. - -- We add ``permission='edit'`` to the decorator attached to the ``add_page`` - view function. This makes the assertion that only users who possess the - effective ``edit`` permission against the context resource at the time of - the request may invoke this view. We've granted the ``group:editors`` - principal the ``edit`` permission at the root model via its ACL, so only - the a user whom is a member of the group named ``group:editors`` will able - to invoke the ``add_page`` view. We've likewise given the ``editor`` user - membership to this group via thes ``security.py`` file by mapping him to - the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function consults the - ``GROUPS`` data structure. This means that the ``editor`` user can add - pages. - -- We add ``permission='edit'`` to the decorator attached to the ``edit_page`` - view function. This makes the assertion that only users who possess the - effective ``edit`` permission against the context resource at the time of - the request may invoke this view. We've granted the ``group:editors`` - principal the ``edit`` permission at the root model via its ACL, so only - the a user whom is a member of the group named ``group:editors`` will able - to invoke the ``edit_page`` view. We've likewise given the ``editor`` user - membership to this group via thes ``security.py`` file by mapping him to - the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function consults the - ``GROUPS`` data structure. This means that the ``editor`` user can edit - pages. + :language: xml Viewing the Application in a Browser ------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can finally examine our application in a browser. The views we'll try are as follows: @@ -267,35 +277,7 @@ as follows: credentials with the username ``editor``, password ``editor`` will show the edit page form being displayed. -Seeing Our Changes To ``views.py`` and our Templates ----------------------------------------------------- - -Our ``views.py`` module will look something like this when we're done: - -.. literalinclude:: src/authorization/tutorial/views.py - :linenos: - :language: python - -Our ``edit.pt`` template will look something like this when we're done: - -.. literalinclude:: src/authorization/tutorial/templates/edit.pt - :linenos: - :language: xml - -Our ``view.pt`` template will look something like this when we're done: - -.. literalinclude:: src/authorization/tutorial/templates/view.pt - :linenos: - :language: xml - -Revisiting the Application ---------------------------- - -When we revisit the application in a browser, and log in (as a result -of hitting an edit or add page and submitting the login form with the -``editor`` credentials), we'll see a Logout link in the upper right -hand corner. When we click it, we're logged out, and redirected back -to the front page. - - - +- After logging in (as a result of hitting an edit or add page and + submitting the login form with the ``editor`` credentials), we'll see + a Logout link in the upper right hand corner. When we click it, + we're logged out, and redirected back to the front page. diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index 3d2d01061..baf497458 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -89,70 +89,16 @@ something like this: :linenos: :language: python -Removing View Configuration ---------------------------- - -In a previous step in this chapter, we removed the -``tutorial.models.MyModel`` class. However, our ``views.py`` module still -attempts to import this class. Temporarily, we'll change ``views.py`` so -that it no longer references ``MyModel`` by removing its imports and removing -a reference to it from the arguments passed to the ``@view_config`` -:term:`configuration decoration` decorator which sits atop the ``my_view`` -view callable. - -The result of all of our edits to ``views.py`` will end up looking -something like this: - -.. literalinclude:: src/models/tutorial/views.py - :linenos: - :language: python - -Testing the Models ------------------- - -To make sure the code we just wrote works, we write tests for the model -classes and the appmaker. Changing ``tests.py``, we'll write a separate test -class for each model class, and we'll write a test class for the -``appmaker``. - -To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a -result of the ``pyramid_zodb`` project generator. We'll add three test -classes: one for the ``Page`` model named ``PageModelTests``, one for the -``Wiki`` model named ``WikiModelTests``, and one for the appmaker named -``AppmakerTests``. - -When we're done changing ``tests.py``, it will look something like so: - -.. literalinclude:: src/models/tutorial/tests.py - :linenos: - :language: python +Viewing the Application in a Browser +------------------------------------ -Running the Tests ------------------ - -We can run these tests by using ``setup.py test`` in the same way we -did in :ref:`running_tests`. Assuming our shell's current working -directory is the "tutorial" distribution directory: - -On UNIX: - -.. code-block:: text - - $ ../bin/python setup.py test -q - -On Windows: - -.. code-block:: text - - c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q - -The expected output is something like this: +We can't. At this point, our system is in a "non-runnable" state; we'll need +to change view-related files in the next chapter to be able to start the +application successfully. If you try to start the application, you'll wind +up with a Python traceback on your console that ends with this exception: .. code-block:: text - ..... - ---------------------------------------------------------------------- - Ran 5 tests in 0.008s - - OK + ImportError: cannot import name MyModel +This will also happen if you attempt to run the tests. diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 233e571f1..b6c083bbf 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -318,48 +318,3 @@ browser. The views we'll try are as follows: will generate an ``IndexError`` for the expression ``request.subpath[0]``. You'll see an interactive traceback facility provided by :term:`WebError`. - -Testing the Views -================= - -We'll modify our ``tests.py`` file, adding tests for each view function we -added above. As a result, we'll *delete* the ``ViewTests`` test in the file, -and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, -``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``, -``view_page``, ``add_page``, and ``edit_page`` views respectively. - -Once we're done with the ``tests.py`` module, it will look a lot like the -below: - -.. literalinclude:: src/views/tutorial/tests.py - :linenos: - :language: python - -Running the Tests -================= - -We can run these tests by using ``setup.py test`` in the same way we did in -:ref:`running_tests`. Assuming our shell's current working directory is the -"tutorial" distribution directory: - -On UNIX: - -.. code-block:: text - - $ ../bin/python setup.py test -q - -On Windows: - -.. code-block:: text - - c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q - -The expected result looks something like: - -.. code-block:: text - - ......... - ---------------------------------------------------------------------- - Ran 9 tests in 0.203s - - OK diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst index 660bf3bd3..c984c4f01 100644 --- a/docs/tutorials/wiki/index.rst +++ b/docs/tutorials/wiki/index.rst @@ -23,5 +23,6 @@ tutorial can be browsed at definingmodels definingviews authorization + tests distributing diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py new file mode 100644 index 000000000..d9ff866f1 --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py @@ -0,0 +1,216 @@ +import unittest + +from pyramid import testing + +class PageModelTests(unittest.TestCase): + + def _getTargetClass(self): + from tutorial.models import Page + return Page + + def _makeOne(self, data=u'some data'): + return self._getTargetClass()(data=data) + + def test_constructor(self): + instance = self._makeOne() + self.assertEqual(instance.data, u'some data') + +class WikiModelTests(unittest.TestCase): + + def _getTargetClass(self): + from tutorial.models import Wiki + return Wiki + + def _makeOne(self): + return self._getTargetClass()() + + def test_it(self): + wiki = self._makeOne() + self.assertEqual(wiki.__parent__, None) + self.assertEqual(wiki.__name__, None) + +class AppmakerTests(unittest.TestCase): + def _callFUT(self, zodb_root): + from tutorial.models import appmaker + return appmaker(zodb_root) + + def test_it(self): + root = {} + self._callFUT(root) + self.assertEqual(root['app_root']['FrontPage'].data, + 'This is the front page') + +class ViewWikiTests(unittest.TestCase): + def test_it(self): + from tutorial.views import view_wiki + context = testing.DummyResource() + request = testing.DummyRequest() + response = view_wiki(context, request) + self.assertEqual(response.location, 'http://example.com/FrontPage') + +class ViewPageTests(unittest.TestCase): + def _callFUT(self, context, request): + from tutorial.views import view_page + return view_page(context, request) + + def test_it(self): + wiki = testing.DummyResource() + wiki['IDoExist'] = testing.DummyResource() + context = testing.DummyResource(data='Hello CruelWorld IDoExist') + context.__parent__ = wiki + context.__name__ = 'thepage' + request = testing.DummyRequest() + info = self._callFUT(context, request) + self.assertEqual(info['page'], context) + self.assertEqual( + info['content'], + '<div class="document">\n' + '<p>Hello <a href="http://example.com/add_page/CruelWorld">' + 'CruelWorld</a> ' + '<a href="http://example.com/IDoExist/">' + 'IDoExist</a>' + '</p>\n</div>\n') + self.assertEqual(info['edit_url'], + 'http://example.com/thepage/edit_page') + + +class AddPageTests(unittest.TestCase): + def _callFUT(self, context, request): + from tutorial.views import add_page + return add_page(context, request) + + def test_it_notsubmitted(self): + from pyramid.url import resource_url + context = testing.DummyResource() + request = testing.DummyRequest() + request.subpath = ['AnotherPage'] + info = self._callFUT(context, request) + self.assertEqual(info['page'].data,'') + self.assertEqual( + info['save_url'], + resource_url(context, request, 'add_page', 'AnotherPage')) + + def test_it_submitted(self): + context = testing.DummyResource() + request = testing.DummyRequest({'form.submitted':True, + 'body':'Hello yo!'}) + request.subpath = ['AnotherPage'] + self._callFUT(context, request) + page = context['AnotherPage'] + self.assertEqual(page.data, 'Hello yo!') + self.assertEqual(page.__name__, 'AnotherPage') + self.assertEqual(page.__parent__, context) + +class EditPageTests(unittest.TestCase): + def _callFUT(self, context, request): + from tutorial.views import edit_page + return edit_page(context, request) + + def test_it_notsubmitted(self): + from pyramid.url import resource_url + context = testing.DummyResource() + request = testing.DummyRequest() + info = self._callFUT(context, request) + self.assertEqual(info['page'], context) + self.assertEqual(info['save_url'], + resource_url(context, request, 'edit_page')) + + def test_it_submitted(self): + context = testing.DummyResource() + request = testing.DummyRequest({'form.submitted':True, + 'body':'Hello yo!'}) + response = self._callFUT(context, request) + self.assertEqual(response.location, 'http://example.com/') + self.assertEqual(context.data, 'Hello yo!') + +class FunctionalTests(unittest.TestCase): + + viewer_login = '/login?login=viewer&password=viewer' \ + '&came_from=FrontPage&form.submitted=Login' + viewer_wrong_login = '/login?login=viewer&password=incorrect' \ + '&came_from=FrontPage&form.submitted=Login' + editor_login = '/login?login=editor&password=editor' \ + '&came_from=FrontPage&form.submitted=Login' + + def setUp(self): + import tempfile + import os.path + from tutorial import main + self.tmpdir = tempfile.mkdtemp() + + dbpath = os.path.join( self.tmpdir, 'test.db') + settings = { 'zodb_uri' : 'file://' + dbpath } + + app = main({}, **settings) + from repoze.zodbconn.middleware import EnvironmentDeleterMiddleware + app = EnvironmentDeleterMiddleware(app) + from webtest import TestApp + self.testapp = TestApp(app) + + def tearDown(self): + import shutil + shutil.rmtree( self.tmpdir ) + + def test_root(self): + res = self.testapp.get('/', status=302) + self.assertTrue(not res.body) + + def test_FrontPage(self): + res = self.testapp.get('/FrontPage', status=200) + self.assertTrue('FrontPage' in res.body) + + def test_unexisting_page(self): + res = self.testapp.get('/SomePage', status=404) + self.assertTrue('Not Found' in res.body) + + def test_successful_log_in(self): + res = self.testapp.get( self.viewer_login, status=302) + self.assertTrue(res.location == 'FrontPage') + + def test_failed_log_in(self): + res = self.testapp.get( self.viewer_wrong_login, status=200) + self.assertTrue('login' in res.body) + + def test_logout_link_present_when_logged_in(self): + res = self.testapp.get( self.viewer_login, status=302) + res = self.testapp.get('/FrontPage', status=200) + self.assertTrue('Logout' in res.body) + + def test_logout_link_not_present_after_logged_out(self): + res = self.testapp.get( self.viewer_login, status=302) + res = self.testapp.get('/FrontPage', status=200) + res = self.testapp.get('/logout', status=302) + self.assertTrue('Logout' not in res.body) + + def test_anonymous_user_cannot_edit(self): + res = self.testapp.get('/FrontPage/edit_page', status=200) + self.assertTrue('Login' in res.body) + + def test_anonymous_user_cannot_add(self): + res = self.testapp.get('/add_page/NewPage', status=200) + self.assertTrue('Login' in res.body) + + def test_viewer_user_cannot_edit(self): + res = self.testapp.get( self.viewer_login, status=302) + res = self.testapp.get('/FrontPage/edit_page', status=200) + self.assertTrue('Login' in res.body) + + def test_viewer_user_cannot_add(self): + res = self.testapp.get( self.viewer_login, status=302) + res = self.testapp.get('/add_page/NewPage', status=200) + self.assertTrue('Login' in res.body) + + def test_editors_member_user_can_edit(self): + res = self.testapp.get( self.editor_login, status=302) + res = self.testapp.get('/FrontPage/edit_page', status=200) + self.assertTrue('Editing' in res.body) + + def test_editors_member_user_can_add(self): + res = self.testapp.get( self.editor_login, status=302) + res = self.testapp.get('/add_page/NewPage', status=200) + self.assertTrue('Editing' in res.body) + + def test_editors_member_user_can_view(self): + res = self.testapp.get( self.editor_login, status=302) + res = self.testapp.get('/FrontPage', status=200) + self.assertTrue('FrontPage' in res.body) diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst new file mode 100644 index 000000000..f3151dbcc --- /dev/null +++ b/docs/tutorials/wiki/tests.rst @@ -0,0 +1,78 @@ +============ +Adding Tests +============ + +We will now add tests for the models and the views and a few functional +tests in the ``tests.py``. Tests ensure that an application works, and +that it continues to work after some changes are made in the future. + +Testing the Models +================== + +We write tests for the model +classes and the appmaker. Changing ``tests.py``, we'll write a separate test +class for each model class, and we'll write a test class for the +``appmaker``. + +To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a +result of the ``pyramid_zodb`` project generator. We'll add three test +classes: one for the ``Page`` model named ``PageModelTests``, one for the +``Wiki`` model named ``WikiModelTests``, and one for the appmaker named +``AppmakerTests``. + +Testing the Views +================= + +We'll modify our ``tests.py`` file, adding tests for each view function we +added above. As a result, we'll *delete* the ``ViewTests`` test in the file, +and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, +``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``, +``view_page``, ``add_page``, and ``edit_page`` views respectively. + + +Functional tests +================ + +We test the whole application, covering security aspects that are not +tested in the unit tests, like logging in, logging out, checking that +the ``viewer`` user cannot add or edit pages, but the ``editor`` user +can, and so on. + +Viewing the results of all our edits to ``tests.py`` +==================================================== + +Once we're done with the ``tests.py`` module, it will look a lot like the +below: + +.. literalinclude:: src/tests/tutorial/tests.py + :linenos: + :language: python + +Running the Tests +================= + +We can run these tests by using ``setup.py test`` in the same way we did in +:ref:`running_tests`. Assuming our shell's current working directory is the +"tutorial" distribution directory: + +On UNIX: + +.. code-block:: text + + $ ../bin/python setup.py test -q + +On Windows: + +.. code-block:: text + + c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q + +The expected result looks something like: + +.. code-block:: text + + ......... + ---------------------------------------------------------------------- + Ran 9 tests in 0.203s + + OK diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index 64cab30db..19d438fad 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -76,7 +76,14 @@ For any :app:`Pyramid` application to perform authorization, we need to add a We'll change our ``__init__.py`` file to enable an ``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable -declarative security checking. +declarative security checking. We need to import the new policies: + +.. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 2-3,8 + :linenos: + :language: python + +Then, we'll add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 15-21 @@ -97,25 +104,32 @@ We'll also change ``__init__.py``, adding a call to :term:`view callable`. This is also known as a :term:`forbidden view`: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 24-26 + :lines: 24-26,41-43 :linenos: :language: python A forbidden view configures our newly created login view to show up when :app:`Pyramid` detects that a view invocation can not be authorized. -We'll also add ``view_permission`` arguments with the value ``edit`` to the -``edit_page`` and ``add_page`` routes. This indicates that the view -callables which these routes reference cannot be invoked without the +A ``logout`` :term:`view callable` will allow users to log out later: + +.. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 27-28 + :linenos: + :language: python + +We'll also add ``permission`` arguments with the value ``edit`` to the +``edit_page`` and ``add_page`` views. This indicates that the view +callables which these views reference cannot be invoked without the authenticated user possessing the ``edit`` permission with respect to the current context. .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 32-39 + :lines: 37-40 :linenos: :language: python -Adding these ``view_permission`` arguments causes Pyramid to make the +Adding these ``permission`` arguments causes Pyramid to make the assertion that only users who possess the effective ``edit`` permission at the time of the request may invoke those two views. We've granted the ``group:editors`` principal the ``edit`` permission at the root model via its diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index 0dbcf6684..82e112c64 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -81,28 +81,34 @@ via the :meth:`pyramid.config.Configurator.add_route` method that will be used when the URL is ``/``: .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 13-14 + :lines: 13 :language: py Since this route has a ``pattern`` equalling ``/`` it is the route that will -be called when the URL ``/`` is visted, e.g. ``http://localhost:6543/``. The -argument named ``view`` with the value ``tutorial.views.my_view`` is the +be matched when the URL ``/`` is visted, e.g. ``http://localhost:6543/``. + +Mapping the ``home`` route to code is done by registering a view. You will +use :meth:`pyramid.config.Configurator.add_view` in :term:`URL dispatch` to +register views for the routes, mapping your patterns to code: + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 14 + :language: py + +The first positional ``add_view`` argument ``tutorial.views.my_view`` is the dotted name to a *function* we write (generated by the ``pyramid_routesalchemy`` scaffold) that is given a ``request`` object and -which returns a response or a dictionary. - -You will use :meth:`pyramid.config.Configurator.add_route` statements in a -:term:`URL dispatch` based application to map URLs to code. This route also -names a ``view_renderer``, which is a template which lives in the -``templates`` subdirectory of the package. When the -``tutorial.views.my_view`` view returns a dictionary, a :term:`renderer` will -use this template to create a response. +which returns a response or a dictionary. This view also names a +``renderer``, which is a template which lives in the ``templates`` +subdirectory of the package. When the ``tutorial.views.my_view`` view +returns a dictionary, a :term:`renderer` will use this template to create a +response. This -Fimnally, we use the :meth:`pyramid.config.Configurator.make_wsgi_app` +Finally, we use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application: .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 15 + :lines: 16 :language: py Our final ``__init__.py`` file will look like this: diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index 1661753c1..e5d283125 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -26,6 +26,14 @@ The first thing we want to do is remove the stock ``MyModel`` class from the generated ``models.py`` file. The ``MyModel`` class is only a sample and we're not going to use it. +Next, we'll remove the :class:`sqlalchemy.Unicode` import and replace it +with :class:`sqlalchemy.Text`. + +.. literalinclude:: src/models/tutorial/models.py + :lines: 5 + :linenos: + :language: py + Then, we'll add a ``Page`` class. Because this is a SQLAlchemy application, this class should inherit from an instance of :class:`sqlalchemy.ext.declarative.declarative_base`. Declarative diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index c5a452d11..832f90b92 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -272,8 +272,8 @@ Mapping Views to URLs in ``__init__.py`` ======================================== The ``__init__.py`` file contains -:meth:`pyramid.config.Configurator.add_route` calls which serve to map -URLs via :term:`url dispatch` to view functions. First, we’ll get rid of the +:meth:`pyramid.config.Configurator.add_view` calls which serve to map +routes via :term:`url dispatch` to views. First, we’ll get rid of the existing route created by the template using the name ``home``. It’s only an example and isn’t relevant to our application. @@ -282,21 +282,33 @@ these declarations is very important. ``route`` declarations are matched in the order they're found in the ``__init__.py`` file. #. Add a declaration which maps the pattern ``/`` (signifying the root URL) - to the view named ``view_wiki`` in our ``views.py`` file with the name - ``view_wiki``. This is the :term:`default view` for the wiki. + to the route named ``view_wiki``. -#. Add a declaration which maps the pattern ``/{pagename}`` to the view named - ``view_page`` in our ``views.py`` file with the view name ``view_page``. - This is the regular view for a page. +#. Add a declaration which maps the pattern ``/{pagename}`` to the route named + ``view_page``. This is the regular view for a page. -#. Add a declaration which maps the pattern - ``/add_page/{pagename}`` to the view named ``add_page`` in our - ``views.py`` file with the name ``add_page``. This is the add view - for a new page. +#. Add a declaration which maps the pattern ``/add_page/{pagename}`` to the + route named ``add_page``. This is the add view for a new page. #. Add a declaration which maps the pattern ``/{pagename}/edit_page`` to the - view named ``edit_page`` in our ``views.py`` file with the name - ``edit_page``. This is the edit view for a page. + route named ``edit_page``. This is the edit view for a page. + +After we've defined the routes for our application, we can register views +to handle the processing and rendering that needs to happen when each route is +requested. + +#. Add a declaration which maps the ``view_wiki`` route to the view named + ``view_wiki`` in our ``views.py`` file. This is the :term:`default view` + for the wiki. + +#. Add a declaration which maps the ``view_page`` route to the view named + ``view_page`` in our ``views.py`` file. + +#. Add a declaration which maps the ``add_page`` route to the view named + ``add_page`` in our ``views.py`` file. + +#. Add a declaration which maps the ``edit_page`` route to the view named + ``edit_page`` in our ``views.py`` file. As a result of our edits, the ``__init__.py`` file should look something like so: diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py index 025b94927..e8baa568c 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py @@ -20,25 +20,26 @@ def main(global_config, **settings): authentication_policy=authn_policy, authorization_policy=authz_policy) config.add_static_view('static', 'tutorial:static') - config.add_route('view_wiki', '/', view='tutorial.views.view_wiki') - config.add_route('login', '/login', - view='tutorial.login.login', - view_renderer='tutorial:templates/login.pt') - config.add_route('logout', '/logout', - view='tutorial.login.logout') - config.add_route('view_page', '/{pagename}', - view='tutorial.views.view_page', - view_renderer='tutorial:templates/view.pt') - config.add_route('add_page', '/add_page/{pagename}', - view='tutorial.views.add_page', - view_renderer='tutorial:templates/edit.pt', - view_permission='edit') - config.add_route('edit_page', '/{pagename}/edit_page', - view='tutorial.views.edit_page', - view_renderer='tutorial:templates/edit.pt', - view_permission='edit') + + config.add_route('view_wiki', '/') + config.add_route('login', '/login') + config.add_route('logout', '/logout') + config.add_route('view_page', '/{pagename}') + config.add_route('add_page', '/add_page/{pagename}') + config.add_route('edit_page', '/{pagename}/edit_page') + config.add_route('view_wiki', '/') + + config.add_view('tutorial.login.login', route_name='login', + renderer='tutorial:templates/login.pt') + config.add_view('tutorial.login.logout', route_name='logout') + config.add_view('tutorial.views.view_page', route_name='view_page', + renderer='tutorial:templates/view.pt') + config.add_view('tutorial.views.add_page', route_name='add_page', + renderer='tutorial:templates/edit.pt', permission='edit') + config.add_view('tutorial.views.edit_page', route_name='edit_page', + renderer='tutorial:templates/edit.pt', permission='edit') config.add_view('tutorial.login.login', - renderer='tutorial:templates/login.pt', - context='pyramid.exceptions.Forbidden') + context='pyramid.exceptions.Forbidden', + renderer='tutorial:templates/login.pt') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py index d27b891c0..c74f07652 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py @@ -10,8 +10,9 @@ def main(global_config, **settings): initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', 'tutorial:static') - config.add_route('home', '/', view='tutorial.views.my_view', - view_renderer='templates/mytemplate.pt') + config.add_route('home', '/') + config.add_view('tutorial.views.my_view', route_name='home', + renderer='templates/mytemplate.pt') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py index c912a015b..ecc41ca9f 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py @@ -10,6 +10,7 @@ def main(global_config, **settings): initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', 'tutorial:static') - config.add_route('home', '/', view='tutorial.views.my_view', - view_renderer='templates/mytemplate.pt') + config.add_route('home', '/') + config.add_view('tutorial.views.my_view', route_name='home', + renderer='templates/mytemplate.pt') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py index 1a8d24499..ad89c124e 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py @@ -10,15 +10,16 @@ def main(global_config, **settings): initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', 'tutorial:static') - config.add_route('view_wiki', '/', view='tutorial.views.view_wiki') - config.add_route('view_page', '/{pagename}', - view='tutorial.views.view_page', - view_renderer='tutorial:templates/view.pt') - config.add_route('add_page', '/add_page/{pagename}', - view='tutorial.views.add_page', - view_renderer='tutorial:templates/edit.pt') - config.add_route('edit_page', '/{pagename}/edit_page', - view='tutorial.views.edit_page', - view_renderer='tutorial:templates/edit.pt') + config.add_route('view_wiki', '/') + config.add_route('view_page', '/{pagename}') + config.add_route('add_page', '/add_page/{pagename}') + config.add_route('edit_page', '/{pagename}/edit_page') + config.add_view('tutorial.views.view_wiki', route_name='view_wiki') + config.add_view('tutorial.views.view_page', route_name='view_page', + renderer='tutorial:templates/view.pt') + config.add_view('tutorial.views.add_page', route_name='add_page', + renderer='tutorial:templates/edit.pt') + config.add_view('tutorial.views.edit_page', route_name='edit_page', + renderer='tutorial:templates/edit.pt') return config.make_wsgi_app() diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 78349854b..a6c74e549 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -61,12 +61,12 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): ``callback`` - Default: ``None``. A callback passed the :mod:`repoze.who` - identity and the :term:`request`, expected to return ``None`` - if the user represented by the identity doesn't exist or a - sequence of group identifiers (possibly empty) if the user - does exist. If ``callback`` is None, the userid will be - assumed to exist with no groups. + Default: ``None``. A callback passed the :mod:`repoze.who` identity + and the :term:`request`, expected to return ``None`` if the user + represented by the identity doesn't exist or a sequence of principal + identifiers (possibly empty) representing groups if the user does + exist. If ``callback`` is None, the userid will be assumed to exist + with no group principals. Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. @@ -149,10 +149,10 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): ``callback`` Default: ``None``. A callback passed the userid and the request, - expected to return None if the userid doesn't exist or a sequence - of group identifiers (possibly empty) if the user does exist. - If ``callback`` is None, the userid will be assumed to exist with no - groups. + expected to return None if the userid doesn't exist or a sequence of + principal identifiers (possibly empty) representing groups if the + user does exist. If ``callback`` is None, the userid will be assumed + to exist with no group principals. Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. @@ -187,9 +187,9 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): Default: ``None``. A callback passed the userid and the request, expected to return ``None`` if the userid doesn't - exist or a sequence of group identifiers (possibly empty) if + exist or a sequence of principal identifiers (possibly empty) if the user does exist. If ``callback`` is ``None``, the userid - will be assumed to exist with no groups. Optional. + will be assumed to exist with no principals. Optional. ``cookie_name`` diff --git a/pyramid/compat/__init__.py b/pyramid/compat/__init__.py index 8bbf79703..096fb3ddf 100644 --- a/pyramid/compat/__init__.py +++ b/pyramid/compat/__init__.py @@ -146,3 +146,12 @@ except ImportError: # pragma: no cover import md5 md5 = md5.new +try: + any = any # make importable +except NameError: # pragma: no cover + def any(L): + for thing in L: + if thing: + return True + return False + diff --git a/pyramid/config.py b/pyramid/config.py index 6206d64c4..a24392302 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -4,6 +4,7 @@ import re import sys import types import traceback +import warnings import venusian @@ -50,6 +51,7 @@ from pyramid import renderers from pyramid.authorization import ACLAuthorizationPolicy from pyramid.compat import all from pyramid.compat import md5 +from pyramid.compat import any from pyramid.events import ApplicationCreated from pyramid.exceptions import ConfigurationError from pyramid.exceptions import Forbidden @@ -1378,6 +1380,52 @@ class Configurator(object): discriminator = tuple(discriminator) self.action(discriminator, register) + def _add_view_from_route(self, + route_name, + view, + context, + permission, + renderer, + attr, + ): + if view: + self.add_view( + permission=permission, + context=context, + view=view, + name='', + route_name=route_name, + renderer=renderer, + attr=attr, + ) + else: + # prevent mistakes due to misunderstanding of how hybrid calls to + # add_route and add_view interact + if attr: + raise ConfigurationError( + 'view_attr argument not permitted without view ' + 'argument') + if context: + raise ConfigurationError( + 'view_context argument not permitted without view ' + 'argument') + if permission: + raise ConfigurationError( + 'view_permission argument not permitted without view ' + 'argument') + if renderer: + raise ConfigurationError( + 'view_renderer argument not permitted without ' + 'view argument') + + warnings.warn( + 'Passing view-related arguments to add_route() is deprecated as of ' + 'Pyramid 1.1. Use add_view() to associate a view with a route ' + 'instead. See "Deprecations" in "What\'s New in Pyramid 1.1" ' + 'within the general Pyramid documentation for further details.', + DeprecationWarning, + 4) + @action_method def add_route(self, name, @@ -1484,6 +1532,14 @@ class Configurator(object): by applications, it is meant to be hooked by frameworks that use :app:`Pyramid` as a base. + use_global_views + + When a request matches this route, and view lookup cannot + find a view which has a ``route_name`` predicate argument + that matches the route, try to fall back to using a view + that otherwise matches the context, request, and view name + (but which does not match the route_name predicate). + Predicate Arguments pattern @@ -1592,14 +1648,27 @@ class Configurator(object): View-Related Arguments + .. warning:: The arguments described below have been deprecated as of + :app:`Pyramid` 1.1. *Do not use these for new development; they + should only be used to support older code bases which depend upon + them.* Use a separate call to + :meth:`pyramid.config.Configurator.add_view` to associate a view + with a route. See :ref:`add_route_view_config` for more info. + view + .. warning:: Deprecated as of :app:`Pyramid` 1.1; see + :ref:`add_route_view_config`. + A Python object or :term:`dotted Python name` to the same object that will be used as a view callable when this route matches. e.g. ``mypackage.views.my_view``. view_context + .. warning:: Deprecated as of :app:`Pyramid` 1.1; see + :ref:`add_route_view_config`. + A class or an :term:`interface` or :term:`dotted Python name` to the same object which the :term:`context` of the view should match for the view named by the route to be @@ -1614,6 +1683,9 @@ class Configurator(object): view_permission + .. warning:: Deprecated as of :app:`Pyramid` 1.1; see + :ref:`add_route_view_config`. + The permission name required to invoke the view associated with this route. e.g. ``edit``. (see :ref:`using_security_with_urldispatch` for more information @@ -1626,6 +1698,9 @@ class Configurator(object): view_renderer + .. warning:: Deprecated as of :app:`Pyramid` 1.1; see + :ref:`add_route_view_config`. + This is either a single string term (e.g. ``json``) or a string implying a path or :term:`asset specification` (e.g. ``templates/views.pt``). If the renderer value is a @@ -1648,6 +1723,9 @@ class Configurator(object): view_attr + .. warning:: Deprecated as of :app:`Pyramid` 1.1; see + :ref:`add_route_view_config`. + The view machinery defaults to using the ``__call__`` method of the view callable (or the function itself, if the view callable is a function) to obtain a response dictionary. @@ -1662,14 +1740,6 @@ class Configurator(object): If the ``view`` argument is not provided, this argument has no effect. - use_global_views - - When a request matches this route, and view lookup cannot - find a view which has a ``route_name`` predicate argument - that matches the route, try to fall back to using a view - that otherwise matches the context, request, and view name - (but which does not match the route_name predicate). - """ # these are route predicates; if they do not match, the next route # in the routelist will be tried @@ -1698,42 +1768,17 @@ class Configurator(object): for info in view_info: self.add_view(**info) - if view_context is None: - view_context = view_for - if view_context is None: - view_context = for_ - view_permission = view_permission or permission - view_renderer = view_renderer or renderer - - if view: - self.add_view( - permission=view_permission, - context=view_context, - view=view, - name='', + # deprecated adding views from add_route + if any([view, view_context, view_permission, view_renderer, + view_for, for_, permission, renderer, view_attr]): + self._add_view_from_route( route_name=name, - renderer=view_renderer, + view=view, + permission=view_permission or permission, + context=view_context or view_for or for_, + renderer=view_renderer or renderer, attr=view_attr, - ) - else: - # prevent mistakes due to misunderstanding of how hybrid calls to - # add_route and add_view interact - if view_attr: - raise ConfigurationError( - 'view_attr argument not permitted without view ' - 'argument') - if view_context: - raise ConfigurationError( - 'view_context argument not permitted without view ' - 'argument') - if view_permission: - raise ConfigurationError( - 'view_permission argument not permitted without view ' - 'argument') - if view_renderer: - raise ConfigurationError( - 'view_renderer argument not permitted without ' - 'view argument') + ) mapper = self.get_routes_mapper() diff --git a/pyramid/paster.py b/pyramid/paster.py index 4437db497..a94b4f2d3 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -10,7 +10,7 @@ from paste.util.template import paste_script_template_renderer from pyramid.scripting import get_root class PyramidTemplate(Template): - def pre(self, command, output_dir, vars): # pragma: no cover + def pre(self, command, output_dir, vars): vars['random_string'] = os.urandom(20).encode('hex') package_logger = vars['package'] if package_logger == 'root': @@ -19,9 +19,12 @@ class PyramidTemplate(Template): vars['package_logger'] = package_logger return Template.pre(self, command, output_dir, vars) - def post(self, *arg, **kw): # pragma: no cover - print 'Welcome to Pyramid. Sorry for the convenience.' - return Template.post(self, *arg, **kw) + def post(self, command, output_dir, vars): + self.out('Welcome to Pyramid. Sorry for the convenience.') + return Template.post(self, command, output_dir, vars) + + def out(self, msg): # pragma: no cover (replaceable testing hook) + print msg class StarterProjectTemplate(PyramidTemplate): _template_dir = 'paster_templates/starter' @@ -88,7 +91,7 @@ class PShellCommand(PCommand): command will almost certainly fail. """ - summary = "Open an interactive shell with a pyramid app loaded" + summary = "Open an interactive shell with a Pyramid application loaded" min_args = 2 max_args = 2 @@ -100,10 +103,11 @@ class PShellCommand(PCommand): help="Don't use IPython even if it is available") def command(self, IPShell=_marker): - if IPShell is _marker: - try: #pragma no cover + # IPShell passed to command method is for testing purposes + if IPShell is _marker: # pragma: no cover + try: from IPython.Shell import IPShell - except ImportError: #pragma no cover + except ImportError: IPShell = None cprt =('Type "help" for more information. "root" is the Pyramid app ' 'root object, "registry" is the Pyramid registry object.') @@ -113,16 +117,17 @@ class PShellCommand(PCommand): app = self.get_app(config_file, section_name, loadapp=self.loadapp[0]) root, closer = self.get_root(app) shell_globals = {'root':root, 'registry':app.registry} - if IPShell is not None and not self.options.disable_ipython: + + if (IPShell is None) or self.options.disable_ipython: try: - shell = IPShell(argv=[], user_ns=shell_globals) - shell.IP.BANNER = shell.IP.BANNER + '\n\n' + banner - shell.mainloop() + self.interact[0](banner, local=shell_globals) finally: closer() else: try: - self.interact[0](banner, local=shell_globals) + shell = IPShell(argv=[], user_ns=shell_globals) + shell.IP.BANNER = shell.IP.BANNER + '\n\n' + banner + shell.mainloop() finally: closer() diff --git a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl index 479740297..f5e3a0630 100644 --- a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl @@ -10,8 +10,9 @@ def main(global_config, **settings): initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', '{{package}}:static') - config.add_route('home', '/', view='{{package}}.views.my_view', - view_renderer='templates/mytemplate.pt') + config.add_route('home', '/') + config.add_view('{{package}}.views.my_view', + route_name='home', + renderer='templates/mytemplate.pt') return config.make_wsgi_app() - diff --git a/pyramid/renderers.py b/pyramid/renderers.py index c8771709a..a6dce9b3a 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -1,6 +1,7 @@ import os import pkg_resources import threading +import warnings from zope.interface import implements @@ -134,8 +135,10 @@ def json_renderer_factory(info): def _render(value, system): request = system.get('request') if request is not None: - if not hasattr(request, 'response_content_type'): - request.response_content_type = 'application/json' + response = request.response + ct = response.content_type + if ct == response.default_content_type: + response.content_type = 'application/json' return json.dumps(value) return _render @@ -145,8 +148,10 @@ def string_renderer_factory(info): value = str(value) request = system.get('request') if request is not None: - if not hasattr(request, 'response_content_type'): - request.response_content_type = 'text/plain' + response = request.response + ct = response.content_type + if ct == response.default_content_type: + response.content_type = 'text/plain' return value return _render @@ -344,29 +349,54 @@ class RendererHelper(object): return self._make_response(result, request) def _make_response(self, result, request): - registry = self.registry - response_factory = registry.queryUtility(IResponseFactory, - default=Response) + response = getattr(request, 'response', None) + if response is None: + # request is None or request is not a pyramid.response.Response + registry = self.registry + response_factory = registry.queryUtility(IResponseFactory, + default=Response) + + response = response_factory() - response = response_factory(result) + if result is None: + result = '' + + if isinstance(result, unicode): + response.unicode_body = result + else: + response.body = result if request is not None: + # deprecated mechanism to set up request.response_* attrs attrs = request.__dict__ content_type = attrs.get('response_content_type', None) if content_type is not None: response.content_type = content_type + deprecate_req_attr('Setting', 'content_type', + 'set', 'content_type') headerlist = attrs.get('response_headerlist', None) if headerlist is not None: for k, v in headerlist: response.headers.add(k, v) + deprecate_req_attr('Setting or mutating', 'headerlist', + 'set or mutate', 'headerlist') status = attrs.get('response_status', None) if status is not None: response.status = status + deprecate_req_attr('Setting', 'status', 'set', 'status') charset = attrs.get('response_charset', None) if charset is not None: response.charset = charset + deprecate_req_attr('Setting', 'charset', 'set', 'charset') cache_for = attrs.get('response_cache_for', None) if cache_for is not None: response.cache_expires = cache_for + deprecate_req_attr('Setting', 'cache_for', + 'set', 'cache_expires') return response + +def deprecate_req_attr(*args): + depwarn = ('%s "request.response_%s" is deprecated as of Pyramid 1.1; %s ' + '"request.response.%s" instead.') + warnings.warn(depwarn % args, DeprecationWarning, 3) diff --git a/pyramid/request.py b/pyramid/request.py index b607f159f..0fe8b9379 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -5,9 +5,12 @@ from webob import Request as WebobRequest from pyramid.interfaces import IRequest from pyramid.interfaces import ISessionFactory +from pyramid.interfaces import IResponseFactory from pyramid.exceptions import ConfigurationError from pyramid.decorator import reify +from pyramid.response import Response +from pyramid.traversal import quote_path_segment from pyramid.url import resource_url from pyramid.url import route_url from pyramid.url import static_url @@ -298,6 +301,26 @@ class Request(WebobRequest): """ return route_path(route_name, self, *elements, **kw) + @reify + def response(self): + """This attribute is actually a "reified" property which returns an + instance of the :class:`pyramid.response.Response`. class. The + response object returned does not exist until this attribute is + accessed. Once it is accessed, subsequent accesses will return the + same Response object. + + The ``request.response`` API is used by renderers. A render obtains + the response object it will return from a view that uses that renderer + by accessing ``request.response``. Therefore, it's possible to use the + ``request.response`` API to set up a response object with "the + right" attributes (e.g. by calling ``request.response.set_cookie()``) + within a view that uses a renderer. Mutations to this response object + will be preserved in the response sent to the client.""" + registry = self.registry + response_factory = registry.queryUtility(IResponseFactory, + default=Response) + return response_factory() + # override default WebOb "environ['adhoc_attr']" mutation behavior __getattr__ = object.__getattribute__ __setattr__ = object.__setattr__ @@ -371,3 +394,54 @@ def add_global_response_headers(request, headerlist): response.headerlist.append((k, v)) request.add_response_callback(add_headers) +def call_app_with_subpath_as_path_info(request, app): + # Copy the request. Use the source request's subpath (if it exists) as + # the new request's PATH_INFO. Set the request copy's SCRIPT_NAME to the + # prefix before the subpath. Call the application with the new request + # and return a response. + # + # Postconditions: + # - SCRIPT_NAME and PATH_INFO are empty or start with / + # - At least one of SCRIPT_NAME or PATH_INFO are set. + # - SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should + # be '/'). + + environ = request.environ + script_name = environ.get('SCRIPT_NAME', '') + path_info = environ.get('PATH_INFO', '/') + subpath = list(getattr(request, 'subpath', ())) + + new_script_name = '' + + # compute new_path_info + new_path_info = '/' + '/'.join([x.encode('utf-8') for x in subpath]) + + if new_path_info != '/': # don't want a sole double-slash + if path_info != '/': # if orig path_info is '/', we're already done + if path_info.endswith('/'): + # readd trailing slash stripped by subpath (traversal) + # conversion + new_path_info += '/' + + # compute new_script_name + workback = (script_name + path_info).split('/') + + tmp = [] + while workback: + if tmp == subpath: + break + el = workback.pop() + if el: + tmp.insert(0, el.decode('utf-8')) + + # strip all trailing slashes from workback to avoid appending undue slashes + # to end of script_name + while workback and (workback[-1] == ''): + workback = workback[:-1] + + new_script_name = '/'.join(workback) + + new_request = request.copy() + new_request.environ['SCRIPT_NAME'] = new_script_name + new_request.environ['PATH_INFO'] = new_path_info + return new_request.get_response(app) diff --git a/pyramid/static.py b/pyramid/static.py index 3866126ac..ec7b4cb00 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -13,6 +13,7 @@ from zope.interface import implements from pyramid.asset import resolve_asset_spec from pyramid.interfaces import IStaticURLInfo from pyramid.path import caller_package +from pyramid.request import call_app_with_subpath_as_path_info from pyramid.url import route_url class PackageURLParser(StaticURLParser): @@ -37,21 +38,18 @@ class PackageURLParser(StaticURLParser): filename = request.path_info_pop(environ) resource = os.path.normcase(os.path.normpath( self.resource_name + '/' + filename)) - if ( (self.root_resource is not None) and - (not resource.startswith(self.root_resource)) ): + if not resource.startswith(self.root_resource): # Out of bounds return self.not_found(environ, start_response) if not pkg_resources.resource_exists(self.package_name, resource): return self.not_found(environ, start_response) if pkg_resources.resource_isdir(self.package_name, resource): # @@: Cache? - child_root = (self.root_resource is not None and - self.root_resource or self.resource_name) return self.__class__( - self.package_name, resource, root_resource=child_root, + self.package_name, resource, root_resource=self.resource_name, cache_max_age=self.cache_max_age)(environ, start_response) - if (environ.get('PATH_INFO') - and environ.get('PATH_INFO') != '/'): # pragma: no cover + pi = environ.get('PATH_INFO') + if pi and pi != '/': return self.error_extra_path(environ, start_response) full = pkg_resources.resource_filename(self.package_name, resource) if_none_match = environ.get('HTTP_IF_NONE_MATCH') @@ -151,13 +149,26 @@ class StaticURLInfo(object): permission = extra.pop('permission', None) if permission is None: permission = '__no_permission_required__' - extra['view_permission'] = permission - extra['view'] = view - # register a route using the computed view, permission, and pattern, - # plus any extras passed to us via add_static_view + context = extra.pop('view_context', None) + if context is None: + context = extra.pop('view_for', None) + if context is None: + context = extra.pop('for_', None) + + renderer = extra.pop('view_renderer', None) + if renderer is None: + renderer = extra.pop('renderer', None) + + attr = extra.pop('view_attr', None) + + # register a route using the computed view, permission, and + # pattern, plus any extras passed to us via add_static_view pattern = "%s*subpath" % name # name already ends with slash self.config.add_route(name, pattern, **extra) + self.config.add_view(route_name=name, view=view, + permission=permission, context=context, + renderer=renderer, attr=attr) self.registrations.append((name, spec, False)) class static_view(object): @@ -211,12 +222,4 @@ class static_view(object): self.app = app def __call__(self, context, request): - subpath = '/'.join(request.subpath) - request_copy = request.copy() - # Fix up PATH_INFO to get rid of everything but the "subpath" - # (the actual path to the file relative to the root dir). - request_copy.environ['PATH_INFO'] = '/' + subpath - # Zero out SCRIPT_NAME for good measure. - request_copy.environ['SCRIPT_NAME'] = '' - return request_copy.get_response(self.app) - + return call_app_with_subpath_as_path_info(request, self.app) diff --git a/pyramid/testing.py b/pyramid/testing.py index a98807a19..36cc38830 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -9,12 +9,14 @@ from zope.interface import Interface from zope.interface import alsoProvides from pyramid.interfaces import IRequest +from pyramid.interfaces import IResponseFactory from pyramid.interfaces import ISecuredView from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import ISession from pyramid.config import Configurator +from pyramid.decorator import reify from pyramid.exceptions import Forbidden from pyramid.response import Response from pyramid.registry import Registry @@ -653,6 +655,8 @@ class DummyRequest(object): response_callbacks = () charset = 'UTF-8' script_name = '' + _registry = None + def __init__(self, params=None, environ=None, headers=None, path='/', cookies=None, post=None, **kw): if environ is None: @@ -698,9 +702,23 @@ class DummyRequest(object): self.response_callbacks = [] self.response_callbacks.append(callback) - @property - def registry(self): - return get_current_registry() + def _get_registry(self): + if self._registry is None: + return get_current_registry() + return self._registry + + def _set_registry(self, registry): + self._registry = registry + + def _del_registry(self): + self._registry = None + + registry = property(_get_registry, _set_registry, _del_registry) + + @reify + def response(self): + f = self.registry.queryUtility(IResponseFactory, default=Response) + return f() def setUp(registry=None, request=None, hook_zca=True, autocommit=True, settings=None): diff --git a/pyramid/tests/ccbugapp/__init__.py b/pyramid/tests/ccbugapp/__init__.py index 6c2eb6ecf..ad6387a75 100644 --- a/pyramid/tests/ccbugapp/__init__.py +++ b/pyramid/tests/ccbugapp/__init__.py @@ -1,7 +1,8 @@ def includeme(config): - config.add_route('rdf', - 'licenses/:license_code/:license_version/rdf', - '.views.rdf_view') + config.add_route('rdf', 'licenses/:license_code/:license_version/rdf') config.add_route('juri', - 'licenses/:license_code/:license_version/:jurisdiction', - '.views.juri_view') + 'licenses/:license_code/:license_version/:jurisdiction') + config.add_view('.views.rdf_view', route_name='rdf') + config.add_view('.views.juri_view', route_name='juri') + + diff --git a/pyramid/tests/exceptionviewapp/__init__.py b/pyramid/tests/exceptionviewapp/__init__.py index cf69227cd..f169e0cd5 100644 --- a/pyramid/tests/exceptionviewapp/__init__.py +++ b/pyramid/tests/exceptionviewapp/__init__.py @@ -1,21 +1,23 @@ def includeme(config): + config.add_route('route_raise_exception', 'route_raise_exception') + config.add_route('route_raise_exception2', 'route_raise_exception2', + factory='.models.route_factory') + config.add_route('route_raise_exception3', 'route_raise_exception3', + factory='.models.route_factory2') + config.add_route('route_raise_exception4', 'route_raise_exception4') config.add_view('.views.maybe') config.add_view('.views.no', context='.models.NotAnException') config.add_view('.views.yes', context=".models.AnException") config.add_view('.views.raise_exception', name='raise_exception') - config.add_route('route_raise_exception', 'route_raise_exception', - view='.views.raise_exception') - config.add_route('route_raise_exception2', - 'route_raise_exception2', - view='.views.raise_exception', - factory='.models.route_factory') - config.add_route('route_raise_exception3', - 'route_raise_exception3', - view='.views.raise_exception', - factory='.models.route_factory2') + config.add_view('.views.raise_exception', + route_name='route_raise_exception') + config.add_view('.views.raise_exception', + route_name='route_raise_exception2') + config.add_view('.views.raise_exception', + route_name='route_raise_exception3') config.add_view('.views.whoa', context='.models.AnException', route_name='route_raise_exception3') - config.add_route('route_raise_exception4', 'route_raise_exception4', - view='.views.raise_exception') + config.add_view('.views.raise_exception', + route_name='route_raise_exception4') config.add_view('.views.whoa', context='.models.AnException', route_name='route_raise_exception4') diff --git a/pyramid/tests/hybridapp/__init__.py b/pyramid/tests/hybridapp/__init__.py index 5b51e3d1e..1cc2dde83 100644 --- a/pyramid/tests/hybridapp/__init__.py +++ b/pyramid/tests/hybridapp/__init__.py @@ -1,6 +1,7 @@ def includeme(config): # <!-- we want this view to "win" --> - config.add_route('route', 'abc', view='.views.route_view') + config.add_route('route', 'abc') + config.add_view('.views.route_view', route_name='route') # <!-- .. even though this one has a more specific context --> config.add_view('.views.global_view', context='pyramid.traversal.DefaultRootFactory') diff --git a/pyramid/tests/restbugapp/__init__.py b/pyramid/tests/restbugapp/__init__.py index 461fcce92..9ad79e32e 100644 --- a/pyramid/tests/restbugapp/__init__.py +++ b/pyramid/tests/restbugapp/__init__.py @@ -1,14 +1,15 @@ def includeme(config): config.add_route('gameactions_pet_get_pets', '/pet', - view='.views.PetRESTView', - view_attr='GET', - request_method='GET', - permission='view', - renderer='json') + request_method='GET') config.add_route('gameactions_pet_care_for_pet', '/pet', - view='.views.PetRESTView', - view_attr='POST', - request_method='POST', - permission='view', - renderer='json') - + request_method='POST') + config.add_view('.views.PetRESTView', + route_name='gameactions_pet_get_pets', + attr='GET', + permission='view', + renderer='json') + config.add_view('.views.PetRESTView', + route_name='gameactions_pet_care_for_pet', + attr='POST', + permission='view', + renderer='json') diff --git a/pyramid/tests/test_asset.py b/pyramid/tests/test_asset.py index 4a93b14a0..1bd855cdb 100644 --- a/pyramid/tests/test_asset.py +++ b/pyramid/tests/test_asset.py @@ -74,7 +74,7 @@ class TestOverrideProvider(unittest.TestCase): import pyramid.tests provider = self._makeOne(pyramid.tests) result = provider.resource_listdir(resource_name) - self.failUnless(result) + self.assertTrue(result) def test_get_resource_filename_override_returns_None(self): overrides = DummyOverrides(None) @@ -137,7 +137,7 @@ class TestOverrideProvider(unittest.TestCase): import pyramid.tests provider = self._makeOne(pyramid.tests) result = provider.resource_listdir(resource_name) - self.failUnless(result) + self.assertTrue(result) def test_get_resource_filename_override_returns_value(self): overrides = DummyOverrides('value') @@ -371,7 +371,7 @@ class TestPackageOverrides(unittest.TestCase): package = DummyPackage('package') po = self._makeOne(package) po.overrides= overrides - self.failUnless(po.listdir('whatever')) + self.assertTrue(po.listdir('whatever')) def test_listdir_doesnt_exist(self): overrides = [ DummyOverride(None), DummyOverride( diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 1d2b939b7..ecd76a71c 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -470,7 +470,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): plugin.now = now + 1 request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'}) result = plugin.identify(request) - self.failUnless(result) + self.assertTrue(result) self.assertEqual(len(request.callbacks), 1) response = DummyResponse() request.callbacks[0](None, response) @@ -486,7 +486,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'}) request._authtkt_reissued = True result = plugin.identify(request) - self.failUnless(result) + self.assertTrue(result) self.assertEqual(len(request.callbacks), 0) def test_identify_cookie_reissue_notyet(self): @@ -497,7 +497,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): plugin.now = now + 1 request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'}) result = plugin.identify(request) - self.failUnless(result) + self.assertTrue(result) self.assertEqual(len(request.callbacks), 0) def test_identify_cookie_reissue_with_tokens_default(self): @@ -510,13 +510,13 @@ class TestAuthTktCookieHelper(unittest.TestCase): plugin.now = now + 1 request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'}) result = plugin.identify(request) - self.failUnless(result) + self.assertTrue(result) self.assertEqual(len(request.callbacks), 1) response = DummyResponse() request.callbacks[0](None, response) self.assertEqual(len(response.headerlist), 3) self.assertEqual(response.headerlist[0][0], 'Set-Cookie') - self.failUnless("'tokens': []" in response.headerlist[0][1]) + self.assertTrue("'tokens': []" in response.headerlist[0][1]) def test_remember(self): plugin = self._makeOne('secret') @@ -525,16 +525,16 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') - self.failUnless(result[0][1].endswith('; Path=/')) - self.failUnless(result[0][1].startswith('auth_tkt=')) + self.assertTrue(result[0][1].endswith('; Path=/')) + self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') - self.failUnless(result[1][1].endswith('; Path=/; Domain=localhost')) - self.failUnless(result[1][1].startswith('auth_tkt=')) + self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost')) + self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') - self.failUnless(result[2][1].endswith('; Path=/; Domain=.localhost')) - self.failUnless(result[2][1].startswith('auth_tkt=')) + self.assertTrue(result[2][1].endswith('; Path=/; Domain=.localhost')) + self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_include_ip(self): plugin = self._makeOne('secret', include_ip=True) @@ -543,16 +543,16 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') - self.failUnless(result[0][1].endswith('; Path=/')) - self.failUnless(result[0][1].startswith('auth_tkt=')) + self.assertTrue(result[0][1].endswith('; Path=/')) + self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') - self.failUnless(result[1][1].endswith('; Path=/; Domain=localhost')) - self.failUnless(result[1][1].startswith('auth_tkt=')) + self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost')) + self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') - self.failUnless(result[2][1].endswith('; Path=/; Domain=.localhost')) - self.failUnless(result[2][1].startswith('auth_tkt=')) + self.assertTrue(result[2][1].endswith('; Path=/; Domain=.localhost')) + self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_path(self): plugin = self._makeOne('secret', include_ip=True, @@ -562,18 +562,18 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') - self.failUnless(result[0][1].endswith('; Path=/cgi-bin/app.cgi/')) - self.failUnless(result[0][1].startswith('auth_tkt=')) + self.assertTrue(result[0][1].endswith('; Path=/cgi-bin/app.cgi/')) + self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') - self.failUnless(result[1][1].endswith( + self.assertTrue(result[1][1].endswith( '; Path=/cgi-bin/app.cgi/; Domain=localhost')) - self.failUnless(result[1][1].startswith('auth_tkt=')) + self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') - self.failUnless(result[2][1].endswith( + self.assertTrue(result[2][1].endswith( '; Path=/cgi-bin/app.cgi/; Domain=.localhost')) - self.failUnless(result[2][1].startswith('auth_tkt=')) + self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_http_only(self): plugin = self._makeOne('secret', include_ip=True, http_only=True) @@ -582,16 +582,16 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') - self.failUnless(result[0][1].endswith('; HttpOnly')) - self.failUnless(result[0][1].startswith('auth_tkt=')) + self.assertTrue(result[0][1].endswith('; HttpOnly')) + self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') - self.failUnless(result[1][1].endswith('; HttpOnly')) - self.failUnless(result[1][1].startswith('auth_tkt=')) + self.assertTrue(result[1][1].endswith('; HttpOnly')) + self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') - self.failUnless(result[2][1].endswith('; HttpOnly')) - self.failUnless(result[2][1].startswith('auth_tkt=')) + self.assertTrue(result[2][1].endswith('; HttpOnly')) + self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_secure(self): plugin = self._makeOne('secret', include_ip=True, secure=True) @@ -600,16 +600,16 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') - self.failUnless('; Secure' in result[0][1]) - self.failUnless(result[0][1].startswith('auth_tkt=')) + self.assertTrue('; Secure' in result[0][1]) + self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') - self.failUnless('; Secure' in result[1][1]) - self.failUnless(result[1][1].startswith('auth_tkt=')) + self.assertTrue('; Secure' in result[1][1]) + self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') - self.failUnless('; Secure' in result[2][1]) - self.failUnless(result[2][1].startswith('auth_tkt=')) + self.assertTrue('; Secure' in result[2][1]) + self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_wild_domain_disabled(self): plugin = self._makeOne('secret', wild_domain=False) @@ -619,11 +619,11 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue(result[0][1].endswith('; Path=/')) - self.failUnless(result[0][1].startswith('auth_tkt=')) + self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost')) - self.failUnless(result[1][1].startswith('auth_tkt=')) + self.assertTrue(result[1][1].startswith('auth_tkt=')) def test_remember_domain_has_port(self): plugin = self._makeOne('secret', wild_domain=False) @@ -634,11 +634,11 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue(result[0][1].endswith('; Path=/')) - self.failUnless(result[0][1].startswith('auth_tkt=')) + self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue(result[1][1].endswith('; Path=/; Domain=example.com')) - self.failUnless(result[1][1].startswith('auth_tkt=')) + self.assertTrue(result[1][1].startswith('auth_tkt=')) def test_remember_string_userid(self): plugin = self._makeOne('secret') @@ -690,7 +690,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): values = self._parseHeaders(result) self.assertEqual(len(result), 3) value = values[0] - self.failUnless('userid' in value.value) + self.assertTrue('userid' in value.value) def test_remember_max_age(self): plugin = self._makeOne('secret') @@ -700,7 +700,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(values[0]['max-age'], '500') - self.failUnless(values[0]['expires']) + self.assertTrue(values[0]['expires']) def test_remember_tokens(self): plugin = self._makeOne('secret') @@ -709,13 +709,13 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') - self.failUnless("'tokens': ('foo', 'bar')" in result[0][1]) + self.assertTrue("'tokens': ('foo', 'bar')" in result[0][1]) self.assertEqual(result[1][0], 'Set-Cookie') - self.failUnless("'tokens': ('foo', 'bar')" in result[1][1]) + self.assertTrue("'tokens': ('foo', 'bar')" in result[1][1]) self.assertEqual(result[2][0], 'Set-Cookie') - self.failUnless("'tokens': ('foo', 'bar')" in result[2][1]) + self.assertTrue("'tokens': ('foo', 'bar')" in result[2][1]) def test_remember_non_string_token(self): plugin = self._makeOne('secret') diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py index dcc4f8344..213f25f51 100644 --- a/pyramid/tests/test_chameleon_text.py +++ b/pyramid/tests/test_chameleon_text.py @@ -49,7 +49,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template, instance.__dict__['template']) @@ -58,7 +58,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template.translate, lookup.translate) @@ -68,7 +68,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() lookup.debug = True instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template.debug, True) @@ -78,7 +78,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() lookup.auto_reload = True instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template.auto_reload, True) @@ -88,7 +88,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() lookup.auto_reload = False instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template.auto_reload, False) @@ -98,7 +98,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() instance = self._makeOne(minimal, lookup) result = instance({}, {}) - self.failUnless(isinstance(result, str)) + self.assertTrue(isinstance(result, str)) self.assertEqual(result, 'Hello.\n') @skip_on('java') @@ -114,7 +114,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() instance = self._makeOne(nonminimal, lookup) result = instance({'name':'Chris'}, {}) - self.failUnless(isinstance(result, str)) + self.assertTrue(isinstance(result, str)) self.assertEqual(result, 'Hello, Chris!\n') @skip_on('java') @@ -123,7 +123,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() instance = self._makeOne(minimal, lookup) result = instance.implementation()() - self.failUnless(isinstance(result, str)) + self.assertTrue(isinstance(result, str)) self.assertEqual(result, 'Hello.\n') class RenderTemplateTests(Base, unittest.TestCase): @@ -135,7 +135,7 @@ class RenderTemplateTests(Base, unittest.TestCase): def test_it(self): minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) - self.failUnless(isinstance(result, str)) + self.assertTrue(isinstance(result, str)) self.assertEqual(result, 'Hello.\n') class RenderTemplateToResponseTests(Base, unittest.TestCase): @@ -148,7 +148,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) from webob import Response - self.failUnless(isinstance(result, Response)) + self.assertTrue(isinstance(result, Response)) self.assertEqual(result.app_iter, ['Hello.\n']) self.assertEqual(result.status, '200 OK') self.assertEqual(len(result.headerlist), 2) @@ -162,7 +162,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): self._registerUtility(Response2, IResponseFactory) minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) - self.failUnless(isinstance(result, Response2)) + self.assertTrue(isinstance(result, Response2)) class GetRendererTests(Base, unittest.TestCase): def _callFUT(self, name): @@ -180,7 +180,7 @@ class GetRendererTests(Base, unittest.TestCase): return renderer self._registerUtility(rf, IRendererFactory, name='foo') result = self._callFUT('foo') - self.failUnless(result is renderer) + self.assertTrue(result is renderer) class GetTemplateTests(Base, unittest.TestCase): def _callFUT(self, name): @@ -199,7 +199,7 @@ class GetTemplateTests(Base, unittest.TestCase): return renderer self._registerUtility(rf, IRendererFactory, name='foo') result = self._callFUT('foo') - self.failUnless(result is renderer.template) + self.assertTrue(result is renderer.template) class DummyLookup(object): auto_reload=True diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py index 9bdb47776..84eaedcf4 100644 --- a/pyramid/tests/test_chameleon_zpt.py +++ b/pyramid/tests/test_chameleon_zpt.py @@ -51,7 +51,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() instance = self._makeOne(minimal, lookup) result = instance({}, {}) - self.failUnless(isinstance(result, unicode)) + self.assertTrue(isinstance(result, unicode)) self.assertEqual(result.rstrip('\n'), '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') @@ -60,7 +60,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template, instance.__dict__['template']) @@ -69,7 +69,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template.translate, lookup.translate) @@ -79,7 +79,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() lookup.debug = True instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template.debug, True) @@ -89,7 +89,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() lookup.debug = False instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template.debug, False) @@ -99,7 +99,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() lookup.auto_reload = True instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template.auto_reload, True) @@ -109,7 +109,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() lookup.auto_reload = False instance = self._makeOne(minimal, lookup) - self.failIf('template' in instance.__dict__) + self.assertFalse('template' in instance.__dict__) template = instance.template self.assertEqual(template.auto_reload, False) @@ -126,7 +126,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() instance = self._makeOne(minimal, lookup) result = instance.implementation()() - self.failUnless(isinstance(result, unicode)) + self.assertTrue(isinstance(result, unicode)) self.assertEqual(result.rstrip('\n'), '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') @@ -140,7 +140,7 @@ class RenderTemplateTests(Base, unittest.TestCase): def test_it(self): minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) - self.failUnless(isinstance(result, unicode)) + self.assertTrue(isinstance(result, unicode)) self.assertEqual(result.rstrip('\n'), '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') @@ -154,7 +154,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) from webob import Response - self.failUnless(isinstance(result, Response)) + self.assertTrue(isinstance(result, Response)) self.assertEqual(result.app_iter[0].rstrip('\n'), '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') self.assertEqual(result.status, '200 OK') @@ -169,7 +169,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): self._registerUtility(Response2, IResponseFactory) minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) - self.failUnless(isinstance(result, Response2)) + self.assertTrue(isinstance(result, Response2)) class GetRendererTests(Base, unittest.TestCase): def _callFUT(self, name): @@ -187,7 +187,7 @@ class GetRendererTests(Base, unittest.TestCase): return renderer self._registerUtility(rf, IRendererFactory, name='foo') result = self._callFUT('foo') - self.failUnless(result is renderer) + self.assertTrue(result is renderer) class GetTemplateTests(Base, unittest.TestCase): def _callFUT(self, name): @@ -206,7 +206,7 @@ class GetTemplateTests(Base, unittest.TestCase): return renderer self._registerUtility(rf, IRendererFactory, name='foo') result = self._callFUT('foo') - self.failUnless(result is renderer.template) + self.assertTrue(result is renderer.template) class DummyLookup(object): auto_reload=True diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index d5903d4fb..f52d194b6 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -68,6 +68,12 @@ class ConfiguratorTests(unittest.TestCase): request.registry = config.registry return request + def _conflictFunctions(self, e): + conflicts = e._conflicts.values() + for conflict in conflicts: + for confinst in conflict: + yield confinst[2] + def test_ctor_no_registry(self): import sys from pyramid.interfaces import ISettings @@ -75,20 +81,20 @@ class ConfiguratorTests(unittest.TestCase): from pyramid.interfaces import IRendererFactory config = Configurator() this_pkg = sys.modules['pyramid.tests'] - self.failUnless(config.registry.getUtility(ISettings)) + self.assertTrue(config.registry.getUtility(ISettings)) self.assertEqual(config.package, this_pkg) - self.failUnless(config.registry.getUtility(IRendererFactory, 'json')) - self.failUnless(config.registry.getUtility(IRendererFactory, 'string')) + self.assertTrue(config.registry.getUtility(IRendererFactory, 'json')) + self.assertTrue(config.registry.getUtility(IRendererFactory, 'string')) if not __pypy__: - self.failUnless(config.registry.getUtility(IRendererFactory, '.pt')) - self.failUnless(config.registry.getUtility(IRendererFactory,'.txt')) - self.failUnless(config.registry.getUtility(IRendererFactory, '.mak')) - self.failUnless(config.registry.getUtility(IRendererFactory, '.mako')) + self.assertTrue(config.registry.getUtility(IRendererFactory, '.pt')) + self.assertTrue(config.registry.getUtility(IRendererFactory,'.txt')) + self.assertTrue(config.registry.getUtility(IRendererFactory, '.mak')) + self.assertTrue(config.registry.getUtility(IRendererFactory, '.mako')) def test__set_settings_as_None(self): config = self._makeOne() settings = config._set_settings(None) - self.failUnless(settings) + self.assertTrue(settings) def test__set_settings_as_dictwithvalues(self): config = self._makeOne() @@ -171,7 +177,7 @@ class ConfiguratorTests(unittest.TestCase): def test_ctor_no_root_factory(self): from pyramid.interfaces import IRootFactory config = self._makeOne() - self.failUnless(config.registry.getUtility(IRootFactory)) + self.assertTrue(config.registry.getUtility(IRootFactory)) def test_ctor_alternate_renderers(self): from pyramid.interfaces import IRendererFactory @@ -390,7 +396,7 @@ class ConfiguratorTests(unittest.TestCase): reg = Registry() config = self._makeOne(reg) config.setup_registry() - self.failUnless(reg.getUtility(IRootFactory)) + self.assertTrue(reg.getUtility(IRootFactory)) def test_setup_registry_dottedname_root_factory(self): from pyramid.registry import Registry @@ -591,9 +597,9 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(app.__class__, Router) self.assertEqual(manager.pushed['registry'], config.registry) self.assertEqual(manager.pushed['request'], None) - self.failUnless(manager.popped) + self.assertTrue(manager.popped) self.assertEqual(len(subscriber), 1) - self.failUnless(IApplicationCreated.providedBy(subscriber[0])) + self.assertTrue(IApplicationCreated.providedBy(subscriber[0])) def test_include_with_dotted_name(self): from pyramid import tests @@ -610,7 +616,7 @@ class ConfiguratorTests(unittest.TestCase): ) self.assertEqual(context_after.basepath, None) self.assertEqual(context_after.includepath, ()) - self.failUnless(context_after is context_before) + self.assertTrue(context_after is context_before) def test_include_with_python_callable(self): from pyramid import tests @@ -627,7 +633,7 @@ class ConfiguratorTests(unittest.TestCase): ) self.assertEqual(context_after.basepath, None) self.assertEqual(context_after.includepath, ()) - self.failUnless(context_after is context_before) + self.assertTrue(context_after is context_before) def test_include_with_module_defaults_to_includeme(self): from pyramid import tests @@ -644,7 +650,7 @@ class ConfiguratorTests(unittest.TestCase): ) self.assertEqual(context_after.basepath, None) self.assertEqual(context_after.includepath, ()) - self.failUnless(context_after is context_before) + self.assertTrue(context_after is context_before) def test_with_context(self): config = self._makeOne() @@ -683,7 +689,7 @@ class ConfiguratorTests(unittest.TestCase): self._registerRenderer(config, name='dummy') config.add_view(renderer='dummy') view = self._getViewCallable(config) - self.failUnless('Hello!' in view(None, None).body) + self.assertTrue('Hello!' in view(None, None).body) def test_add_view_wrapped_view_is_decorated(self): def view(request): # request-only wrapper @@ -729,7 +735,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(autocommit=True) config.add_view(view=view, decorator=view_wrapper) wrapper = self._getViewCallable(config) - self.failIf(wrapper is view) + self.assertFalse(wrapper is view) self.assertEqual(wrapper.__doc__, view.__doc__) result = wrapper(None, None) self.assertEqual(result, 'OK') @@ -900,7 +906,7 @@ class ConfiguratorTests(unittest.TestCase): return 'OK' config.add_view(view=newview, xhr=True) wrapper = self._getViewCallable(config) - self.failIf(IMultiView.providedBy(wrapper)) + self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') @@ -926,7 +932,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_view(view=newview, xhr=True, context=RuntimeError) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) - self.failIf(IMultiView.providedBy(wrapper)) + self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') @@ -945,7 +951,7 @@ class ConfiguratorTests(unittest.TestCase): return 'OK' config.add_view(view=newview) wrapper = self._getViewCallable(config) - self.failIf(IMultiView.providedBy(wrapper)) + self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') @@ -967,7 +973,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_view(view=newview, context=RuntimeError) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) - self.failIf(IMultiView.providedBy(wrapper)) + self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') @@ -988,7 +994,7 @@ class ConfiguratorTests(unittest.TestCase): return 'OK' config.add_view(view=newview) wrapper = self._getViewCallable(config) - self.failIf(IMultiView.providedBy(wrapper)) + self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') @@ -1012,7 +1018,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_view(view=newview, context=RuntimeError) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) - self.failIf(IMultiView.providedBy(wrapper)) + self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') @@ -1030,7 +1036,7 @@ class ConfiguratorTests(unittest.TestCase): view, (IViewClassifier, IRequest, Interface), IView, name='') config.add_view(view=view) wrapper = self._getViewCallable(config) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_exc_multiview_replaces_existing_view(self): @@ -1054,7 +1060,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_view(view=view, context=RuntimeError) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_multiview_replaces_existing_securedview(self): @@ -1071,7 +1077,7 @@ class ConfiguratorTests(unittest.TestCase): ISecuredView, name='') config.add_view(view=view) wrapper = self._getViewCallable(config) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_exc_multiview_replaces_existing_securedview(self): @@ -1095,7 +1101,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_view(view=view, context=RuntimeError) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_with_accept_multiview_replaces_existing_view(self): @@ -1113,7 +1119,7 @@ class ConfiguratorTests(unittest.TestCase): view, (IViewClassifier, IRequest, Interface), IView, name='') config.add_view(view=view2, accept='text/html') wrapper = self._getViewCallable(config) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) self.assertEqual(len(wrapper.media_views), 1) self.assertEqual(wrapper(None, None), 'OK') @@ -1144,7 +1150,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_view(view=view2, accept='text/html', context=RuntimeError) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) self.assertEqual(len(wrapper.media_views), 1) self.assertEqual(wrapper(None, None), 'OK') @@ -1169,7 +1175,7 @@ class ConfiguratorTests(unittest.TestCase): view, (IViewClassifier, IRequest, Interface), IView, name='') config.add_view(view=view2) wrapper = self._getViewCallable(config) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) self.assertEqual(len(wrapper.media_views), 1) self.assertEqual(wrapper(None, None), 'OK2') @@ -1202,7 +1208,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_view(view=view2, context=RuntimeError) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) self.assertEqual(len(wrapper.media_views), 1) self.assertEqual(wrapper(None, None), 'OK2') @@ -1223,7 +1229,7 @@ class ConfiguratorTests(unittest.TestCase): view2 = lambda *arg: 'OK2' config.add_view(view=view2) wrapper = self._getViewCallable(config) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual([x[:2] for x in wrapper.views], [(view2, None)]) self.assertEqual(wrapper(None, None), 'OK1') @@ -1247,7 +1253,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_view(view=view2, context=RuntimeError) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) - self.failUnless(IMultiView.providedBy(wrapper)) + self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual([x[:2] for x in wrapper.views], [(view2, None)]) self.assertEqual(wrapper(None, None), 'OK1') @@ -1268,10 +1274,10 @@ class ConfiguratorTests(unittest.TestCase): view, (IViewClassifier, IRequest, ISuper), IView, name='') config.add_view(view=view2, for_=ISub) wrapper = self._getViewCallable(config, ISuper, IRequest) - self.failIf(IMultiView.providedBy(wrapper)) + self.assertFalse(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') wrapper = self._getViewCallable(config, ISub, IRequest) - self.failIf(IMultiView.providedBy(wrapper)) + self.assertFalse(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK2') def test_add_view_multiview_exception_superclass_then_subclass(self): @@ -1298,14 +1304,14 @@ class ConfiguratorTests(unittest.TestCase): wrapper_exc_view = self._getViewCallable( config, implementedBy(Super), IRequest, exception_view=True) self.assertEqual(wrapper_exc_view, wrapper) - self.failIf(IMultiView.providedBy(wrapper_exc_view)) + self.assertFalse(IMultiView.providedBy(wrapper_exc_view)) self.assertEqual(wrapper_exc_view(None, None), 'OK') wrapper = self._getViewCallable( config, implementedBy(Sub), IRequest) wrapper_exc_view = self._getViewCallable( config, implementedBy(Sub), IRequest, exception_view=True) self.assertEqual(wrapper_exc_view, wrapper) - self.failIf(IMultiView.providedBy(wrapper_exc_view)) + self.assertFalse(IMultiView.providedBy(wrapper_exc_view)) self.assertEqual(wrapper_exc_view(None, None), 'OK2') def test_add_view_multiview_call_ordering(self): @@ -1486,9 +1492,9 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper, None) config.add_route('foo', '/a/b') request_iface = self._getRouteRequestIface(config, 'foo') - self.failIfEqual(request_iface, None) + self.assertNotEqual(request_iface, None) wrapper = self._getViewCallable(config, request_iface=request_iface) - self.failIfEqual(wrapper, None) + self.assertNotEqual(wrapper, None) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_with_route_name_deferred_views_already_exist(self): @@ -1532,11 +1538,11 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper_exc_view, None) config.add_route('foo', '/a/b') request_iface = self._getRouteRequestIface(config, 'foo') - self.failIfEqual(request_iface, None) + self.assertNotEqual(request_iface, None) wrapper_exc_view = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), request_iface=request_iface, exception_view=True) - self.failIfEqual(wrapper_exc_view, None) + self.assertNotEqual(wrapper_exc_view, None) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), request_iface=request_iface) @@ -1979,129 +1985,6 @@ class ConfiguratorTests(unittest.TestCase): request.accept = ['text/html'] self.assertEqual(predicate(None, request), False) - def test_add_route_with_view(self): - config = self._makeOne(autocommit=True) - view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view) - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable(config, None, request_type) - self.assertEqual(wrapper(None, None), 'OK') - self._assertRoute(config, 'name', 'path') - - def test_add_route_with_view_context(self): - config = self._makeOne(autocommit=True) - view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, view_context=IDummy) - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable(config, IDummy, request_type) - self.assertEqual(wrapper(None, None), 'OK') - self._assertRoute(config, 'name', 'path') - wrapper = self._getViewCallable(config, IOther, request_type) - self.assertEqual(wrapper, None) - - def test_add_route_with_view_exception(self): - from zope.interface import implementedBy - config = self._makeOne(autocommit=True) - view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, view_context=RuntimeError) - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable( - config, ctx_iface=implementedBy(RuntimeError), - request_iface=request_type, exception_view=True) - self.assertEqual(wrapper(None, None), 'OK') - self._assertRoute(config, 'name', 'path') - wrapper = self._getViewCallable( - config, ctx_iface=IOther, - request_iface=request_type, exception_view=True) - self.assertEqual(wrapper, None) - - def test_add_route_with_view_for(self): - config = self._makeOne(autocommit=True) - view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, view_for=IDummy) - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable(config, IDummy, request_type) - self.assertEqual(wrapper(None, None), 'OK') - self._assertRoute(config, 'name', 'path') - wrapper = self._getViewCallable(config, IOther, request_type) - self.assertEqual(wrapper, None) - - def test_add_route_with_for_(self): - config = self._makeOne(autocommit=True) - view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, for_=IDummy) - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable(config, IDummy, request_type) - self.assertEqual(wrapper(None, None), 'OK') - self._assertRoute(config, 'name', 'path') - wrapper = self._getViewCallable(config, IOther, request_type) - self.assertEqual(wrapper, None) - - def test_add_route_with_view_renderer(self): - config = self._makeOne(autocommit=True) - self._registerRenderer(config) - view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, - view_renderer='fixtures/minimal.txt') - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable(config, None, request_type) - self._assertRoute(config, 'name', 'path') - self.assertEqual(wrapper(None, None).body, 'Hello!') - - def test_add_route_with_view_attr(self): - config = self._makeOne(autocommit=True) - self._registerRenderer(config) - class View(object): - def __init__(self, context, request): - pass - def alt(self): - return 'OK' - config.add_route('name', 'path', view=View, view_attr='alt') - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable(config, None, request_type) - self._assertRoute(config, 'name', 'path') - request = self._makeRequest(config) - self.assertEqual(wrapper(None, request), 'OK') - - def test_add_route_with_view_renderer_alias(self): - config = self._makeOne(autocommit=True) - self._registerRenderer(config) - view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, - renderer='fixtures/minimal.txt') - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable(config, None, request_type) - self._assertRoute(config, 'name', 'path') - self.assertEqual(wrapper(None, None).body, 'Hello!') - - def test_add_route_with_view_permission(self): - from pyramid.interfaces import IAuthenticationPolicy - from pyramid.interfaces import IAuthorizationPolicy - config = self._makeOne(autocommit=True) - policy = lambda *arg: None - config.registry.registerUtility(policy, IAuthenticationPolicy) - config.registry.registerUtility(policy, IAuthorizationPolicy) - view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, view_permission='edit') - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable(config, None, request_type) - self._assertRoute(config, 'name', 'path') - self.failUnless(hasattr(wrapper, '__call_permissive__')) - - def test_add_route_with_view_permission_alias(self): - from pyramid.interfaces import IAuthenticationPolicy - from pyramid.interfaces import IAuthorizationPolicy - config = self._makeOne(autocommit=True) - policy = lambda *arg: None - config.registry.registerUtility(policy, IAuthenticationPolicy) - config.registry.registerUtility(policy, IAuthorizationPolicy) - view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, permission='edit') - request_type = self._getRouteRequestIface(config, 'name') - wrapper = self._getViewCallable(config, None, request_type) - self._assertRoute(config, 'name', 'path') - self.failUnless(hasattr(wrapper, '__call_permissive__')) - def test_add_route_no_pattern_with_path(self): config = self._makeOne(autocommit=True) route = config.add_route('name', path='path') @@ -2163,14 +2046,14 @@ class ConfiguratorTests(unittest.TestCase): return 'OK' config = self._makeOne() result = config.derive_view(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(result(None, None), 'OK') def test_derive_view_dottedname(self): config = self._makeOne() result = config.derive_view( 'pyramid.tests.test_config.dummy_view') - self.failIf(result is dummy_view) + self.assertFalse(result is dummy_view) self.assertEqual(result(None, None), 'OK') def test_derive_view_with_default_renderer_no_explicit_renderer(self): @@ -2184,7 +2067,7 @@ class ConfiguratorTests(unittest.TestCase): def view(request): return 'OK' result = config.derive_view(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(result(None, None).body, 'moo') def test_derive_view_with_default_renderer_with_explicit_renderer(self): @@ -2200,7 +2083,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_renderer(None, moo) config.add_renderer('foo', foo) result = config.derive_view(view, renderer='foo') - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest(config) self.assertEqual(result(None, request).body, 'foo') @@ -2350,7 +2233,7 @@ class ConfiguratorTests(unittest.TestCase): result = view(None, request) finally: config.end() - self.failUnless('div' in result.body) + self.assertTrue('div' in result.body) def test_set_forbidden_view(self): from zope.interface import implementedBy @@ -2397,7 +2280,7 @@ class ConfiguratorTests(unittest.TestCase): result = view(None, request) finally: config.end() - self.failUnless('div' in result.body) + self.assertTrue('div' in result.body) def test__set_authentication_policy(self): from pyramid.interfaces import IAuthenticationPolicy @@ -2791,7 +2674,7 @@ class ConfiguratorTests(unittest.TestCase): from pyramid.interfaces import IAuthenticationPolicy from pyramid.interfaces import IAuthorizationPolicy ut = config.registry.getUtility(IAuthenticationPolicy) - self.failUnless(isinstance(ut, DummySecurityPolicy)) + self.assertTrue(isinstance(ut, DummySecurityPolicy)) ut = config.registry.getUtility(IAuthorizationPolicy) self.assertEqual(ut.userid, 'user') self.assertEqual(ut.groupids, ('group1', 'group2')) @@ -2899,7 +2782,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(autocommit=True) renderer = config.testing_add_renderer('templates/foo.pt') from pyramid.testing import DummyTemplateRenderer - self.failUnless(isinstance(renderer, DummyTemplateRenderer)) + self.assertTrue(isinstance(renderer, DummyTemplateRenderer)) from pyramid.renderers import render_to_response # must provide request to pass in registry (this is a functest) request = DummyRequest() @@ -2915,8 +2798,8 @@ class ConfiguratorTests(unittest.TestCase): renderer1 = config.testing_add_renderer('templates/foo.pt') renderer2 = config.testing_add_renderer('templates/bar.pt') from pyramid.testing import DummyTemplateRenderer - self.failUnless(isinstance(renderer1, DummyTemplateRenderer)) - self.failUnless(isinstance(renderer2, DummyTemplateRenderer)) + self.assertTrue(isinstance(renderer1, DummyTemplateRenderer)) + self.assertTrue(isinstance(renderer2, DummyTemplateRenderer)) from pyramid.renderers import render_to_response # must provide request to pass in registry (this is a functest) request = DummyRequest() @@ -2955,7 +2838,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(autocommit=True) renderer = config.testing_add_template('templates/foo.pt') from pyramid.testing import DummyTemplateRenderer - self.failUnless(isinstance(renderer, DummyTemplateRenderer)) + self.assertTrue(isinstance(renderer, DummyTemplateRenderer)) from pyramid.renderers import render_to_response # must provide request to pass in registry (this is a functest) request = DummyRequest() @@ -3035,24 +2918,6 @@ class ConfiguratorTests(unittest.TestCase): registeredview = self._getViewCallable(config) self.assertEqual(registeredview.__name__, 'view3') - def test_conflict_route_with_view(self): - from zope.configuration.config import ConfigurationConflictError - config = self._makeOne() - def view1(request): pass - def view2(request): pass - config.add_route('a', '/a', view=view1) - config.add_route('a', '/a', view=view2) - try: - config.commit() - except ConfigurationConflictError, why: - c1, c2, c3, c4 = self._conflictFunctions(why) - self.assertEqual(c1, 'test_conflict_route_with_view') - self.assertEqual(c2, 'test_conflict_route_with_view') - self.assertEqual(c3, 'test_conflict_route_with_view') - self.assertEqual(c4, 'test_conflict_route_with_view') - else: # pragma: no cover - raise AssertionError - def test_conflict_set_notfound_view(self): from zope.configuration.config import ConfigurationConflictError config = self._makeOne() @@ -3102,16 +2967,10 @@ class ConfiguratorTests(unittest.TestCase): yield confinst[3] which = list(scanconflicts(why)) self.assertEqual(len(which), 4) - self.failUnless("@view_config(renderer='string')" in which) - self.failUnless("@view_config(name='two', renderer='string')" in + self.assertTrue("@view_config(renderer='string')" in which) + self.assertTrue("@view_config(name='two', renderer='string')" in which) - def _conflictFunctions(self, e): - conflicts = e._conflicts.values() - for conflict in conflicts: - for confinst in conflict: - yield confinst[2] - def test___getattr__missing_when_directives_exist(self): config = self._makeOne() directives = {} @@ -3128,7 +2987,7 @@ class ConfiguratorTests(unittest.TestCase): directives = {'foo':(foo, True)} config.registry._directives = directives foo_meth = config.foo - self.failUnless(foo_meth.im_func.__docobj__ is foo) + self.assertTrue(foo_meth.im_func.__docobj__ is foo) def test___getattr__matches_no_action_wrap(self): config = self._makeOne() @@ -3136,7 +2995,218 @@ class ConfiguratorTests(unittest.TestCase): directives = {'foo':(foo, False)} config.registry._directives = directives foo_meth = config.foo - self.failUnless(foo_meth.im_func is foo) + self.assertTrue(foo_meth.im_func is foo) + +class TestConfiguratorDeprecatedFeatures(unittest.TestCase): + def setUp(self): + import warnings + warnings.filterwarnings('ignore') + + def tearDown(self): + import warnings + warnings.resetwarnings() + + def _makeOne(self, *arg, **kw): + from pyramid.config import Configurator + return Configurator(*arg, **kw) + + def _getRouteRequestIface(self, config, name): + from pyramid.interfaces import IRouteRequest + iface = config.registry.getUtility(IRouteRequest, name) + return iface + + def _getViewCallable(self, config, ctx_iface=None, request_iface=None, + name='', exception_view=False): + from zope.interface import Interface + from pyramid.interfaces import IView + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IExceptionViewClassifier + if exception_view: + classifier = IExceptionViewClassifier + else: + classifier = IViewClassifier + if ctx_iface is None: + ctx_iface = Interface + return config.registry.adapters.lookup( + (classifier, request_iface, ctx_iface), IView, name=name, + default=None) + + def _registerRenderer(self, config, name='.txt'): + from pyramid.interfaces import IRendererFactory + from pyramid.interfaces import ITemplateRenderer + from zope.interface import implements + class Renderer: + implements(ITemplateRenderer) + def __init__(self, info): + self.__class__.info = info + def __call__(self, *arg): + return 'Hello!' + config.registry.registerUtility(Renderer, IRendererFactory, name=name) + return Renderer + + def _assertRoute(self, config, name, path, num_predicates=0): + from pyramid.interfaces import IRoutesMapper + mapper = config.registry.getUtility(IRoutesMapper) + routes = mapper.get_routes() + route = routes[0] + self.assertEqual(len(routes), 1) + self.assertEqual(route.name, name) + self.assertEqual(route.path, path) + self.assertEqual(len(routes[0].predicates), num_predicates) + return route + + def _makeRequest(self, config): + request = DummyRequest() + request.registry = config.registry + return request + + def _conflictFunctions(self, e): + conflicts = e._conflicts.values() + for conflict in conflicts: + for confinst in conflict: + yield confinst[2] + + def test_add_route_with_view(self): + config = self._makeOne(autocommit=True) + view = lambda *arg: 'OK' + config.add_route('name', 'path', view=view) + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable(config, None, request_type) + self.assertEqual(wrapper(None, None), 'OK') + self._assertRoute(config, 'name', 'path') + + def test_add_route_with_view_context(self): + config = self._makeOne(autocommit=True) + view = lambda *arg: 'OK' + config.add_route('name', 'path', view=view, view_context=IDummy) + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable(config, IDummy, request_type) + self.assertEqual(wrapper(None, None), 'OK') + self._assertRoute(config, 'name', 'path') + wrapper = self._getViewCallable(config, IOther, request_type) + self.assertEqual(wrapper, None) + + def test_add_route_with_view_exception(self): + from zope.interface import implementedBy + config = self._makeOne(autocommit=True) + view = lambda *arg: 'OK' + config.add_route('name', 'path', view=view, view_context=RuntimeError) + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable( + config, ctx_iface=implementedBy(RuntimeError), + request_iface=request_type, exception_view=True) + self.assertEqual(wrapper(None, None), 'OK') + self._assertRoute(config, 'name', 'path') + wrapper = self._getViewCallable( + config, ctx_iface=IOther, + request_iface=request_type, exception_view=True) + self.assertEqual(wrapper, None) + + def test_add_route_with_view_for(self): + config = self._makeOne(autocommit=True) + view = lambda *arg: 'OK' + config.add_route('name', 'path', view=view, view_for=IDummy) + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable(config, IDummy, request_type) + self.assertEqual(wrapper(None, None), 'OK') + self._assertRoute(config, 'name', 'path') + wrapper = self._getViewCallable(config, IOther, request_type) + self.assertEqual(wrapper, None) + + def test_add_route_with_for_(self): + config = self._makeOne(autocommit=True) + view = lambda *arg: 'OK' + config.add_route('name', 'path', view=view, for_=IDummy) + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable(config, IDummy, request_type) + self.assertEqual(wrapper(None, None), 'OK') + self._assertRoute(config, 'name', 'path') + wrapper = self._getViewCallable(config, IOther, request_type) + self.assertEqual(wrapper, None) + + def test_add_route_with_view_renderer(self): + config = self._makeOne(autocommit=True) + self._registerRenderer(config) + view = lambda *arg: 'OK' + config.add_route('name', 'path', view=view, + view_renderer='fixtures/minimal.txt') + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable(config, None, request_type) + self._assertRoute(config, 'name', 'path') + self.assertEqual(wrapper(None, None).body, 'Hello!') + + def test_add_route_with_view_attr(self): + config = self._makeOne(autocommit=True) + self._registerRenderer(config) + class View(object): + def __init__(self, context, request): + pass + def alt(self): + return 'OK' + config.add_route('name', 'path', view=View, view_attr='alt') + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable(config, None, request_type) + self._assertRoute(config, 'name', 'path') + request = self._makeRequest(config) + self.assertEqual(wrapper(None, request), 'OK') + + def test_add_route_with_view_renderer_alias(self): + config = self._makeOne(autocommit=True) + self._registerRenderer(config) + view = lambda *arg: 'OK' + config.add_route('name', 'path', view=view, + renderer='fixtures/minimal.txt') + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable(config, None, request_type) + self._assertRoute(config, 'name', 'path') + self.assertEqual(wrapper(None, None).body, 'Hello!') + + def test_add_route_with_view_permission(self): + from pyramid.interfaces import IAuthenticationPolicy + from pyramid.interfaces import IAuthorizationPolicy + config = self._makeOne(autocommit=True) + policy = lambda *arg: None + config.registry.registerUtility(policy, IAuthenticationPolicy) + config.registry.registerUtility(policy, IAuthorizationPolicy) + view = lambda *arg: 'OK' + config.add_route('name', 'path', view=view, view_permission='edit') + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable(config, None, request_type) + self._assertRoute(config, 'name', 'path') + self.assertTrue(hasattr(wrapper, '__call_permissive__')) + + def test_add_route_with_view_permission_alias(self): + from pyramid.interfaces import IAuthenticationPolicy + from pyramid.interfaces import IAuthorizationPolicy + config = self._makeOne(autocommit=True) + policy = lambda *arg: None + config.registry.registerUtility(policy, IAuthenticationPolicy) + config.registry.registerUtility(policy, IAuthorizationPolicy) + view = lambda *arg: 'OK' + config.add_route('name', 'path', view=view, permission='edit') + request_type = self._getRouteRequestIface(config, 'name') + wrapper = self._getViewCallable(config, None, request_type) + self._assertRoute(config, 'name', 'path') + self.assertTrue(hasattr(wrapper, '__call_permissive__')) + + def test_conflict_route_with_view(self): + from zope.configuration.config import ConfigurationConflictError + config = self._makeOne() + def view1(request): pass + def view2(request): pass + config.add_route('a', '/a', view=view1) + config.add_route('a', '/a', view=view2) + try: + config.commit() + except ConfigurationConflictError, why: + c1, c2, c3, c4 = self._conflictFunctions(why) + self.assertEqual(c1, 'test_conflict_route_with_view') + self.assertEqual(c2, 'test_conflict_route_with_view') + self.assertEqual(c3, 'test_conflict_route_with_view') + self.assertEqual(c4, 'test_conflict_route_with_view') + else: # pragma: no cover + raise AssertionError + class TestConfigurator_add_directive(unittest.TestCase): @@ -3250,7 +3320,7 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(result(None, None), 'OK') def test_requestonly_function_with_renderer(self): @@ -3265,7 +3335,7 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne(renderer=moo()) result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() context = testing.DummyResource() self.assertEqual(result(context, request), 'moo') @@ -3283,7 +3353,7 @@ class TestViewDeriver(unittest.TestCase): self.config.add_renderer('moo', moo) deriver = self._makeOne(renderer='string') result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() request.override_renderer = 'moo' context = testing.DummyResource() @@ -3301,12 +3371,12 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne(renderer=moo()) result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() request.__view__ = 'view' context = testing.DummyResource() self.assertEqual(result(context, request), 'moo') - self.failIf(hasattr(request, '__view__')) + self.assertFalse(hasattr(request, '__view__')) def test_class_without_attr(self): class View(object): @@ -3337,8 +3407,8 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne() result = deriver(view) - self.failUnless(result is view) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertTrue(result is view) + self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(view(None, None), 'OK') def test_as_function_requestonly(self): @@ -3346,11 +3416,11 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') def test_as_newstyle_class_context_and_request(self): @@ -3361,11 +3431,11 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') self.assertEqual(request.__view__.__class__, view) @@ -3378,11 +3448,11 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') self.assertEqual(request.__view__.__class__, view) @@ -3395,11 +3465,11 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') self.assertEqual(request.__view__.__class__, view) @@ -3412,11 +3482,11 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') self.assertEqual(request.__view__.__class__, view) @@ -3428,8 +3498,8 @@ class TestViewDeriver(unittest.TestCase): view = View() deriver = self._makeOne() result = deriver(view) - self.failUnless(result is view) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertTrue(result is view) + self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') def test_as_instance_requestonly(self): @@ -3439,11 +3509,11 @@ class TestViewDeriver(unittest.TestCase): view = View() deriver = self._makeOne() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) - self.failUnless('instance' in result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertTrue('instance' in result.__name__) + self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') def test_with_debug_authorization_no_authpol(self): @@ -3456,7 +3526,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' @@ -3479,7 +3549,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' @@ -3502,7 +3572,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' @@ -3524,7 +3594,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' @@ -3624,7 +3694,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' @@ -3645,7 +3715,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' @@ -3662,7 +3732,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) + self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' @@ -3736,7 +3806,7 @@ class TestViewDeriver(unittest.TestCase): deriver = self._makeOne(viewname='inner', wrapper_viewname='owrap') result = deriver(inner_view) - self.failIf(result is inner_view) + self.assertFalse(result is inner_view) self.assertEqual(inner_view.__module__, result.__module__) self.assertEqual(inner_view.__doc__, result.__doc__) request = self._makeRequest() @@ -3768,7 +3838,7 @@ class TestViewDeriver(unittest.TestCase): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') result = deriver(View) - self.failIf(result is View) + self.assertFalse(result is View) self.assertEqual(result.__module__, View.__module__) self.assertEqual(result.__doc__, View.__doc__) self.assertEqual(result.__name__, View.__name__) @@ -3791,7 +3861,7 @@ class TestViewDeriver(unittest.TestCase): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') result = deriver(View) - self.failIf(result is View) + self.assertFalse(result is View) self.assertEqual(result.__module__, View.__module__) self.assertEqual(result.__doc__, View.__doc__) self.assertEqual(result.__name__, View.__name__) @@ -3814,7 +3884,7 @@ class TestViewDeriver(unittest.TestCase): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') result = deriver(View) - self.failIf(result is View) + self.assertFalse(result is View) self.assertEqual(result.__module__, View.__module__) self.assertEqual(result.__doc__, View.__doc__) self.assertEqual(result.__name__, View.__name__) @@ -3837,7 +3907,7 @@ class TestViewDeriver(unittest.TestCase): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') result = deriver(View) - self.failIf(result is View) + self.assertFalse(result is View) self.assertEqual(result.__module__, View.__module__) self.assertEqual(result.__doc__, View.__doc__) self.assertEqual(result.__name__, View.__name__) @@ -3859,7 +3929,7 @@ class TestViewDeriver(unittest.TestCase): deriver = self._makeOne(renderer=renderer(), attr='index') view = View() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(result.__module__, view.__module__) self.assertEqual(result.__doc__, view.__doc__) request = self._makeRequest() @@ -3880,7 +3950,7 @@ class TestViewDeriver(unittest.TestCase): deriver = self._makeOne(renderer=renderer(), attr='index') view = View() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(result.__module__, view.__module__) self.assertEqual(result.__doc__, view.__doc__) request = self._makeRequest() @@ -3898,7 +3968,7 @@ class TestViewDeriver(unittest.TestCase): def view(context, request): return 'NOTOK' deriver = self._makeOne(mapper=mapper) result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(result(None, None), 'OK') def test_with_view_mapper_view_specified(self): @@ -3913,7 +3983,7 @@ class TestViewDeriver(unittest.TestCase): view.__view_mapper__ = mapper deriver = self._makeOne() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(result(None, None), 'OK') def test_with_view_mapper_default_mapper_specified(self): @@ -3928,7 +3998,7 @@ class TestViewDeriver(unittest.TestCase): def view(context, request): return 'NOTOK' deriver = self._makeOne() result = deriver(view) - self.failIf(result is view) + self.assertFalse(result is view) self.assertEqual(result(None, None), 'OK') def test_attr_wrapped_view_branching_default_phash(self): @@ -3968,7 +4038,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne() result = mapper(view) - self.failUnless(result is view) + self.assertTrue(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -3977,7 +4047,7 @@ class TestDefaultViewMapper(unittest.TestCase): """ """ mapper = self._makeOne(attr='__name__') result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertRaises(TypeError, result, None, request) @@ -3986,7 +4056,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne() result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -3995,7 +4065,7 @@ class TestDefaultViewMapper(unittest.TestCase): """ """ mapper = self._makeOne(attr='__name__') result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertRaises(TypeError, result, None, request) @@ -4007,7 +4077,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne() result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4019,7 +4089,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne(attr='index') result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4031,7 +4101,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne() result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4043,7 +4113,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne(attr='index') result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4055,7 +4125,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne() result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4067,7 +4137,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne(attr='index') result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4079,7 +4149,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne() result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4091,7 +4161,7 @@ class TestDefaultViewMapper(unittest.TestCase): return 'OK' mapper = self._makeOne(attr='index') result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4102,7 +4172,7 @@ class TestDefaultViewMapper(unittest.TestCase): view = View() mapper = self._makeOne() result = mapper(view) - self.failUnless(result is view) + self.assertTrue(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4113,7 +4183,7 @@ class TestDefaultViewMapper(unittest.TestCase): view = View() mapper = self._makeOne(attr='index') result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4124,7 +4194,7 @@ class TestDefaultViewMapper(unittest.TestCase): view = View() mapper = self._makeOne() result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4135,7 +4205,7 @@ class TestDefaultViewMapper(unittest.TestCase): view = View() mapper = self._makeOne(attr='index') result = mapper(view) - self.failIf(result is view) + self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') @@ -4148,7 +4218,7 @@ class Test_preserve_view_attrs(unittest.TestCase): def view(context, request): """ """ result = self._callFUT(view, view) - self.failUnless(result is view) + self.assertTrue(result is view) def test_it_different_with_existing_original_view(self): def view1(context, request): pass @@ -4156,7 +4226,7 @@ class Test_preserve_view_attrs(unittest.TestCase): def view2(context, request): pass result = self._callFUT(view1, view2) self.assertEqual(result.__original_view__, 'abc') - self.failIf(result is view1) + self.assertFalse(result is view1) def test_it_different(self): class DummyView1: @@ -4187,15 +4257,15 @@ class Test_preserve_view_attrs(unittest.TestCase): view2 = DummyView2() result = self._callFUT(view2, view1) self.assertEqual(result, view1) - self.failUnless(view1.__original_view__ is view2) - self.failUnless(view1.__doc__ is view2.__doc__) - self.failUnless(view1.__module__ is view2.__module__) - self.failUnless(view1.__name__ is view2.__name__) - self.failUnless(view1.__call_permissive__.im_func is + self.assertTrue(view1.__original_view__ is view2) + self.assertTrue(view1.__doc__ is view2.__doc__) + self.assertTrue(view1.__module__ is view2.__module__) + self.assertTrue(view1.__name__ is view2.__name__) + self.assertTrue(view1.__call_permissive__.im_func is view2.__call_permissive__.im_func) - self.failUnless(view1.__permitted__.im_func is + self.assertTrue(view1.__permitted__.im_func is view2.__permitted__.im_func) - self.failUnless(view1.__predicated__.im_func is + self.assertTrue(view1.__predicated__.im_func is view2.__predicated__.im_func) class Test__make_predicates(unittest.TestCase): @@ -4206,7 +4276,7 @@ class Test__make_predicates(unittest.TestCase): def test_ordering_xhr_and_request_method_trump_only_containment(self): order1, _, _ = self._callFUT(xhr=True, request_method='GET') order2, _, _ = self._callFUT(containment=True) - self.failUnless(order1 < order2) + self.assertTrue(order1 < order2) def test_ordering_number_of_predicates(self): order1, _, _ = self._callFUT( @@ -4286,15 +4356,15 @@ class Test__make_predicates(unittest.TestCase): order11, _, _ = self._callFUT( ) self.assertEqual(order1, order2) - self.failUnless(order3 > order2) - self.failUnless(order4 > order3) - self.failUnless(order5 > order4) - self.failUnless(order6 > order5) - self.failUnless(order7 > order6) - self.failUnless(order8 > order7) - self.failUnless(order9 > order8) - self.failUnless(order10 > order9) - self.failUnless(order11 > order10) + self.assertTrue(order3 > order2) + self.assertTrue(order4 > order3) + self.assertTrue(order5 > order4) + self.assertTrue(order6 > order5) + self.assertTrue(order7 > order6) + self.assertTrue(order8 > order7) + self.assertTrue(order9 > order8) + self.assertTrue(order10 > order9) + self.assertTrue(order11 > order10) def test_ordering_importance_of_predicates(self): order1, _, _ = self._callFUT( @@ -4324,14 +4394,14 @@ class Test__make_predicates(unittest.TestCase): order9, _, _ = self._callFUT( custom=(DummyCustomPredicate(),), ) - self.failUnless(order1 > order2) - self.failUnless(order2 > order3) - self.failUnless(order3 > order4) - self.failUnless(order4 > order5) - self.failUnless(order5 > order6) - self.failUnless(order6 > order7) - self.failUnless(order7 > order8) - self.failUnless(order8 > order9) + self.assertTrue(order1 > order2) + self.assertTrue(order2 > order3) + self.assertTrue(order3 > order4) + self.assertTrue(order4 > order5) + self.assertTrue(order5 > order6) + self.assertTrue(order6 > order7) + self.assertTrue(order7 > order8) + self.assertTrue(order8 > order9) def test_ordering_importance_and_number(self): order1, _, _ = self._callFUT( @@ -4341,7 +4411,7 @@ class Test__make_predicates(unittest.TestCase): order2, _, _ = self._callFUT( custom=(DummyCustomPredicate(),), ) - self.failUnless(order1 < order2) + self.assertTrue(order1 < order2) order1, _, _ = self._callFUT( xhr='xhr', @@ -4351,7 +4421,7 @@ class Test__make_predicates(unittest.TestCase): request_method='request_method', custom=(DummyCustomPredicate(),), ) - self.failUnless(order1 > order2) + self.assertTrue(order1 > order2) order1, _, _ = self._callFUT( xhr='xhr', @@ -4362,7 +4432,7 @@ class Test__make_predicates(unittest.TestCase): request_method='request_method', custom=(DummyCustomPredicate(),), ) - self.failUnless(order1 < order2) + self.assertTrue(order1 < order2) order1, _, _ = self._callFUT( xhr='xhr', @@ -4374,7 +4444,7 @@ class Test__make_predicates(unittest.TestCase): request_method='request_method', custom=(DummyCustomPredicate(),), ) - self.failUnless(order1 > order2) + self.assertTrue(order1 > order2) def test_different_custom_predicates_with_same_hash(self): class PredicateWithHash(object): diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index 9f655959d..22a42758a 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -190,12 +190,12 @@ class TestBeforeRender(unittest.TestCase): def test__contains__True(self): system = {'a':1} event = self._makeOne(system) - self.failUnless('a' in event) + self.assertTrue('a' in event) def test__contains__False(self): system = {} event = self._makeOne(system) - self.failIf('a' in event) + self.assertFalse('a' in event) def test__getitem__success(self): system = {'a':1} diff --git a/pyramid/tests/test_exceptions.py b/pyramid/tests/test_exceptions.py index 15eaa8698..5d0fa1e1a 100644 --- a/pyramid/tests/test_exceptions.py +++ b/pyramid/tests/test_exceptions.py @@ -7,7 +7,7 @@ class TestExceptionResponse(unittest.TestCase): def test_app_iter(self): exc = self._makeOne('') - self.failUnless('<code></code>' in exc.app_iter[0]) + self.assertTrue('<code></code>' in exc.app_iter[0]) def test_headerlist(self): exc = self._makeOne('') @@ -20,7 +20,7 @@ class TestExceptionResponse(unittest.TestCase): def test_withmessage(self): exc = self._makeOne('abc&123') - self.failUnless('<code>abc&123</code>' in exc.app_iter[0]) + self.assertTrue('<code>abc&123</code>' in exc.app_iter[0]) class TestNotFound(unittest.TestCase): def _makeOne(self, message): @@ -30,7 +30,7 @@ class TestNotFound(unittest.TestCase): def test_it(self): from pyramid.exceptions import ExceptionResponse e = self._makeOne('notfound') - self.failUnless(isinstance(e, ExceptionResponse)) + self.assertTrue(isinstance(e, ExceptionResponse)) self.assertEqual(e.status, '404 Not Found') class TestForbidden(unittest.TestCase): @@ -41,5 +41,5 @@ class TestForbidden(unittest.TestCase): def test_it(self): from pyramid.exceptions import ExceptionResponse e = self._makeOne('unauthorized') - self.failUnless(isinstance(e, ExceptionResponse)) + self.assertTrue(isinstance(e, ExceptionResponse)) self.assertEqual(e.status, '403 Forbidden') diff --git a/pyramid/tests/test_i18n.py b/pyramid/tests/test_i18n.py index 3155f0ba1..588f0a7cc 100644 --- a/pyramid/tests/test_i18n.py +++ b/pyramid/tests/test_i18n.py @@ -41,7 +41,7 @@ class TestLocalizer(unittest.TestCase): localizer = self._makeOne(None, translations) self.assertEqual(localizer.translate('123', domain='1', mapping={}), '123') - self.failUnless(localizer.translator) + self.assertTrue(localizer.translator) def test_pluralize(self): translations = DummyTranslations() @@ -49,7 +49,7 @@ class TestLocalizer(unittest.TestCase): self.assertEqual(localizer.pluralize('singular', 'plural', 1, domain='1', mapping={}), 'singular') - self.failUnless(localizer.pluralizer) + self.assertTrue(localizer.pluralizer) def test_pluralize_pluralizer_already_added(self): translations = DummyTranslations() @@ -63,7 +63,7 @@ class TestLocalizer(unittest.TestCase): result, (('singular', 'plural', 1), {'domain': '1', 'mapping': {}}) ) - self.failUnless(localizer.pluralizer is pluralizer) + self.assertTrue(localizer.pluralizer is pluralizer) class Test_negotiate_locale_name(unittest.TestCase): def setUp(self): @@ -174,7 +174,7 @@ class Test_make_localizer(unittest.TestCase): self.assertEqual(result.translate('Approve', 'deformsite'), 'Genehmigen') self.assertEqual(result.translate('Approve'), 'Approve') - self.failUnless(hasattr(result, 'pluralize')) + self.assertTrue(hasattr(result, 'pluralize')) def test_locale_from_mo_bad_mo(self): import os @@ -260,7 +260,7 @@ class Test_get_localizer(unittest.TestCase): self.assertEqual(result.translate('Approve', 'deformsite'), 'Genehmigen') self.assertEqual(result.translate('Approve'), 'Approve') - self.failUnless(hasattr(result, 'pluralize')) + self.assertTrue(hasattr(result, 'pluralize')) def test_locale_from_mo_bad_mo(self): import os diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index dc7525080..dd77d3aec 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -20,8 +20,8 @@ class WGSIAppPlusViewConfigTests(unittest.TestCase): def test_it(self): from venusian import ATTACH_ATTR import types - self.failUnless(getattr(wsgiapptest, ATTACH_ATTR)) - self.failUnless(type(wsgiapptest) is types.FunctionType) + self.assertTrue(getattr(wsgiapptest, ATTACH_ATTR)) + self.assertTrue(type(wsgiapptest) is types.FunctionType) context = DummyContext() request = DummyRequest() result = wsgiapptest(context, request) @@ -45,7 +45,7 @@ here = os.path.dirname(__file__) staticapp = static(os.path.join(here, 'fixtures')) class TestStaticApp(unittest.TestCase): - def test_it(self): + def test_basic(self): from webob import Request context = DummyContext() from StringIO import StringIO @@ -57,13 +57,68 @@ class TestStaticApp(unittest.TestCase): 'wsgi.version':(1,0), 'wsgi.url_scheme':'http', 'wsgi.input':StringIO()}) - request.subpath = ['minimal.pt'] + request.subpath = ('minimal.pt',) result = staticapp(context, request) self.assertEqual(result.status, '200 OK') self.assertEqual( result.body.replace('\r', ''), open(os.path.join(here, 'fixtures/minimal.pt'), 'r').read()) + def test_file_in_subdir(self): + from webob import Request + context = DummyContext() + from StringIO import StringIO + request = Request({'PATH_INFO':'', + 'SCRIPT_NAME':'', + 'SERVER_NAME':'localhost', + 'SERVER_PORT':'80', + 'REQUEST_METHOD':'GET', + 'wsgi.version':(1,0), + 'wsgi.url_scheme':'http', + 'wsgi.input':StringIO()}) + request.subpath = ('static', 'index.html',) + result = staticapp(context, request) + self.assertEqual(result.status, '200 OK') + self.assertEqual( + result.body.replace('\r', ''), + open(os.path.join(here, 'fixtures/static/index.html'), 'r').read()) + + def test_redirect_to_subdir(self): + from webob import Request + context = DummyContext() + from StringIO import StringIO + request = Request({'PATH_INFO':'', + 'SCRIPT_NAME':'', + 'SERVER_NAME':'localhost', + 'SERVER_PORT':'80', + 'REQUEST_METHOD':'GET', + 'wsgi.version':(1,0), + 'wsgi.url_scheme':'http', + 'wsgi.input':StringIO()}) + request.subpath = ('static',) + result = staticapp(context, request) + self.assertEqual(result.status, '301 Moved Permanently') + self.assertEqual(result.location, 'http://localhost/static/') + + def test_redirect_to_subdir_with_existing_script_name(self): + from webob import Request + context = DummyContext() + from StringIO import StringIO + request = Request({'PATH_INFO':'/static', + 'SCRIPT_NAME':'/script_name', + 'SERVER_NAME':'localhost', + 'SERVER_PORT':'80', + 'REQUEST_METHOD':'GET', + 'wsgi.version':(1,0), + 'wsgi.url_scheme':'http', + 'wsgi.input':StringIO()}) + request.subpath = ('static',) + result = staticapp(context, request) + self.assertEqual(result.status, '301 Moved Permanently') + self.assertEqual(result.location, + 'http://localhost/script_name/static/') + + class IntegrationBase(unittest.TestCase): root_factory = None package = None @@ -201,13 +256,13 @@ class TestForbiddenAppHasResult(IntegrationBase): def test_it(self): res = self.testapp.get('/x', status=403) message, result = [x.strip() for x in res.body.split('\n')] - self.failUnless(message.endswith('failed permission check')) - self.failUnless( + self.assertTrue(message.endswith('failed permission check')) + self.assertTrue( result.startswith("ACLDenied permission 'private' via ACE " "'<default deny>' in ACL " "'<No ACL found on any object in resource " "lineage>' on context")) - self.failUnless( + self.assertTrue( result.endswith("for principals ['system.Everyone']")) class TestViewDecoratorApp(IntegrationBase): @@ -221,20 +276,20 @@ class TestViewDecoratorApp(IntegrationBase): # we use mako here instead of chameleon because it works on Jython self._configure_mako() res = self.testapp.get('/first', status=200) - self.failUnless('OK' in res.body) + self.assertTrue('OK' in res.body) def test_second(self): # we use mako here instead of chameleon because it works on Jython self._configure_mako() res = self.testapp.get('/second', status=200) - self.failUnless('OK2' in res.body) + self.assertTrue('OK2' in res.body) class TestViewPermissionBug(IntegrationBase): # view_execution_permitted bug as reported by Shane at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003603.html package = 'pyramid.tests.permbugapp' def test_test(self): res = self.testapp.get('/test', status=200) - self.failUnless('ACLDenied' in res.body) + self.assertTrue('ACLDenied' in res.body) def test_x(self): self.testapp.get('/x', status=403) @@ -244,15 +299,15 @@ class TestDefaultViewPermissionBug(IntegrationBase): package = 'pyramid.tests.defpermbugapp' def test_x(self): res = self.testapp.get('/x', status=403) - self.failUnless('failed permission check' in res.body) + self.assertTrue('failed permission check' in res.body) def test_y(self): res = self.testapp.get('/y', status=403) - self.failUnless('failed permission check' in res.body) + self.assertTrue('failed permission check' in res.body) def test_z(self): res = self.testapp.get('/z', status=200) - self.failUnless('public' in res.body) + self.assertTrue('public' in res.body) from pyramid.tests.exceptionviewapp.models import AnException, NotAnException excroot = {'anexception':AnException(), @@ -263,31 +318,31 @@ class TestExceptionViewsApp(IntegrationBase): root_factory = lambda *arg: excroot def test_root(self): res = self.testapp.get('/', status=200) - self.failUnless('maybe' in res.body) + self.assertTrue('maybe' in res.body) def test_notanexception(self): res = self.testapp.get('/notanexception', status=200) - self.failUnless('no' in res.body) + self.assertTrue('no' in res.body) def test_anexception(self): res = self.testapp.get('/anexception', status=200) - self.failUnless('yes' in res.body) + self.assertTrue('yes' in res.body) def test_route_raise_exception(self): res = self.testapp.get('/route_raise_exception', status=200) - self.failUnless('yes' in res.body) + self.assertTrue('yes' in res.body) def test_route_raise_exception2(self): res = self.testapp.get('/route_raise_exception2', status=200) - self.failUnless('yes' in res.body) + self.assertTrue('yes' in res.body) def test_route_raise_exception3(self): res = self.testapp.get('/route_raise_exception3', status=200) - self.failUnless('whoa' in res.body) + self.assertTrue('whoa' in res.body) def test_route_raise_exception4(self): res = self.testapp.get('/route_raise_exception4', status=200) - self.failUnless('whoa' in res.body) + self.assertTrue('whoa' in res.body) class ImperativeIncludeConfigurationTest(unittest.TestCase): def setUp(self): @@ -305,15 +360,15 @@ class ImperativeIncludeConfigurationTest(unittest.TestCase): def test_root(self): res = self.testapp.get('/', status=200) - self.failUnless('root' in res.body) + self.assertTrue('root' in res.body) def test_two(self): res = self.testapp.get('/two', status=200) - self.failUnless('two' in res.body) + self.assertTrue('two' in res.body) def test_three(self): res = self.testapp.get('/three', status=200) - self.failUnless('three' in res.body) + self.assertTrue('three' in res.body) class SelfScanAppTest(unittest.TestCase): def setUp(self): @@ -329,11 +384,27 @@ class SelfScanAppTest(unittest.TestCase): def test_root(self): res = self.testapp.get('/', status=200) - self.failUnless('root' in res.body) + self.assertTrue('root' in res.body) def test_two(self): res = self.testapp.get('/two', status=200) - self.failUnless('two' in res.body) + self.assertTrue('two' in res.body) + +class WSGIApp2AppTest(unittest.TestCase): + def setUp(self): + from pyramid.tests.wsgiapp2app import main + config = main() + app = config.make_wsgi_app() + from webtest import TestApp + self.testapp = TestApp(app) + self.config = config + + def tearDown(self): + self.config.end() + + def test_hello(self): + res = self.testapp.get('/hello', status=200) + self.assertTrue('Hello' in res.body) class DummyContext(object): pass diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py index 8a03d4b5c..054c83d2b 100644 --- a/pyramid/tests/test_mako_templating.py +++ b/pyramid/tests/test_mako_templating.py @@ -260,7 +260,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() instance = self._makeOne('path', lookup) result = instance({}, {'system':1}) - self.failUnless(isinstance(result, unicode)) + self.assertTrue(isinstance(result, unicode)) self.assertEqual(result, u'result') def test_call_with_system_context(self): @@ -268,7 +268,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() instance = self._makeOne('path', lookup) result = instance({}, {'context':1}) - self.failUnless(isinstance(result, unicode)) + self.assertTrue(isinstance(result, unicode)) self.assertEqual(result, u'result') self.assertEqual(lookup.values, {'_context':1}) @@ -289,7 +289,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() instance = self._makeOne('path', lookup) result = instance.implementation().render_unicode() - self.failUnless(isinstance(result, unicode)) + self.assertTrue(isinstance(result, unicode)) self.assertEqual(result, u'result') class TestIntegration(unittest.TestCase): @@ -378,12 +378,12 @@ class TestPkgResourceTemplateLookup(unittest.TestCase): fixturedir = self.get_fixturedir() inst = self._makeOne(directories=[fixturedir]) result = inst.get_template('helloworld.mak') - self.failIf(result is None) + self.assertFalse(result is None) def test_get_template_asset_spec_with_filesystem_checks(self): inst = self._makeOne(filesystem_checks=True) result = inst.get_template('pyramid.tests:fixtures/helloworld.mak') - self.failIf(result is None) + self.assertFalse(result is None) def test_get_template_asset_spec_missing(self): from mako.exceptions import TopLevelLookupException diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 85a79b681..f317e08db 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -1,5 +1,39 @@ import unittest +class TestPyramidTemplate(unittest.TestCase): + def _getTargetClass(self): + from pyramid.paster import PyramidTemplate + return PyramidTemplate + + def _makeOne(self, name): + cls = self._getTargetClass() + return cls(name) + + def test_pre_logger_eq_root(self): + tmpl = self._makeOne('name') + vars = {'package':'root'} + result = tmpl.pre(None, None, vars) + self.assertEqual(result, None) + self.assertEqual(vars['package_logger'], 'app') + self.assertTrue(len(vars['random_string']) == 40) + + def test_pre_logger_noteq_root(self): + tmpl = self._makeOne('name') + vars = {'package':'notroot'} + result = tmpl.pre(None, None, vars) + self.assertEqual(result, None) + self.assertEqual(vars['package_logger'], 'notroot') + self.assertTrue(len(vars['random_string']) == 40) + + def test_post(self): + tmpl = self._makeOne('name') + vars = {'package':'root'} + L = [] + tmpl.out = lambda msg: L.append(msg) + result = tmpl.post(None, None, vars) + self.assertEqual(result, None) + self.assertEqual(L, ['Welcome to Pyramid. Sorry for the convenience.']) + class TestPShellCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.paster import PShellCommand @@ -8,7 +42,7 @@ class TestPShellCommand(unittest.TestCase): def _makeOne(self): return self._getTargetClass()('pshell') - def test_command_ipython_disabled(self): + def test_command_ipshell_is_None_ipython_enabled(self): command = self._makeOne() interact = DummyInteractor() app = DummyApp() @@ -18,18 +52,42 @@ class TestPShellCommand(unittest.TestCase): command.args = ('/foo/bar/myapp.ini', 'myapp') class Options(object): pass command.options = Options() - command.options.disable_ipython =True + command.options.disable_ipython = False command.command(IPShell=None) self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(loadapp.section_name, 'myapp') - self.failUnless(loadapp.relative_to) + self.assertTrue(loadapp.relative_to) + self.assertEqual(len(app.threadlocal_manager.pushed), 1) + pushed = app.threadlocal_manager.pushed[0] + self.assertEqual(pushed['registry'], dummy_registry) + self.assertEqual(pushed['request'].registry, dummy_registry) + self.assertEqual(interact.local, {'root':dummy_root, + 'registry':dummy_registry}) + self.assertTrue(interact.banner) + self.assertEqual(len(app.threadlocal_manager.popped), 1) + + def test_command_ipshell_is_not_None_ipython_disabled(self): + command = self._makeOne() + interact = DummyInteractor() + app = DummyApp() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + command.args = ('/foo/bar/myapp.ini', 'myapp') + class Options(object): pass + command.options = Options() + command.options.disable_ipython = True + command.command(IPShell='notnone') + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertTrue(loadapp.relative_to) self.assertEqual(len(app.threadlocal_manager.pushed), 1) pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], dummy_registry) self.assertEqual(pushed['request'].registry, dummy_registry) self.assertEqual(interact.local, {'root':dummy_root, 'registry':dummy_registry}) - self.failUnless(interact.banner) + self.assertTrue(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) def test_command_ipython_enabled(self): @@ -45,7 +103,7 @@ class TestPShellCommand(unittest.TestCase): command.command(IPShell=dummy_shell_factory) self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(loadapp.section_name, 'myapp') - self.failUnless(loadapp.relative_to) + self.assertTrue(loadapp.relative_to) self.assertEqual(len(app.threadlocal_manager.pushed), 1) pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], dummy_registry) @@ -53,7 +111,7 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(dummy_shell_factory.shell.local_ns, {'root':dummy_root, 'registry':dummy_registry}) self.assertEqual(dummy_shell_factory.shell.global_ns, {}) - self.failUnless('\n\n' in dummy_shell_factory.shell.IP.BANNER) + self.assertTrue('\n\n' in dummy_shell_factory.shell.IP.BANNER) self.assertEqual(len(app.threadlocal_manager.popped), 1) def test_command_get_app_hookable(self): @@ -79,7 +137,7 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(pushed['request'].registry, dummy_registry) self.assertEqual(interact.local, {'root':dummy_root, 'registry':dummy_registry}) - self.failUnless(interact.banner) + self.assertTrue(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) self.assertEqual(apped, [(('/foo/bar/myapp.ini', 'myapp'), {'loadapp': loadapp})]) @@ -104,11 +162,11 @@ class TestPShellCommand(unittest.TestCase): command.command(IPShell=None) self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(loadapp.section_name, 'myapp') - self.failUnless(loadapp.relative_to) + self.assertTrue(loadapp.relative_to) self.assertEqual(len(app.threadlocal_manager.pushed), 0) self.assertEqual(interact.local, {'root':root, 'registry':dummy_registry}) - self.failUnless(interact.banner) + self.assertTrue(interact.banner) self.assertEqual(apps, [app]) class TestPRoutesCommand(unittest.TestCase): @@ -133,6 +191,19 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(result, None) self.assertEqual(L, []) + def test_no_mapper(self): + command = self._makeOne() + command._get_mapper = lambda *arg:None + L = [] + command.out = L.append + app = DummyApp() + loadapp = DummyLoadApp(app) + command.loadapp = (loadapp,) + command.args = ('/foo/bar/myapp.ini', 'myapp') + result = command.command() + self.assertEqual(result, None) + self.assertEqual(L, []) + def test_single_route_no_route_registered(self): command = self._makeOne() route = DummyRoute('a', '/a') diff --git a/pyramid/tests/test_path.py b/pyramid/tests/test_path.py index 5619aafa1..c2261d223 100644 --- a/pyramid/tests/test_path.py +++ b/pyramid/tests/test_path.py @@ -53,7 +53,7 @@ class TestCallerModule(unittest.TestCase): def test_it_level_3(self): from pyramid.tests import test_path result = self._callFUT(3) - self.failIfEqual(result, test_path) + self.assertNotEqual(result, test_path) def test_it_no___name__(self): class DummyFrame(object): @@ -121,7 +121,7 @@ class TestPackagePath(unittest.TestCase): from pyramid.tests import test_path module = DummyPackageOrModule(test_path, raise_exc=TypeError) result = self._callFUT(module) - self.failIf(hasattr(module, '__abspath__')) + self.assertFalse(hasattr(module, '__abspath__')) self.assertEqual(result, module.package_path) class TestPackageOf(unittest.TestCase): diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 70c2c620e..0dbb0d821 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -3,6 +3,18 @@ import unittest from pyramid.testing import cleanUp from pyramid import testing +def hide_warnings(wrapped): + import warnings + def wrapper(*arg, **kw): + warnings.filterwarnings('ignore') + try: + wrapped(*arg, **kw) + finally: + warnings.resetwarnings() + wrapper.__name__ = wrapped.__name__ + wrapper.__doc__ = wrapped.__doc__ + return wrapper + class TestTemplateRendererFactory(unittest.TestCase): def setUp(self): self.config = cleanUp() @@ -46,7 +58,7 @@ class TestTemplateRendererFactory(unittest.TestCase): 'type':'type', }) result = self._callFUT(info, None) - self.failUnless(result is renderer) + self.assertTrue(result is renderer) class TestChameleonRendererLookup(unittest.TestCase): def setUp(self): @@ -190,7 +202,7 @@ class TestChameleonRendererLookup(unittest.TestCase): }) lookup = self._makeOne(None) result = lookup(info) - self.failUnless(result is renderer) + self.assertTrue(result is renderer) def test___call__abspath_notyetregistered(self): import os @@ -221,7 +233,7 @@ class TestChameleonRendererLookup(unittest.TestCase): }) lookup = self._makeOne(None) result = lookup(info) - self.failUnless(renderer is result) + self.assertTrue(renderer is result) def test___call__relpath_has_package_registered(self): renderer = {} @@ -237,7 +249,7 @@ class TestChameleonRendererLookup(unittest.TestCase): }) lookup = self._makeOne(None) result = lookup(info) - self.failUnless(renderer is result) + self.assertTrue(renderer is result) def test___call__spec_notfound(self): spec = 'pyramid.tests:wont/exist' @@ -267,7 +279,7 @@ class TestChameleonRendererLookup(unittest.TestCase): self._registerTemplateRenderer(renderer, spec) lookup = self._makeOne(None) result = lookup(info) - self.failUnless(result is renderer) + self.assertTrue(result is renderer) def test___call__spec_notyetregistered(self): import os @@ -286,11 +298,11 @@ class TestChameleonRendererLookup(unittest.TestCase): }) lookup = self._makeOne(factory) result = lookup(info) - self.failUnless(result is renderer) + self.assertTrue(result is renderer) path = os.path.abspath(__file__).split('$')[0] # jython if path.endswith('.pyc'): # pragma: no cover path = path[:-1] - self.failUnless(factory.path.startswith(path)) + self.assertTrue(factory.path.startswith(path)) self.assertEqual(factory.kw, {}) def test___call__reload_assets_true(self): @@ -312,7 +324,7 @@ class TestChameleonRendererLookup(unittest.TestCase): }) lookup = self._makeOne(factory) result = lookup(info) - self.failUnless(result is renderer) + self.assertTrue(result is renderer) spec = '%s:%s' % ('pyramid.tests', 'test_renderers.py') self.assertEqual(reg.queryUtility(ITemplateRenderer, name=spec), None) @@ -334,7 +346,7 @@ class TestChameleonRendererLookup(unittest.TestCase): }) lookup = self._makeOne(factory) result = lookup(info) - self.failUnless(result is renderer) + self.assertTrue(result is renderer) spec = '%s:%s' % ('pyramid.tests', 'test_renderers.py') self.assertNotEqual(reg.queryUtility(ITemplateRenderer, name=spec), None) @@ -394,6 +406,12 @@ class TestRendererFromName(unittest.TestCase): class Test_json_renderer_factory(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + def _callFUT(self, name): from pyramid.renderers import json_renderer_factory return json_renderer_factory(name) @@ -407,14 +425,14 @@ class Test_json_renderer_factory(unittest.TestCase): request = testing.DummyRequest() renderer = self._callFUT(None) renderer({'a':1}, {'request':request}) - self.assertEqual(request.response_content_type, 'application/json') + self.assertEqual(request.response.content_type, 'application/json') def test_with_request_content_type_set(self): request = testing.DummyRequest() - request.response_content_type = 'text/mishmash' + request.response.content_type = 'text/mishmash' renderer = self._callFUT(None) renderer({'a':1}, {'request':request}) - self.assertEqual(request.response_content_type, 'text/mishmash') + self.assertEqual(request.response.content_type, 'text/mishmash') class Test_string_renderer_factory(unittest.TestCase): def _callFUT(self, name): @@ -443,14 +461,14 @@ class Test_string_renderer_factory(unittest.TestCase): request = testing.DummyRequest() renderer = self._callFUT(None) renderer(None, {'request':request}) - self.assertEqual(request.response_content_type, 'text/plain') + self.assertEqual(request.response.content_type, 'text/plain') def test_with_request_content_type_set(self): request = testing.DummyRequest() - request.response_content_type = 'text/mishmash' + request.response.content_type = 'text/mishmash' renderer = self._callFUT(None) renderer(None, {'request':request}) - self.assertEqual(request.response_content_type, 'text/mishmash') + self.assertEqual(request.response.content_type, 'text/mishmash') class TestRendererHelper(unittest.TestCase): @@ -492,8 +510,15 @@ class TestRendererHelper(unittest.TestCase): name='.foo') return renderer + def _registerResponseFactory(self): + from pyramid.interfaces import IResponseFactory + class ResponseFactory(object): + pass + self.config.registry.registerUtility(ResponseFactory, IResponseFactory) + def test_render_to_response(self): self._registerRendererFactory() + self._registerResponseFactory() request = Dummy() helper = self._makeOne('loo.foo') response = helper.render_to_response('values', 'system_values', @@ -502,6 +527,7 @@ class TestRendererHelper(unittest.TestCase): def test_render_view(self): self._registerRendererFactory() + self._registerResponseFactory() request = Dummy() helper = self._makeOne('loo.foo') view = 'view' @@ -532,7 +558,7 @@ class TestRendererHelper(unittest.TestCase): helper = self._makeOne('loo.foo', registry=reg) result = helper.render('value', {}) self.assertEqual(result, ('value', {})) - self.failUnless(reg.queried) + self.assertTrue(reg.queried) self.assertEqual(reg.event._system, {}) self.assertEqual(reg.event.__class__.__name__, 'BeforeRender') @@ -561,8 +587,43 @@ class TestRendererHelper(unittest.TestCase): result = helper.render('values', None) self.assertEqual(result[1]['a'], 1) + def test__make_response_request_is_None(self): + request = None + helper = self._makeOne('loo.foo') + response = helper._make_response('abc', request) + self.assertEqual(response.body, 'abc') + + def test__make_response_request_is_None_response_factory_exists(self): + self._registerResponseFactory() + request = None + helper = self._makeOne('loo.foo') + response = helper._make_response('abc', request) + self.assertEqual(response.__class__.__name__, 'ResponseFactory') + self.assertEqual(response.body, 'abc') + + def test__make_response_result_is_unicode(self): + from pyramid.response import Response + request = testing.DummyRequest() + request.response = Response() + helper = self._makeOne('loo.foo') + la = unicode('/La Pe\xc3\xb1a', 'utf-8') + response = helper._make_response(la, request) + self.assertEqual(response.body, la.encode('utf-8')) + + def test__make_response_result_is_str(self): + from pyramid.response import Response + request = testing.DummyRequest() + request.response = Response() + helper = self._makeOne('loo.foo') + la = unicode('/La Pe\xc3\xb1a', 'utf-8') + response = helper._make_response(la.encode('utf-8'), request) + self.assertEqual(response.body, la.encode('utf-8')) + + @hide_warnings def test__make_response_with_content_type(self): + from pyramid.response import Response request = testing.DummyRequest() + request.response = Response() attrs = {'response_content_type':'text/nonsense'} request.__dict__.update(attrs) helper = self._makeOne('loo.foo') @@ -570,8 +631,11 @@ class TestRendererHelper(unittest.TestCase): self.assertEqual(response.content_type, 'text/nonsense') self.assertEqual(response.body, 'abc') + @hide_warnings def test__make_response_with_headerlist(self): + from pyramid.response import Response request = testing.DummyRequest() + request.response = Response() attrs = {'response_headerlist':[('a', '1'), ('b', '2')]} request.__dict__.update(attrs) helper = self._makeOne('loo.foo') @@ -583,8 +647,11 @@ class TestRendererHelper(unittest.TestCase): ('b', '2')]) self.assertEqual(response.body, 'abc') + @hide_warnings def test__make_response_with_status(self): + from pyramid.response import Response request = testing.DummyRequest() + request.response = Response() attrs = {'response_status':'406 You Lose'} request.__dict__.update(attrs) helper = self._makeOne('loo.foo') @@ -592,16 +659,22 @@ class TestRendererHelper(unittest.TestCase): self.assertEqual(response.status, '406 You Lose') self.assertEqual(response.body, 'abc') + @hide_warnings def test__make_response_with_charset(self): + from pyramid.response import Response request = testing.DummyRequest() + request.response = Response() attrs = {'response_charset':'UTF-16'} request.__dict__.update(attrs) helper = self._makeOne('loo.foo') response = helper._make_response('abc', request) self.assertEqual(response.charset, 'UTF-16') + @hide_warnings def test__make_response_with_cache_for(self): + from pyramid.response import Response request = testing.DummyRequest() + request.response = Response() attrs = {'response_cache_for':100} request.__dict__.update(attrs) helper = self._makeOne('loo.foo') @@ -611,21 +684,21 @@ class TestRendererHelper(unittest.TestCase): def test_with_alternate_response_factory(self): from pyramid.interfaces import IResponseFactory class ResponseFactory(object): - def __init__(self, result): - self.result = result + def __init__(self): + pass self.config.registry.registerUtility(ResponseFactory, IResponseFactory) request = testing.DummyRequest() helper = self._makeOne('loo.foo') response = helper._make_response('abc', request) self.assertEqual(response.__class__, ResponseFactory) - self.assertEqual(response.result, 'abc') + self.assertEqual(response.body, 'abc') def test__make_response_with_real_request(self): # functional from pyramid.request import Request request = Request({}) - attrs = {'response_status':'406 You Lose'} - request.__dict__.update(attrs) + request.registry = self.config.registry + request.response.status = '406 You Lose' helper = self._makeOne('loo.foo') response = helper._make_response('abc', request) self.assertEqual(response.status, '406 You Lose') diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index 40940cfc9..60d59ece6 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -98,13 +98,13 @@ class TestRequest(unittest.TestCase): def test___contains__(self): environ ={'zooma':1} inst = self._makeOne(environ) - self.failUnless('zooma' in inst) + self.assertTrue('zooma' in inst) def test___delitem__(self): environ = {'zooma':1} inst = self._makeOne(environ) del inst['zooma'] - self.failIf('zooma' in environ) + self.assertFalse('zooma' in environ) def test___getitem__(self): environ = {'zooma':1} @@ -325,6 +325,66 @@ class Test_add_global_response_headers(unittest.TestCase): request.response_callbacks[0](None, response) self.assertEqual(response.headerlist, [('c', 1)] ) +class Test_call_app_with_subpath_as_path_info(unittest.TestCase): + def _callFUT(self, request, app): + from pyramid.request import call_app_with_subpath_as_path_info + return call_app_with_subpath_as_path_info(request, app) + + def test_it_all_request_and_environment_data_missing(self): + request = DummyRequest({}) + response = self._callFUT(request, 'app') + self.assertTrue(request.copied) + self.assertEqual(response, 'app') + self.assertEqual(request.environ['SCRIPT_NAME'], '') + self.assertEqual(request.environ['PATH_INFO'], '/') + + def test_it_with_subpath_and_path_info(self): + request = DummyRequest({'PATH_INFO':'/hello'}) + request.subpath = ('hello',) + response = self._callFUT(request, 'app') + self.assertTrue(request.copied) + self.assertEqual(response, 'app') + self.assertEqual(request.environ['SCRIPT_NAME'], '') + self.assertEqual(request.environ['PATH_INFO'], '/hello') + + def test_it_with_subpath_and_path_info_path_info_endswith_slash(self): + request = DummyRequest({'PATH_INFO':'/hello/'}) + request.subpath = ('hello',) + response = self._callFUT(request, 'app') + self.assertTrue(request.copied) + self.assertEqual(response, 'app') + self.assertEqual(request.environ['SCRIPT_NAME'], '') + self.assertEqual(request.environ['PATH_INFO'], '/hello/') + + def test_it_with_subpath_and_path_info_extra_script_name(self): + request = DummyRequest({'PATH_INFO':'/hello', 'SCRIPT_NAME':'/script'}) + request.subpath = ('hello',) + response = self._callFUT(request, 'app') + self.assertTrue(request.copied) + self.assertEqual(response, 'app') + self.assertEqual(request.environ['SCRIPT_NAME'], '/script') + self.assertEqual(request.environ['PATH_INFO'], '/hello') + + def test_it_with_extra_slashes_in_path_info(self): + request = DummyRequest({'PATH_INFO':'//hello/', + 'SCRIPT_NAME':'/script'}) + request.subpath = ('hello',) + response = self._callFUT(request, 'app') + self.assertTrue(request.copied) + self.assertEqual(response, 'app') + self.assertEqual(request.environ['SCRIPT_NAME'], '/script') + self.assertEqual(request.environ['PATH_INFO'], '/hello/') + + def test_subpath_path_info_and_script_name_have_utf8(self): + la = 'La Pe\xc3\xb1a' + request = DummyRequest({'PATH_INFO':'/'+la, 'SCRIPT_NAME':'/'+la}) + request.subpath = (unicode(la, 'utf-8'), ) + response = self._callFUT(request, 'app') + self.assertTrue(request.copied) + self.assertEqual(response, 'app') + self.assertEqual(request.environ['SCRIPT_NAME'], '/' + la) + self.assertEqual(request.environ['PATH_INFO'], '/' + la) + class DummyRequest: def __init__(self, environ=None): if environ is None: @@ -334,6 +394,13 @@ class DummyRequest: def add_response_callback(self, callback): self.response_callbacks = [callback] + def get_response(self, app): + return app + + def copy(self): + self.copied = True + return self + class DummyResponse: def __init__(self): self.headerlist = [] diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index ffda14738..b869a3830 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -117,8 +117,8 @@ class TestRouter(unittest.TestCase): router = self._makeOne() self.assertEqual(router.debug_notfound, False) self.assertEqual(router.debug_routematch, False) - self.failIf('debug_notfound' in router.__dict__) - self.failIf('debug_routematch' in router.__dict__) + self.assertFalse('debug_notfound' in router.__dict__) + self.assertFalse('debug_routematch' in router.__dict__) def test_root_policy(self): context = DummyContext() @@ -142,8 +142,8 @@ class TestRouter(unittest.TestCase): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(NotFound, router, environ, start_response) - self.failUnless('/' in why[0], why) - self.failIf('debug_notfound' in why[0]) + self.assertTrue('/' in why[0], why) + self.assertFalse('debug_notfound' in why[0]) self.assertEqual(len(logger.messages), 0) def test_traverser_raises_notfound_class(self): @@ -163,7 +163,7 @@ class TestRouter(unittest.TestCase): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(NotFound, router, environ, start_response) - self.failUnless('foo' in why[0], why) + self.assertTrue('foo' in why[0], why) def test_traverser_raises_forbidden_class(self): from pyramid.exceptions import Forbidden @@ -182,7 +182,7 @@ class TestRouter(unittest.TestCase): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(Forbidden, router, environ, start_response) - self.failUnless('foo' in why[0], why) + self.assertTrue('foo' in why[0], why) def test_call_no_view_registered_no_isettings(self): from pyramid.exceptions import NotFound @@ -193,8 +193,8 @@ class TestRouter(unittest.TestCase): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(NotFound, router, environ, start_response) - self.failUnless('/' in why[0], why) - self.failIf('debug_notfound' in why[0]) + self.assertTrue('/' in why[0], why) + self.assertFalse('debug_notfound' in why[0]) self.assertEqual(len(logger.messages), 0) def test_call_no_view_registered_debug_notfound_false(self): @@ -207,8 +207,8 @@ class TestRouter(unittest.TestCase): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(NotFound, router, environ, start_response) - self.failUnless('/' in why[0], why) - self.failIf('debug_notfound' in why[0]) + self.assertTrue('/' in why[0], why) + self.assertFalse('debug_notfound' in why[0]) self.assertEqual(len(logger.messages), 0) def test_call_no_view_registered_debug_notfound_true(self): @@ -221,19 +221,19 @@ class TestRouter(unittest.TestCase): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(NotFound, router, environ, start_response) - self.failUnless( + self.assertTrue( "debug_notfound of url http://localhost:8080/; path_info: '/', " "context:" in why[0]) - self.failUnless("view_name: '', subpath: []" in why[0]) - self.failUnless('http://localhost:8080' in why[0], why) + self.assertTrue("view_name: '', subpath: []" in why[0]) + self.assertTrue('http://localhost:8080' in why[0], why) self.assertEqual(len(logger.messages), 1) message = logger.messages[0] - self.failUnless('of url http://localhost:8080' in message) - self.failUnless("path_info: '/'" in message) - self.failUnless('DummyContext instance at' in message) - self.failUnless("view_name: ''" in message) - self.failUnless("subpath: []" in message) + self.assertTrue('of url http://localhost:8080' in message) + self.assertTrue("path_info: '/'" in message) + self.assertTrue('DummyContext instance at' in message) + self.assertTrue("view_name: ''" in message) + self.assertTrue("subpath: []" in message) def test_call_view_returns_nonresponse(self): from pyramid.interfaces import IViewClassifier @@ -557,7 +557,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(request.matched_route.name, 'foo') self.assertEqual(len(logger.messages), 1) - self.failUnless( + self.assertTrue( logger.messages[0].startswith( "route matched for url http://localhost:8080" "/archives/action1/article1; " @@ -623,7 +623,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(environ['bfg.routes.route'].name, 'foo') self.assertEqual(request.matchdict, matchdict) self.assertEqual(request.matched_route.name, 'foo') - self.failUnless(IFoo.providedBy(request)) + self.assertTrue(IFoo.providedBy(request)) def test_root_factory_raises_notfound(self): from pyramid.interfaces import IRootFactory @@ -641,7 +641,7 @@ class TestRouter(unittest.TestCase): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(NotFound, router, environ, start_response) - self.failUnless('from root factory' in why[0]) + self.assertTrue('from root factory' in why[0]) def test_root_factory_raises_forbidden(self): from pyramid.interfaces import IRootFactory @@ -659,7 +659,7 @@ class TestRouter(unittest.TestCase): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(Forbidden, router, environ, start_response) - self.failUnless('from root factory' in why[0]) + self.assertTrue('from root factory' in why[0]) def test_root_factory_exception_propagating(self): from pyramid.interfaces import IRootFactory diff --git a/pyramid/tests/test_security.py b/pyramid/tests/test_security.py index 94cefa642..86149d554 100644 --- a/pyramid/tests/test_security.py +++ b/pyramid/tests/test_security.py @@ -19,9 +19,9 @@ class TestAllPermissionsList(unittest.TestCase): def test_it(self): thing = self._makeOne() - self.failUnless(thing.__eq__(thing)) + self.assertTrue(thing.__eq__(thing)) self.assertEqual(thing.__iter__(), ()) - self.failUnless('anything' in thing) + self.assertTrue('anything' in thing) def test_singleton(self): from pyramid.security import ALL_PERMISSIONS @@ -40,10 +40,10 @@ class TestAllowed(unittest.TestCase): allowed = self._makeOne('hello') self.assertEqual(allowed.msg, 'hello') self.assertEqual(allowed, True) - self.failUnless(allowed) + self.assertTrue(allowed) self.assertEqual(str(allowed), 'hello') - self.failUnless('<Allowed instance at ' in repr(allowed)) - self.failUnless("with msg 'hello'>" in repr(allowed)) + self.assertTrue('<Allowed instance at ' in repr(allowed)) + self.assertTrue("with msg 'hello'>" in repr(allowed)) class TestDenied(unittest.TestCase): def _getTargetClass(self): @@ -58,10 +58,10 @@ class TestDenied(unittest.TestCase): denied = self._makeOne('hello') self.assertEqual(denied.msg, 'hello') self.assertEqual(denied, False) - self.failIf(denied) + self.assertFalse(denied) self.assertEqual(str(denied), 'hello') - self.failUnless('<Denied instance at ' in repr(denied)) - self.failUnless("with msg 'hello'>" in repr(denied)) + self.assertTrue('<Denied instance at ' in repr(denied)) + self.assertTrue("with msg 'hello'>" in repr(denied)) class TestACLAllowed(unittest.TestCase): def _getTargetClass(self): @@ -76,12 +76,12 @@ class TestACLAllowed(unittest.TestCase): msg = ("ACLAllowed permission 'permission' via ACE 'ace' in ACL 'acl' " "on context 'ctx' for principals 'principals'") allowed = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx') - self.failUnless(msg in allowed.msg) + self.assertTrue(msg in allowed.msg) self.assertEqual(allowed, True) - self.failUnless(allowed) + self.assertTrue(allowed) self.assertEqual(str(allowed), msg) - self.failUnless('<ACLAllowed instance at ' in repr(allowed)) - self.failUnless("with msg %r>" % msg in repr(allowed)) + self.assertTrue('<ACLAllowed instance at ' in repr(allowed)) + self.assertTrue("with msg %r>" % msg in repr(allowed)) class TestACLDenied(unittest.TestCase): def _getTargetClass(self): @@ -96,12 +96,12 @@ class TestACLDenied(unittest.TestCase): msg = ("ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' " "on context 'ctx' for principals 'principals'") denied = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx') - self.failUnless(msg in denied.msg) + self.assertTrue(msg in denied.msg) self.assertEqual(denied, False) - self.failIf(denied) + self.assertFalse(denied) self.assertEqual(str(denied), msg) - self.failUnless('<ACLDenied instance at ' in repr(denied)) - self.failUnless("with msg %r>" % msg in repr(denied)) + self.assertTrue('<ACLDenied instance at ' in repr(denied)) + self.assertTrue("with msg %r>" % msg in repr(denied)) class TestViewExecutionPermitted(unittest.TestCase): def setUp(self): @@ -140,8 +140,8 @@ class TestViewExecutionPermitted(unittest.TestCase): request = DummyRequest({}) result = self._callFUT(context, request, '') msg = result.msg - self.failUnless("Allowed: view name '' in context" in msg) - self.failUnless('(no permission defined)' in msg) + self.assertTrue("Allowed: view name '' in context" in msg) + self.assertTrue('(no permission defined)' in msg) self.assertEqual(result, True) def test_with_permission(self): @@ -156,7 +156,7 @@ class TestViewExecutionPermitted(unittest.TestCase): request = DummyRequest({}) directlyProvides(request, IRequest) result = self._callFUT(context, request, '') - self.failUnless(result is True) + self.assertTrue(result is True) class TestHasPermission(unittest.TestCase): def setUp(self): diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index e8a3318ea..17a437af6 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -54,7 +54,7 @@ class TestUnencryptedCookieSession(unittest.TestCase): session = self._makeOne(request) session['a'] = 1 self.assertEqual(session.invalidate(), None) - self.failIf('a' in session) + self.assertFalse('a' in session) def test__set_cookie_on_exception(self): request = testing.DummyRequest() @@ -116,7 +116,7 @@ class TestUnencryptedCookieSession(unittest.TestCase): cookieval= response.headerlist[0][1] val, domain, path, secure, httponly = [x.strip() for x in cookieval.split(';')] - self.failUnless(val.startswith('abc=')) + self.assertTrue(val.startswith('abc=')) self.assertEqual(domain, 'Domain=localhost') self.assertEqual(path, 'Path=/foo') self.assertEqual(secure, 'secure') @@ -205,14 +205,14 @@ class TestUnencryptedCookieSession(unittest.TestCase): session['_csrft_'] = 'token' token = session.get_csrf_token() self.assertEqual(token, 'token') - self.failUnless('_csrft_' in session) + self.assertTrue('_csrft_' in session) def test_get_csrf_token_new(self): request = testing.DummyRequest() session = self._makeOne(request) token = session.get_csrf_token() - self.failUnless(token) - self.failUnless('_csrft_' in session) + self.assertTrue(token) + self.assertTrue('_csrft_' in session) class Test_manage_accessed(unittest.TestCase): def _makeOne(self, wrapped): @@ -254,7 +254,7 @@ class Test_manage_accessed(unittest.TestCase): response = webob.Response() result = callbacks[0](request, response) self.assertEqual(result, None) - self.failIf('Set-Cookie' in dict(response.headerlist)) + self.assertFalse('Set-Cookie' in dict(response.headerlist)) def test_cookie_is_set(self): request = testing.DummyRequest() diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index acf5a754b..e7506628a 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -46,8 +46,8 @@ class TestPackageURLParser(unittest.TestCase): sr = DummyStartResponse() response = inst(environ, sr) body = response[0] - self.failUnless('301 Moved Permanently' in body) - self.failUnless('http://example.com:6543/' in body) + self.assertTrue('301 Moved Permanently' in body) + self.assertTrue('http://example.com:6543/' in body) def test_path_info_slash_means_index_html(self): environ = self._makeEnviron() @@ -55,7 +55,7 @@ class TestPackageURLParser(unittest.TestCase): sr = DummyStartResponse() response = inst(environ, sr) body = response[0] - self.failUnless('<html>static</html>' in body) + self.assertTrue('<html>static</html>' in body) def test_resource_out_of_bounds(self): environ = self._makeEnviron() @@ -64,8 +64,8 @@ class TestPackageURLParser(unittest.TestCase): sr = DummyStartResponse() response = inst(environ, sr) body = response[0] - self.failUnless('404 Not Found' in body) - self.failUnless('http://example.com:6543/' in body) + self.assertTrue('404 Not Found' in body) + self.assertTrue('http://example.com:6543/' in body) def test_resource_doesnt_exist(self): environ = self._makeEnviron(PATH_INFO='/notthere') @@ -73,8 +73,8 @@ class TestPackageURLParser(unittest.TestCase): sr = DummyStartResponse() response = inst(environ, sr) body = response[0] - self.failUnless('404 Not Found' in body) - self.failUnless('http://example.com:6543/' in body) + self.assertTrue('404 Not Found' in body) + self.assertTrue('http://example.com:6543/' in body) def test_resource_isdir(self): environ = self._makeEnviron(PATH_INFO='/subdir/') @@ -82,7 +82,7 @@ class TestPackageURLParser(unittest.TestCase): sr = DummyStartResponse() response = inst(environ, sr) body = response[0] - self.failUnless('<html>subdir</html>' in body) + self.assertTrue('<html>subdir</html>' in body) def test_resource_is_file(self): environ = self._makeEnviron(PATH_INFO='/index.html') @@ -90,7 +90,15 @@ class TestPackageURLParser(unittest.TestCase): sr = DummyStartResponse() response = inst(environ, sr) body = response[0] - self.failUnless('<html>static</html>' in body) + self.assertTrue('<html>static</html>' in body) + + def test_resource_has_extra_path_info(self): + environ = self._makeEnviron(PATH_INFO='/static/index.html/more') + inst = self._makeOne('pyramid.tests', 'fixtures') + sr = DummyStartResponse() + response = inst(environ, sr) + body = response[0] + self.assertTrue("The trailing path '/more' is not allowed" in body) def test_resource_is_file_with_cache_max_age(self): environ = self._makeEnviron(PATH_INFO='/index.html') @@ -99,7 +107,7 @@ class TestPackageURLParser(unittest.TestCase): sr = DummyStartResponse() response = inst(environ, sr) body = response[0] - self.failUnless('<html>static</html>' in body) + self.assertTrue('<html>static</html>' in body) self.assertEqual(len(sr.headerlist), 8) header_names = [ x[0] for x in sr.headerlist ] header_names.sort() @@ -114,7 +122,7 @@ class TestPackageURLParser(unittest.TestCase): sr = DummyStartResponse() response = inst(environ, sr) body = response[0] - self.failUnless('<html>static</html>' in body) + self.assertTrue('<html>static</html>' in body) self.assertEqual(len(sr.headerlist), 6) header_names = [ x[0] for x in sr.headerlist ] header_names.sort() @@ -122,6 +130,15 @@ class TestPackageURLParser(unittest.TestCase): ['Accept-Ranges', 'Content-Length', 'Content-Range', 'Content-Type', 'ETag', 'Last-Modified']) + def test_with_root_resource(self): + environ = self._makeEnviron(PATH_INFO='/static/index.html') + inst = self._makeOne('pyramid.tests', 'fixtures', + root_resource='fixtures/static') + sr = DummyStartResponse() + response = inst(environ, sr) + body = response[0] + self.assertTrue('<html>static</html>' in body) + def test_if_none_match(self): class DummyEq(object): def __eq__(self, other): @@ -136,10 +153,22 @@ class TestPackageURLParser(unittest.TestCase): self.assertEqual(sr.headerlist[0][0], 'ETag') self.assertEqual(response[0], '') + def test_if_none_match_miss(self): + class DummyEq(object): + def __eq__(self, other): + return False + dummy_eq = DummyEq() + environ = self._makeEnviron(HTTP_IF_NONE_MATCH=dummy_eq) + inst = self._makeOne('pyramid.tests', 'fixtures/static') + sr = DummyStartResponse() + inst(environ, sr) + self.assertEqual(len(sr.headerlist), 6) + self.assertEqual(sr.status, '200 OK') + def test_repr(self): import os.path inst = self._makeOne('pyramid.tests', 'fixtures/static') - self.failUnless( + self.assertTrue( repr(inst).startswith( '<PackageURLParser pyramid.tests:%s at' % os.path.join('fixtures', 'static'))) @@ -150,7 +179,7 @@ class TestPackageURLParser(unittest.TestCase): sr = DummyStartResponse() response = inst.not_found(environ, sr, 'debug_message') body = response[0] - self.failUnless('404 Not Found' in body) + self.assertTrue('404 Not Found' in body) self.assertEqual(sr.status, '404 Not Found') class Test_static_view(unittest.TestCase): @@ -230,6 +259,54 @@ class Test_static_view(unittest.TestCase): self.assertEqual(response.package_name, 'another') self.assertEqual(response.cache_max_age, 3600) + def test_no_subpath_preserves_path_info_and_script_name(self): + view = self._makeOne('fixtures', package_name='another') + context = DummyContext() + request = DummyRequest() + request.subpath = () + request.environ = self._makeEnviron(PATH_INFO='/path_info', + SCRIPT_NAME='/script_name') + view(context, request) + self.assertEqual(request.copied, True) + self.assertEqual(request.environ['PATH_INFO'], '/') + self.assertEqual(request.environ['SCRIPT_NAME'], + '/script_name/path_info') + + def test_with_subpath_path_info_ends_with_slash(self): + view = self._makeOne('fixtures', package_name='another') + context = DummyContext() + request = DummyRequest() + request.subpath = ('subpath',) + request.environ = self._makeEnviron(PATH_INFO='/path_info/subpath/') + view(context, request) + self.assertEqual(request.copied, True) + self.assertEqual(request.environ['PATH_INFO'], '/subpath/') + self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info') + + def test_with_subpath_original_script_name_preserved(self): + view = self._makeOne('fixtures', package_name='another') + context = DummyContext() + request = DummyRequest() + request.subpath = ('subpath',) + request.environ = self._makeEnviron(PATH_INFO='/path_info/subpath/', + SCRIPT_NAME='/scriptname') + view(context, request) + self.assertEqual(request.copied, True) + self.assertEqual(request.environ['PATH_INFO'], '/subpath/') + self.assertEqual(request.environ['SCRIPT_NAME'], + '/scriptname/path_info') + + def test_with_subpath_new_script_name_fixes_trailing_slashes(self): + view = self._makeOne('fixtures', package_name='another') + context = DummyContext() + request = DummyRequest() + request.subpath = ('sub', 'path') + request.environ = self._makeEnviron(PATH_INFO='/path_info//sub//path//') + view(context, request) + self.assertEqual(request.copied, True) + self.assertEqual(request.environ['PATH_INFO'], '/sub/path/') + self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info') + class TestStaticURLInfo(unittest.TestCase): def _getTargetClass(self): from pyramid.static import StaticURLInfo @@ -258,6 +335,14 @@ class TestStaticURLInfo(unittest.TestCase): request = DummyRequest() self.assertRaises(ValueError, inst.generate, 'path', request) + def test_generate_registration_miss(self): + inst = self._makeOne(None) + inst.registrations = [('name', 'spec', False), + ('http://example.com/foo/', 'package:path/',True)] + request = DummyRequest() + result = inst.generate('package:path/abc', request) + self.assertEqual(result, 'http://example.com/foo/abc') + def test_generate_slash_in_name1(self): inst = self._makeOne(None) inst.registrations = [('http://example.com/foo/', 'package:path/',True)] @@ -306,32 +391,83 @@ class TestStaticURLInfo(unittest.TestCase): def test_add_viewname(self): from pyramid.static import static_view - class Config: - def add_route(self, *arg, **kw): - self.arg = arg - self.kw = kw - config = Config() + config = DummyConfig() inst = self._makeOne(config) inst.add('view', 'anotherpackage:path', cache_max_age=1) expected = [('view/', 'anotherpackage:path/', False)] self.assertEqual(inst.registrations, expected) - self.assertEqual(config.arg, ('view/', 'view/*subpath')) - self.assertEqual(config.kw['view_permission'], + self.assertEqual(config.route_args, ('view/', 'view/*subpath')) + self.assertEqual(config.view_kw['permission'], '__no_permission_required__') - self.assertEqual(config.kw['view'].__class__, static_view) - self.assertEqual(config.kw['view'].app.cache_max_age, 1) - self.assertEqual(inst.registrations, expected) + self.assertEqual(config.view_kw['view'].__class__, static_view) + self.assertEqual(config.view_kw['view'].app.cache_max_age, 1) def test_add_viewname_with_permission(self): - class Config: - def add_route(self, *arg, **kw): - self.arg = arg - self.kw = kw - config = Config() + config = DummyConfig() inst = self._makeOne(config) inst.add('view', 'anotherpackage:path', cache_max_age=1, permission='abc') - self.assertEqual(config.kw['view_permission'], 'abc') + self.assertEqual(config.view_kw['permission'], 'abc') + + def test_add_viewname_with_view_permission(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_permission='abc') + self.assertEqual(config.view_kw['permission'], 'abc') + + def test_add_viewname_with_view_context(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_context=DummyContext) + self.assertEqual(config.view_kw['context'], DummyContext) + + def test_add_viewname_with_view_for(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_for=DummyContext) + self.assertEqual(config.view_kw['context'], DummyContext) + + def test_add_viewname_with_for_(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + for_=DummyContext) + self.assertEqual(config.view_kw['context'], DummyContext) + + def test_add_viewname_with_view_renderer(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_renderer='mypackage:templates/index.pt') + self.assertEqual(config.view_kw['renderer'], + 'mypackage:templates/index.pt') + + def test_add_viewname_with_renderer(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + renderer='mypackage:templates/index.pt') + self.assertEqual(config.view_kw['renderer'], + 'mypackage:templates/index.pt') + + def test_add_viewname_with_view_attr(self): + config = DummyConfig() + inst = self._makeOne(config) + inst.add('view', 'anotherpackage:path', cache_max_age=1, + view_attr='attr') + self.assertEqual(config.view_kw['attr'], 'attr') + +class DummyConfig: + def add_route(self, *args, **kw): + self.route_args = args + self.route_kw = kw + + def add_view(self, *args, **kw): + self.view_args = args + self.view_kw = kw class DummyStartResponse: def __call__(self, status, headerlist, exc_info=None): diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py index 99b0a1911..0c94a2803 100644 --- a/pyramid/tests/test_testing.py +++ b/pyramid/tests/test_testing.py @@ -27,7 +27,7 @@ class Test_registerDummySecurityPolicy(TestBase): from pyramid.interfaces import IAuthorizationPolicy ut = self.registry.getUtility(IAuthenticationPolicy) from pyramid.testing import DummySecurityPolicy - self.failUnless(isinstance(ut, DummySecurityPolicy)) + self.assertTrue(isinstance(ut, DummySecurityPolicy)) ut = self.registry.getUtility(IAuthorizationPolicy) self.assertEqual(ut.userid, 'user') self.assertEqual(ut.groupids, ('group1', 'group2')) @@ -65,7 +65,7 @@ class Test_registerTemplateRenderer(TestBase): from pyramid import testing renderer = testing.registerTemplateRenderer('templates/foo') from pyramid.testing import DummyTemplateRenderer - self.failUnless(isinstance(renderer, DummyTemplateRenderer)) + self.assertTrue(isinstance(renderer, DummyTemplateRenderer)) from pyramid.renderers import render_to_response render_to_response('templates/foo', dict(foo=1, bar=2)) renderer.assert_(foo=1) @@ -116,7 +116,7 @@ class Test_registerView(TestBase): from pyramid import testing view = testing.registerView('moo.html') import types - self.failUnless(isinstance(view, types.FunctionType)) + self.assertTrue(isinstance(view, types.FunctionType)) from pyramid.view import render_view_to_response request = DummyRequest() request.registry = self.registry @@ -127,7 +127,7 @@ class Test_registerView(TestBase): from pyramid import testing view = testing.registerView('moo.html', 'yo') import types - self.failUnless(isinstance(view, types.FunctionType)) + self.assertTrue(isinstance(view, types.FunctionType)) from pyramid.view import render_view_to_response request = DummyRequest() request.registry = self.registry @@ -141,7 +141,7 @@ class Test_registerView(TestBase): return Response('123') view = testing.registerView('moo.html', view=view) import types - self.failUnless(isinstance(view, types.FunctionType)) + self.assertTrue(isinstance(view, types.FunctionType)) from pyramid.view import render_view_to_response request = DummyRequest() request.registry = self.registry @@ -156,7 +156,7 @@ class Test_registerView(TestBase): view = testing.registerView('moo.html', view=view, permission='bar') testing.registerDummySecurityPolicy(permissive=False) import types - self.failUnless(isinstance(view, types.FunctionType)) + self.assertTrue(isinstance(view, types.FunctionType)) from pyramid.view import render_view_to_response request = DummyRequest() request.registry = self.registry @@ -171,7 +171,7 @@ class Test_registerView(TestBase): view = testing.registerView('moo.html', view=view, permission='bar') testing.registerDummySecurityPolicy(permissive=False) import types - self.failUnless(isinstance(view, types.FunctionType)) + self.assertTrue(isinstance(view, types.FunctionType)) result = view_execution_permitted(None, None, 'moo.html') self.assertEqual(result, False) @@ -183,7 +183,7 @@ class Test_registerView(TestBase): view = testing.registerView('moo.html', view=view, permission='bar') testing.registerDummySecurityPolicy(permissive=True) import types - self.failUnless(isinstance(view, types.FunctionType)) + self.assertTrue(isinstance(view, types.FunctionType)) from pyramid.view import render_view_to_response request = DummyRequest() request.registry = self.registry @@ -355,9 +355,9 @@ class TestDummyResource(unittest.TestCase): self.assertEqual(resource['abc'], dummy) self.assertEqual(resource.get('abc'), dummy) self.assertRaises(KeyError, resource.__getitem__, 'none') - self.failUnless('abc' in resource) + self.assertTrue('abc' in resource) del resource['abc'] - self.failIf('abc' in resource) + self.assertFalse('abc' in resource) self.assertEqual(resource.get('abc', 'foo'), 'foo') self.assertEqual(resource.get('abc'), None) @@ -390,7 +390,7 @@ class TestDummyResource(unittest.TestCase): def test_ctor_with__provides__(self): resource = self._makeOne(__provides__=IDummy) - self.failUnless(IDummy.providedBy(resource)) + self.assertTrue(IDummy.providedBy(resource)) class TestDummyRequest(unittest.TestCase): def _getTargetClass(self): @@ -498,10 +498,55 @@ class TestDummyRequest(unittest.TestCase): registry = Registry('this_test') config = Configurator(registry=registry) config.begin() - self.failUnless(request.registry is registry) + self.assertTrue(request.registry is registry) finally: config.end() + def test_set_registry(self): + request = self._makeOne() + request.registry = 'abc' + self.assertEqual(request.registry, 'abc') + + def test_del_registry(self): + # see https://github.com/Pylons/pyramid/issues/165 + from pyramid.registry import Registry + from pyramid.config import Configurator + request = self._makeOne() + request.registry = 'abc' + self.assertEqual(request.registry, 'abc') + del request.registry + try: + registry = Registry('this_test') + config = Configurator(registry=registry) + config.begin() + self.assertTrue(request.registry is registry) + finally: + config.end() + + def test_response_with_responsefactory(self): + from pyramid.registry import Registry + from pyramid.interfaces import IResponseFactory + registry = Registry('this_test') + class ResponseFactory(object): + pass + registry.registerUtility(ResponseFactory, IResponseFactory) + request = self._makeOne() + request.registry = registry + resp = request.response + self.assertEqual(resp.__class__, ResponseFactory) + self.assertTrue(request.response is resp) # reified + + def test_response_without_responsefactory(self): + from pyramid.registry import Registry + from pyramid.response import Response + registry = Registry('this_test') + request = self._makeOne() + request.registry = registry + resp = request.response + self.assertEqual(resp.__class__, Response) + self.assertTrue(request.response is resp) # reified + + class TestDummyTemplateRenderer(unittest.TestCase): def _getTargetClass(self, ): from pyramid.testing import DummyTemplateRenderer @@ -528,7 +573,7 @@ class TestDummyTemplateRenderer(unittest.TestCase): renderer({'a':1, 'b':2}) self.assertRaises(AssertionError, renderer.assert_, c=1) self.assertRaises(AssertionError, renderer.assert_, b=3) - self.failUnless(renderer.assert_(a=1, b=2)) + self.assertTrue(renderer.assert_(a=1, b=2)) def test_nondefault_string_response(self): renderer = self._makeOne('abc') @@ -550,7 +595,7 @@ class Test_setUp(unittest.TestCase): try: config = self._callFUT() current = manager.get() - self.failIf(current is old) + self.assertFalse(current is old) self.assertEqual(config.registry, current['registry']) self.assertEqual(current['registry'].__class__, Registry) self.assertEqual(current['request'], None) @@ -593,7 +638,7 @@ class Test_setUp(unittest.TestCase): try: self._callFUT(registry=registry, hook_zca=False) sm = getSiteManager() - self.failIf(sm is registry) + self.assertFalse(sm is registry) finally: getSiteManager.reset() manager.clear() @@ -780,7 +825,7 @@ class TestDummySession(unittest.TestCase): session = self._makeOne() session['a'] = 1 self.assertEqual(session.invalidate(), None) - self.failIf('a' in session) + self.assertFalse('a' in session) def test_flash_default(self): session = self._makeOne() @@ -838,7 +883,7 @@ class TestDummySession(unittest.TestCase): session['_csrft_'] = 'token' token = session.get_csrf_token() self.assertEqual(token, 'token') - self.failUnless('_csrft_' in session) + self.assertTrue('_csrft_' in session) from zope.interface import Interface diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py index 5be04478f..6e1474b1d 100644 --- a/pyramid/tests/test_urldispatch.py +++ b/pyramid/tests/test_urldispatch.py @@ -21,8 +21,8 @@ class TestRoute(unittest.TestCase): self.assertEqual(route.path, ':path') self.assertEqual(route.name, 'name') self.assertEqual(route.factory, 'factory') - self.failUnless(route.generate.__class__ is types.FunctionType) - self.failUnless(route.match.__class__ is types.FunctionType) + self.assertTrue(route.generate.__class__ is types.FunctionType) + self.assertTrue(route.match.__class__ is types.FunctionType) def test_ctor_defaults(self): import types @@ -31,8 +31,8 @@ class TestRoute(unittest.TestCase): self.assertEqual(route.path, ':path') self.assertEqual(route.name, 'name') self.assertEqual(route.factory, None) - self.failUnless(route.generate.__class__ is types.FunctionType) - self.failUnless(route.match.__class__ is types.FunctionType) + self.assertTrue(route.generate.__class__ is types.FunctionType) + self.assertTrue(route.match.__class__ is types.FunctionType) def test_match(self): route = self._makeOne('name', ':path') @@ -288,20 +288,28 @@ class TestCompileRoute(unittest.TestCase): {'baz':'abc', 'buz':'buz'}) self.assertEqual(generator({'baz':1, 'buz':2}), '/foo/1/biz/2/bar') - # XXX reenable after torturous_route_re replacement is found for - # Jython - ## def test_custom_regex_with_embedded_squigglies(self): - ## matcher, generator = self._callFUT('/{buz:\d{4}}') - ## self.assertEqual(matcher('/2001'), {'buz':'2001'}) - ## self.assertEqual(matcher('/200'), None) - ## self.assertEqual(generator({'buz':2001}), '/2001') - - ## def test_custom_regex_with_embedded_squigglies2(self): - ## matcher, generator = self._callFUT('/{buz:\d{3,4}}') - ## self.assertEqual(matcher('/2001'), {'buz':'2001'}) - ## self.assertEqual(matcher('/200'), {'buz':'200'}) - ## self.assertEqual(matcher('/20'), None) - ## self.assertEqual(generator({'buz':2001}), '/2001') + def test_custom_regex_with_embedded_squigglies(self): + matcher, generator = self._callFUT('/{buz:\d{4}}') + self.assertEqual(matcher('/2001'), {'buz':'2001'}) + self.assertEqual(matcher('/200'), None) + self.assertEqual(generator({'buz':2001}), '/2001') + + def test_custom_regex_with_embedded_squigglies2(self): + matcher, generator = self._callFUT('/{buz:\d{3,4}}') + self.assertEqual(matcher('/2001'), {'buz':'2001'}) + self.assertEqual(matcher('/200'), {'buz':'200'}) + self.assertEqual(matcher('/20'), None) + self.assertEqual(generator({'buz':2001}), '/2001') + + def test_custom_regex_with_embedded_squigglies3(self): + matcher, generator = self._callFUT('/{buz:(\d{2}|\d{4})-[a-zA-Z]{3,4}-\d{2}}') + self.assertEqual(matcher('/2001-Nov-15'), {'buz':'2001-Nov-15'}) + self.assertEqual(matcher('/99-June-10'), {'buz':'99-June-10'}) + self.assertEqual(matcher('/2-Nov-15'), None) + self.assertEqual(matcher('/200-Nov-15'), None) + self.assertEqual(matcher('/2001-No-15'), None) + self.assertEqual(generator({'buz':'2001-Nov-15'}), '/2001-Nov-15') + self.assertEqual(generator({'buz':'99-June-10'}), '/99-June-10') class TestCompileRouteMatchFunctional(unittest.TestCase): def matches(self, pattern, path, expected): diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 826fc4290..b1d48b98b 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -267,7 +267,7 @@ class TestViewConfigDecorator(unittest.TestCase): decorator.venusian = venusian def foo(): pass wrapped = decorator(foo) - self.failUnless(wrapped is foo) + self.assertTrue(wrapped is foo) settings = call_venusian(venusian) self.assertEqual(len(settings), 1) self.assertEqual(settings[0]['permission'], None) @@ -281,7 +281,7 @@ class TestViewConfigDecorator(unittest.TestCase): decorator.venusian.info.scope = 'class' class foo(object): pass wrapped = decorator(foo) - self.failUnless(wrapped is foo) + self.assertTrue(wrapped is foo) settings = call_venusian(venusian) self.assertEqual(len(settings), 1) self.assertEqual(settings[0]['permission'], None) @@ -296,7 +296,7 @@ class TestViewConfigDecorator(unittest.TestCase): decorator.venusian.info.scope = 'class' class foo(object): pass wrapped = decorator(foo) - self.failUnless(wrapped is foo) + self.assertTrue(wrapped is foo) settings = call_venusian(venusian) self.assertEqual(len(settings), 1) self.assertEqual(settings[0]['permission'], None) @@ -314,8 +314,8 @@ class TestViewConfigDecorator(unittest.TestCase): def foo(): pass wrapped1 = decorator1(foo) wrapped2 = decorator2(wrapped1) - self.failUnless(wrapped1 is foo) - self.failUnless(wrapped2 is foo) + self.assertTrue(wrapped1 is foo) + self.assertTrue(wrapped2 is foo) settings1 = call_venusian(venusian1) self.assertEqual(len(settings1), 1) self.assertEqual(settings1[0]['name'], '1') @@ -344,7 +344,7 @@ class TestViewConfigDecorator(unittest.TestCase): decorator.venusian = venusian def foo(context, request): pass decorated = decorator(foo) - self.failUnless(decorated is foo) + self.assertTrue(decorated is foo) settings = call_venusian(venusian) self.assertEqual(settings[0]['custom_predicates'], (1,)) @@ -355,7 +355,7 @@ class TestViewConfigDecorator(unittest.TestCase): decorator.venusian = venusian def foo(): pass wrapped = decorator(foo) - self.failUnless(wrapped is foo) + self.assertTrue(wrapped is foo) settings = call_venusian(venusian) self.assertEqual(len(settings), 1) renderer = settings[0]['renderer'] @@ -369,7 +369,7 @@ class TestViewConfigDecorator(unittest.TestCase): decorator.venusian = venusian def foo(): pass wrapped = decorator(foo) - self.failUnless(wrapped is foo) + self.assertTrue(wrapped is foo) settings = call_venusian(venusian) self.assertEqual(len(settings), 1) self.assertEqual(settings[0]['renderer'], {'a':1}) @@ -477,20 +477,20 @@ class Test_default_exceptionresponse_view(unittest.TestCase): def test_is_exception(self): context = Exception() result = self._callFUT(context, None) - self.failUnless(result is context) + self.assertTrue(result is context) def test_is_not_exception_context_is_false_still_chose(self): request = DummyRequest() request.exception = 0 result = self._callFUT(None, request) - self.failUnless(result is None) + self.assertTrue(result is None) def test_is_not_exception_no_request_exception(self): context = object() request = DummyRequest() request.exception = None result = self._callFUT(context, request) - self.failUnless(result is context) + self.assertTrue(result is context) def test_is_not_exception_request_exception(self): context = object() diff --git a/pyramid/tests/test_wsgi.py b/pyramid/tests/test_wsgi.py index f63667352..06bcf1cb2 100644 --- a/pyramid/tests/test_wsgi.py +++ b/pyramid/tests/test_wsgi.py @@ -20,11 +20,9 @@ class WSGIApp2Tests(unittest.TestCase): def test_decorator_with_subpath_and_view_name(self): context = DummyContext() request = DummyRequest() - request.traversed = ['a', 'b'] - request.virtual_root_path = ['a'] - request.subpath = ['subpath'] - request.view_name = 'view_name' - request.environ = {'SCRIPT_NAME':'/foo'} + request.subpath = ('subpath',) + request.environ = {'SCRIPT_NAME':'/foo', + 'PATH_INFO':'/b/view_name/subpath'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) @@ -34,11 +32,8 @@ class WSGIApp2Tests(unittest.TestCase): def test_decorator_with_subpath_no_view_name(self): context = DummyContext() request = DummyRequest() - request.traversed = ['a', 'b'] - request.virtual_root_path = ['a'] - request.subpath = ['subpath'] - request.view_name = '' - request.environ = {'SCRIPT_NAME':'/foo'} + request.subpath = ('subpath',) + request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/b/subpath'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) @@ -48,11 +43,8 @@ class WSGIApp2Tests(unittest.TestCase): def test_decorator_no_subpath_with_view_name(self): context = DummyContext() request = DummyRequest() - request.traversed = ['a', 'b'] - request.virtual_root_path = ['a'] - request.subpath = [] - request.view_name = 'view_name' - request.environ = {'SCRIPT_NAME':'/foo'} + request.subpath = () + request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/b/view_name'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) @@ -62,11 +54,8 @@ class WSGIApp2Tests(unittest.TestCase): def test_decorator_traversed_empty_with_view_name(self): context = DummyContext() request = DummyRequest() - request.traversed = [] - request.virtual_root_path = [] - request.subpath = [] - request.view_name = 'view_name' - request.environ = {'SCRIPT_NAME':'/foo'} + request.subpath = () + request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/view_name'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) @@ -76,11 +65,8 @@ class WSGIApp2Tests(unittest.TestCase): def test_decorator_traversed_empty_no_view_name(self): context = DummyContext() request = DummyRequest() - request.traversed = [] - request.virtual_root_path = [] - request.subpath = [] - request.view_name = '' - request.environ = {'SCRIPT_NAME':'/foo'} + request.subpath = () + request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) @@ -90,11 +76,8 @@ class WSGIApp2Tests(unittest.TestCase): def test_decorator_traversed_empty_no_view_name_no_script_name(self): context = DummyContext() request = DummyRequest() - request.traversed = [] - request.virtual_root_path = [] - request.subpath = [] - request.view_name = '' - request.environ = {'SCRIPT_NAME':''} + request.subpath = () + request.environ = {'SCRIPT_NAME':'', 'PATH_INFO':'/'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) @@ -110,3 +93,8 @@ class DummyContext: class DummyRequest: def get_response(self, application): return application + + def copy(self): + self.copied = True + return self + diff --git a/pyramid/tests/wsgiapp2app/__init__.py b/pyramid/tests/wsgiapp2app/__init__.py new file mode 100644 index 000000000..0880556ef --- /dev/null +++ b/pyramid/tests/wsgiapp2app/__init__.py @@ -0,0 +1,17 @@ +from pyramid.view import view_config +from pyramid.wsgi import wsgiapp2 + +@view_config(name='hello', renderer='string') +@wsgiapp2 +def hello(environ, start_response): + assert environ['PATH_INFO'] == '/' + assert environ['SCRIPT_NAME'] == '/hello' + response_headers = [('Content-Type', 'text/plain')] + start_response('200 OK', response_headers) + return ['Hello!'] + +def main(): + from pyramid.config import Configurator + c = Configurator() + c.scan() + return c diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py index be737201b..989cc62b9 100644 --- a/pyramid/urldispatch.py +++ b/pyramid/urldispatch.py @@ -77,20 +77,11 @@ class RoutesMapper(object): old_route_re = re.compile(r'(\:[a-zA-Z]\w*)') star_in_brackets = re.compile(r'\{[^\}]*\*\w*[^\}]*\}') -# The regex named ``torturous_route_re`` below allows us to support at least -# one level of "inner" squigglies inside the expr of a {name:expr} pattern; -# for example, {foo:\d{4}}. Thanks to Zart for the regex help. -# ``torturous_route_re`` is meant to be a replacement for the regex named -# ``route_re`` (which is just (\{[a-zA-Z][^\}]*\})) because ``route_re`` -# chokes when it encounters inner squigglies. However -# http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5050507 means that the -# torturous regex doesn't work on Jython (recursion error), so we've disabled -# it in favor of ``route_re`` for now. If somebody can make something that -# will work on Jython but also match inner squigglies, it'd be useful. - -# torturous_route_re = re.compile(r'(\{[a-zA-Z](?:\{[^\}]*\}|[^\{\}]*)*\})') - -route_re = re.compile(r'(\{[a-zA-Z][^\}]*\})') +# The torturous nature of the regex named ``route_re`` below is due to the +# fact that we need to support at least one level of "inner" squigglies +# inside the expr of a {name:expr} pattern. This regex used to be just +# (\{[a-zA-Z][^\}]*\}) but that choked when supplied with e.g. {foo:\d{4}}. +route_re = re.compile(r'(\{[a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})') def update_pattern(matchobj): name = matchobj.group(0) diff --git a/pyramid/wsgi.py b/pyramid/wsgi.py index e988a000e..e4c61ff63 100644 --- a/pyramid/wsgi.py +++ b/pyramid/wsgi.py @@ -1,5 +1,5 @@ from pyramid.compat import wraps -from pyramid.traversal import quote_path_segment +from pyramid.request import call_app_with_subpath_as_path_info def wsgiapp(wrapped): """ Decorator to turn a WSGI application into a :app:`Pyramid` @@ -31,7 +31,7 @@ def wsgiapp(wrapped): """ def decorator(context, request): return request.get_response(wrapped) - return wraps(wrapped)(decorator) # grokkability + return wraps(wrapped)(decorator) def wsgiapp2(wrapped): """ Decorator to turn a WSGI application into a :app:`Pyramid` @@ -56,31 +56,15 @@ def wsgiapp2(wrapped): config.add_view(hello_world, name='hello_world.txt') The ``wsgiapp2`` decorator will convert the result of the WSGI - application to a Response and return it to :app:`Pyramid` as if - the WSGI app were a :app:`Pyramid` view. The ``SCRIPT_NAME`` - and ``PATH_INFO`` values present in the WSGI environment are fixed - up before the application is invoked. """ + application to a Response and return it to :app:`Pyramid` as if the WSGI + app were a :app:`Pyramid` view. The ``SCRIPT_NAME`` and ``PATH_INFO`` + values present in the WSGI environment are fixed up before the + application is invoked. In particular, a new WSGI environment is + generated, and the :term:`subpath` of the request passed to ``wsgiapp2`` + is used as the new request's ``PATH_INFO`` and everything preceding the + subpath is used as the ``SCRIPT_NAME``. The new environment is passed to + the downstream WSGI application.""" def decorator(context, request): - traversed = request.traversed - vroot_path = request.virtual_root_path - if not vroot_path: - vroot_path = () - view_name = request.view_name - subpath = request.subpath - if not subpath: - subpath = () - script_tuple = traversed[len(vroot_path):] - script_list = [ quote_path_segment(name) for name in script_tuple ] - if view_name: - script_list.append(quote_path_segment(view_name)) - script_name = '/' + '/'.join(script_list) - path_list = [ quote_path_segment(name) for name in subpath ] - path_info = '/' + '/'.join(path_list) - request.environ['PATH_INFO'] = path_info - script_name = request.environ['SCRIPT_NAME'] + script_name - if script_name.endswith('/'): - script_name = script_name[:-1] - request.environ['SCRIPT_NAME'] = script_name - return request.get_response(wrapped) - return wraps(wrapped)(decorator) # grokkability + return call_app_with_subpath_as_path_info(request, wrapped) + return wraps(wrapped)(decorator) @@ -31,7 +31,7 @@ install_requires=[ 'Paste > 1.7', # temp version pin to prevent PyPi install failure :-( 'PasteDeploy', 'PasteScript', - 'WebOb >= 1.0', # no "default_charset" + 'WebOb >= 1.0.2', # no "default_charset"; request.script_name doesnt error 'repoze.lru', 'setuptools', 'zope.component >= 3.6.0', # independent of zope.hookable @@ -53,7 +53,7 @@ if sys.version_info[:2] < (2, 6): install_requires.append('simplejson') setup(name='pyramid', - version='1.0', + version='1.1a0', description=('The Pyramid web application development framework, a ' 'Pylons project'), long_description=README + '\n\n' + CHANGES, @@ -1,19 +1,27 @@ [tox] envlist = - py25,py26,py27,jython,pypy,cover + py24,py25,py26,py27,jython,pypy,cover [testenv] commands = python setup.py test -q -# if we ever want to run the paster template tests, we could also use: -# python template_tests.py -# they take forever, though deps = Sphinx WebTest repoze.sphinx.autointerface virtualenv +[testenv:py24] +# Chameleon 2 doesnt work under py2.4 +commands = + python setup.py test -q +deps = + Sphinx + WebTest + repoze.sphinx.autointerface + virtualenv + Chameleon<=1.999 + [testenv:jython] commands = jython setup.py test -q |
