diff options
43 files changed, 1297 insertions, 223 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index b60600198..6c21e7298 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -65,6 +65,27 @@ Features via ``request.static_url('myapp:static/foo.png')``. See https://github.com/Pylons/pyramid/issues/1252 +- Added ``pyramid.config.Configurator.set_response_factory`` and the + ``response_factory`` keyword argument to the ``Configurator`` for defining + a factory that will return a custom ``Response`` class. + See https://github.com/Pylons/pyramid/pull/1499 + +- Allow an iterator to be returned from a renderer. Previously it was only + possible to return bytes or unicode. + See https://github.com/Pylons/pyramid/pull/1417 + +- ``pserve`` can now take a ``-b`` or ``--browser`` option to open the server + URL in a web browser. See https://github.com/Pylons/pyramid/pull/1533 + +- Overall improvments for the ``proutes`` command. Added ``--format`` and + ``--glob`` arguments to the command, introduced the ``method`` + column for displaying available request methods, and improved the ``view`` + output by showing the module instead of just ``__repr__``. + See https://github.com/Pylons/pyramid/pull/1488 + +- Support keyword-only arguments and function annotations in views in + Python 3. See https://github.com/Pylons/pyramid/pull/1556 + Bug Fixes --------- @@ -105,6 +126,11 @@ Bug Fixes - Fix route generation for static view asset specifications having no path. See https://github.com/Pylons/pyramid/pull/1377 +- Allow the ``pyramid.renderers.JSONP`` renderer to work even if there is no + valid request object. In this case it will not wrap the object in a + callback and thus behave just like the ``pyramid.renderers.JSON` renderer. + See https://github.com/Pylons/pyramid/pull/1561 + Deprecations ------------ @@ -115,6 +141,13 @@ Deprecations Docs ---- +- Moved the documentation for ``accept`` on ``Configurator.add_view`` to no + longer be part of the predicate list. See + https://github.com/Pylons/pyramid/issues/1391 for a bug report stating + ``not_`` was failing on ``accept``. Discussion with @mcdonc led to the + conclusion that it should not be documented as a predicate. + See https://github.com/Pylons/pyramid/pull/1487 for this PR + - Removed logging configuration from Quick Tutorial ini files except for scaffolding- and logging-related chapters to avoid needing to explain it too early. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index e4132cda5..adf2224a5 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -238,3 +238,5 @@ Contributors - Hugo Branquinho, 2014/11/25 - Adrian Teng, 2014/12/17 + +- Ilja Everila, 2015/02/05 diff --git a/HISTORY.txt b/HISTORY.txt index 6aad221a8..242568e98 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1327,7 +1327,7 @@ Bug Fixes - Make test suite pass on 32-bit systems; closes #286. closes #306. See also https://github.com/Pylons/pyramid/issues/286 -- The ``pryamid.view.view_config`` decorator did not accept a ``match_params`` +- The ``pyramid.view.view_config`` decorator did not accept a ``match_params`` predicate argument. See https://github.com/Pylons/pyramid/pull/308 - The AuthTktCookieHelper could potentially generate Unicode headers diff --git a/docs/_themes b/docs/_themes -Subproject 3bec9280a6cedb15e97e5899021aa8d723c2538 +Subproject b14bf8c2a0d95ae8e3d38d07ad3721370ae6f3f diff --git a/docs/glossary.rst b/docs/glossary.rst index 01300a0be..911c22075 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -16,6 +16,10 @@ Glossary An object which, provided a :term:`WSGI` environment as a single positional argument, returns a Pyramid-compatible request. + response factory + An object which, provided a :term:`request` as a single positional + argument, returns a Pyramid-compatible response. + response An object returned by a :term:`view callable` that represents response data returned to the requesting user agent. It must implement the diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 4f16617c4..1fe2d9278 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -312,24 +312,60 @@ For example: :linenos: $ $VENV/bin/proutes development.ini - Name Pattern View - ---- ------- ---- - home / <function my_view> - home2 / <function my_view> - another /another None - static/ static/*subpath <static_view object> - catchall /*subpath <function static_view> - -``proutes`` generates a table with three columns: *Name*, *Pattern*, + Name Pattern View + ---- ------- ---- + debugtoolbar /_debug_toolbar/*subpath <wsgiapp> * + __static/ /static/*subpath dummy_starter:static/ * + __static2/ /static2/*subpath /var/www/static/ * + __pdt_images/ /pdt_images/*subpath pyramid_debugtoolbar:static/img/ * + a / <unknown> * + no_view_attached / <unknown> * + route_and_view_attached / app1.standard_views.route_and_view_attached * + method_conflicts /conflicts app1.standard_conflicts <route mismatch> + multiview /multiview app1.standard_views.multiview GET,PATCH + not_post /not_post app1.standard_views.multview !POST,* + +``proutes`` generates a table with four columns: *Name*, *Pattern*, *Method*, and *View*. The items listed in the Name column are route names, the items listed in the Pattern column are route patterns, and the items listed in the View column are representations of the view callable that will be invoked when a request matches the associated -route pattern. The view column may show ``None`` if no associated view +route pattern. The view column may show ``<unknown>`` if no associated view callable could be found. If no routes are configured within your application, nothing will be printed to the console when ``proutes`` is executed. +It is convenient when using the ``proutes`` often to configure which columns +and the order you would like to view them. To facilitate this, ``proutes`` will +look for a special ``[proutes]`` section in your INI file and use those as +defaults. + +For example you may remove request method and place the view first: + +.. code-block:: text + :linenos: + + [proutes] + format = view + name + pattern + +You can also separate the formats with commas or spaces: + +.. code-block:: text + :linenos: + + [proutes] + format = view name pattern + + [proutes] + format = view, name, pattern + +If you want to temporarily configure the columns and order there is the +``--format`` which is a comma separated list of columns you want to include. The +current available formats are ``name``, ``pattern``, ``view``, and ``method``. + + .. index:: pair: tweens; printing single: ptweens diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 4da36e730..17cae2c67 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -349,6 +349,52 @@ We attach and cache an object named ``extra`` to the ``request`` object. the property .. index:: + single: response factory + +.. _changing_the_response_factory: + +Changing the Response Factory +------------------------------- + +.. versionadded:: 1.6 + +Whenever :app:`Pyramid` returns a response from a view it creates a +:term:`response` object. By default, an instance of the +:class:`pyramid.response.Response` class is created to represent the response +object. + +The factory that :app:`Pyramid` uses to create a response object instance can be +changed by passing a ``response_factory`` argument to the constructor of the +:term:`configurator`. This argument can be either a callable or a +:term:`dotted Python name` representing a callable. + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + class MyResponse(Response): + pass + + config = Configurator(response_factory=lambda r: MyResponse()) + +If you're doing imperative configuration, and you'd rather do it after you've +already constructed a :term:`configurator` it can also be registered via the +:meth:`pyramid.config.Configurator.set_response_factory` method: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + from pyramid.response import Response + + class MyResponse(Response): + pass + + config = Configurator() + config.set_response_factory(lambda r: MyResponse()) + +.. index:: single: before render event single: adding renderer globals @@ -730,7 +776,7 @@ If you want to implement your own Response object instead of using the :class:`pyramid.response.Response` object in any capacity at all, you'll have to make sure the object implements every attribute and method outlined in :class:`pyramid.interfaces.IResponse` and you'll have to ensure that it uses -``zope.interface.implementer(IResponse)`` as a class decoratoror. +``zope.interface.implementer(IResponse)`` as a class decorator. .. code-block:: python :linenos: diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index 4a3258d35..1c324d22b 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -453,7 +453,7 @@ commonly in route declarations that look like this: .. code-block:: python :linenos: - from pryamid.static import static_view + from pyramid.static import static_view www = static_view('mypackage:static', use_subpath=True) diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst index a7bde4cf7..8caba522c 100644 --- a/docs/narr/introspector.rst +++ b/docs/narr/introspector.rst @@ -121,7 +121,7 @@ introspectables in categories not described here. ``subscriber`` The subscriber callable object (the resolution of the ``subscriber`` - argument passed to ``add_susbcriber``). + argument passed to ``add_subscriber``). ``interfaces`` @@ -137,12 +137,12 @@ introspectables in categories not described here. ``predicates`` The predicate objects created as the result of passing predicate arguments - to ``add_susbcriber`` + to ``add_subscriber`` ``derived_predicates`` Wrappers around the predicate objects created as the result of passing - predicate arguments to ``add_susbcriber`` (to be used when predicates take + predicate arguments to ``add_subscriber`` (to be used when predicates take only one value but must be passed more than one). ``response adapters`` @@ -450,9 +450,9 @@ introspectables in categories not described here. The :class:`pyramid.interfaces.IRendererInfo` object which represents this template's renderer. -``view mapper`` +``view mappers`` - Each introspectable in the ``permissions`` category represents a call to + Each introspectable in the ``view mappers`` category represents a call to :meth:`pyramid.config.Configurator.add_view` that has an explicit ``mapper`` argument to *or* a call to :meth:`pyramid.config.Configurator.set_view_mapper`; each will have @@ -481,8 +481,8 @@ introspectables in categories not described here. ``translation directories`` - Each introspectable in the ``asset overrides`` category represents an - individual element in a ``specs`` argument passed to + Each introspectable in the ``translation directories`` category represents + an individual element in a ``specs`` argument passed to :meth:`pyramid.config.Configurator.add_translation_dirs`; each will have the following data. @@ -511,7 +511,7 @@ introspectables in categories not described here. ``type`` - ``implict`` or ``explicit`` as a string. + ``implicit`` or ``explicit`` as a string. ``under`` diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index c16673ae6..921883091 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -254,16 +254,15 @@ level unless they're explicitly set differently. Meaning the ``myapp.views``, ``myapp.models`` (and all your app's modules') loggers by default have an effective level of ``DEBUG`` too. -For more advanced filtering, the logging module provides a `Filter -<http://docs.python.org/lib/node423.html>`_ object; however it cannot be used -directly from the configuration file. +For more advanced filtering, the logging module provides a +:class:`logging.Filter` object; however it cannot be used directly from the +configuration file. -Advanced Configuration +Advanced Configuration ---------------------- -To capture log output to a separate file, use a `FileHandler -<http://docs.python.org/lib/node412.html>`_ (or a `RotatingFileHandler -<http://docs.python.org/lib/node413.html>`_): +To capture log output to a separate file, use :class:`logging.FileHandler` (or +:class:`logging.handlers.RotatingFileHandler`): .. code-block:: ini @@ -317,8 +316,9 @@ output, etc., but not web traffic. For web traffic logging Paste provides the :term:`middleware`. TransLogger produces logs in the `Apache Combined Log Format <http://httpd.apache.org/docs/2.2/logs.html#combined>`_. But TransLogger does not write to files, the Python logging system must be -configured to do this. The Python FileHandler_ logging handler can be used -alongside TransLogger to create an ``access.log`` file similar to Apache's. +configured to do this. The Python :class:`logging.FileHandler` logging +handler can be used alongside TransLogger to create an ``access.log`` file +similar to Apache's. Like any standard :term:`middleware` with a Paste entry point, TransLogger can be configured to wrap your application using ``.ini`` file syntax. First, diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 8da743a01..5c103405a 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -44,7 +44,7 @@ It is digitally signed, however, and thus its data cannot easily be tampered with. You can configure this session factory in your :app:`Pyramid` application -by using the :meth:`pyramid.config.Configurator.set_session_factory`` method. +by using the :meth:`pyramid.config.Configurator.set_session_factory` method. .. code-block:: python :linenos: @@ -380,7 +380,7 @@ Checking CSRF Tokens Manually ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In request handling code, you can check the presence and validity of a CSRF -token with :func:`pyramid.session.check_csrf_token(request)``. If the token is +token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally, you can specify ``raises=False`` to have the check return ``False`` instead of raising an exception. diff --git a/pyramid/compat.py b/pyramid/compat.py index 301984749..0b0d1a584 100644 --- a/pyramid/compat.py +++ b/pyramid/compat.py @@ -244,6 +244,12 @@ else: def is_bound_method(ob): return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None +# support annotations and keyword-only arguments in PY3 +if PY3: # pragma: no cover + from inspect import getfullargspec as getargspec +else: + from inspect import getargspec + if PY3: # pragma: no cover from itertools import zip_longest else: diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index a114cf039..b5b5e841d 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -180,6 +180,11 @@ class Configurator( See :ref:`changing_the_request_factory`. By default it is ``None``, which means use the default request factory. + If ``response_factory`` is passed, it should be a :term:`response + factory` implementation or a :term:`dotted Python name` to the same. + See :ref:`changing_the_response_factory`. By default it is ``None``, + which means use the default response factory. + If ``default_permission`` is passed, it should be a :term:`permission` string to be used as the default permission for all view configuration registrations performed against this @@ -191,7 +196,7 @@ class Configurator( configurations which do not explicitly declare a permission will always be executable by entirely anonymous users (any authorization policy in effect is ignored). - + .. seealso:: See also :ref:`setting_a_default_permission`. @@ -255,6 +260,7 @@ class Configurator( .. versionadded:: 1.6 The ``root_package`` argument. + The ``response_factory`` argument. """ manager = manager # for testing injection venusian = venusian # for testing injection @@ -277,6 +283,7 @@ class Configurator( debug_logger=None, locale_negotiator=None, request_factory=None, + response_factory=None, default_permission=None, session_factory=None, default_view_mapper=None, @@ -311,6 +318,7 @@ class Configurator( debug_logger=debug_logger, locale_negotiator=locale_negotiator, request_factory=request_factory, + response_factory=response_factory, default_permission=default_permission, session_factory=session_factory, default_view_mapper=default_view_mapper, @@ -326,6 +334,7 @@ class Configurator( debug_logger=None, locale_negotiator=None, request_factory=None, + response_factory=None, default_permission=None, session_factory=None, default_view_mapper=None, @@ -413,6 +422,9 @@ class Configurator( if request_factory: self.set_request_factory(request_factory) + if response_factory: + self.set_response_factory(response_factory) + if default_permission: self.set_default_permission(default_permission) @@ -470,7 +482,7 @@ class Configurator( _registry.registerSelfAdapter = registerSelfAdapter # API - + def _get_introspector(self): introspector = getattr(self.registry, 'introspector', _marker) if introspector is _marker: diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py index f6a652e3d..3d11980da 100644 --- a/pyramid/config/adapters.py +++ b/pyramid/config/adapters.py @@ -143,7 +143,7 @@ class AdaptersConfiguratorMixin(object): Adds a subscriber predicate factory. The associated subscriber predicate can later be named as a keyword argument to :meth:`pyramid.config.Configurator.add_subscriber` in the - ``**predicates`` anonyous keyword argument dictionary. + ``**predicates`` anonymous keyword argument dictionary. ``name`` should be the name of the predicate. It must be a valid Python identifier (it will be used as a ``**predicates`` keyword diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 5ce1081c6..d7a48ba93 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -4,6 +4,7 @@ from zope.interface import implementer from pyramid.interfaces import ( IDefaultRootFactory, IRequestFactory, + IResponseFactory, IRequestExtensions, IRootFactory, ISessionFactory, @@ -97,6 +98,32 @@ class FactoriesConfiguratorMixin(object): self.action(IRequestFactory, register, introspectables=(intr,)) @action_method + def set_response_factory(self, factory): + """ The object passed as ``factory`` should be an object (or a + :term:`dotted Python name` which refers to an object) which + will be used by the :app:`Pyramid` as the default response + objects. This factory object must have the same + methods and attributes as the + :class:`pyramid.request.Response` class. + + .. note:: + + Using the ``response_factory`` argument to the + :class:`pyramid.config.Configurator` constructor + can be used to achieve the same purpose. + """ + factory = self.maybe_dotted(factory) + + def register(): + self.registry.registerUtility(factory, IResponseFactory) + + intr = self.introspectable('response factory', None, + self.object_description(factory), + 'response factory') + intr['factory'] = factory + self.action(IResponseFactory, register, introspectables=(intr,)) + + @action_method def add_request_method(self, callable=None, name=None, diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index f1463b50b..24f38a4fd 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -138,6 +138,18 @@ class RoutesConfiguratorMixin(object): .. versionadded:: 1.1 + accept + + This value represents a match query for one or more mimetypes in the + ``Accept`` HTTP request header. If this value is specified, it must + be in one of the following forms: a mimetype match token in the form + ``text/plain``, a wildcard mimetype match token in the form + ``text/*`` or a match-all wildcard mimetype match token in the form + ``*/*``. If any of the forms matches the ``Accept`` header of the + request, or if the ``Accept`` header isn't set at all in the request, + this will match the current route. If this does not match the + ``Accept`` header of the request, route matching continues. + Predicate Arguments pattern @@ -220,19 +232,6 @@ class RoutesConfiguratorMixin(object): case of the header name is not significant. If this predicate returns ``False``, route matching continues. - accept - - This value represents a match query for one or more - mimetypes in the ``Accept`` HTTP request header. If this - value is specified, it must be in one of the following - forms: a mimetype match token in the form ``text/plain``, a - wildcard mimetype match token in the form ``text/*`` or a - match-all wildcard mimetype match token in the form ``*/*``. - If any of the forms matches the ``Accept`` header of the - request, or if the ``Accept`` header isn't set at all in the - request, this predicate will be true. If this predicate - returns ``False``, route matching continues. - effective_principals If specified, this value should be a :term:`principal` identifier or @@ -303,6 +302,8 @@ class RoutesConfiguratorMixin(object): # check for an external route; an external route is one which is # is a full url (e.g. 'http://example.com/{id}') parsed = urlparse.urlparse(pattern) + external_url = pattern + if parsed.hostname: pattern = parsed.path @@ -357,6 +358,10 @@ class RoutesConfiguratorMixin(object): intr['pregenerator'] = pregenerator intr['static'] = static intr['use_global_views'] = use_global_views + + if static is True: + intr['external_url'] = external_url + introspectables.append(intr) if factory: diff --git a/pyramid/config/util.py b/pyramid/config/util.py index 892592196..23cdc6be8 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -3,6 +3,7 @@ import inspect from pyramid.compat import ( bytes_, + getargspec, is_nonstr_iter, ) @@ -201,7 +202,7 @@ def takes_one_arg(callee, attr=None, argname=None): return False try: - argspec = inspect.getargspec(fn) + argspec = getargspec(fn) except TypeError: return False diff --git a/pyramid/config/views.py b/pyramid/config/views.py index c01b72e12..1f69d7e0b 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -841,6 +841,18 @@ class ViewsConfiguratorMixin(object): very useful for 'civilians' who are just developing stock Pyramid applications. Pay no attention to the man behind the curtain. + accept + + This value represents a match query for one or more mimetypes in the + ``Accept`` HTTP request header. If this value is specified, it must + be in one of the following forms: a mimetype match token in the form + ``text/plain``, a wildcard mimetype match token in the form + ``text/*`` or a match-all wildcard mimetype match token in the form + ``*/*``. If any of the forms matches the ``Accept`` header of the + request, or if the ``Accept`` header isn't set at all in the request, + this will match the current view. If this does not match the + ``Accept`` header of the request, view matching continues. + Predicate Arguments name @@ -941,17 +953,6 @@ class ViewsConfiguratorMixin(object): This is useful for detecting AJAX requests issued from jQuery, Prototype and other Javascript libraries. - accept - - The value of this argument represents a match query for one - or more mimetypes in the ``Accept`` HTTP request header. If - this value is specified, it must be in one of the following - forms: a mimetype match token in the form ``text/plain``, a - wildcard mimetype match token in the form ``text/*`` or a - match-all wildcard mimetype match token in the form ``*/*``. - If any of the forms matches the ``Accept`` header of the - request, this predicate will be true. - header This value represents an HTTP header name or a header diff --git a/pyramid/decorator.py b/pyramid/decorator.py index 0d17bc398..df30c5e10 100644 --- a/pyramid/decorator.py +++ b/pyramid/decorator.py @@ -1,3 +1,6 @@ +import functools + + class reify(object): """ Use as a class method decorator. It operates almost exactly like the Python ``@property`` decorator, but it puts the result of the method it @@ -26,10 +29,7 @@ class reify(object): """ def __init__(self, wrapped): self.wrapped = wrapped - try: - self.__doc__ = wrapped.__doc__ - except: # pragma: no cover - pass + functools.update_wrapper(self, wrapped) def __get__(self, inst, objtype=None): if inst is None: diff --git a/pyramid/renderers.py b/pyramid/renderers.py index e647ebacf..3c35551ea 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -10,7 +10,6 @@ from zope.interface.registry import Components from pyramid.interfaces import ( IJSONAdapter, IRendererFactory, - IResponseFactory, IRendererInfo, ) @@ -25,7 +24,7 @@ from pyramid.events import BeforeRender from pyramid.path import caller_package -from pyramid.response import Response +from pyramid.response import _get_response_factory from pyramid.threadlocal import get_current_registry # API @@ -356,19 +355,19 @@ class JSONP(JSON): ``self.param_name`` is present in request.GET; otherwise returns plain-JSON encoded string with content-type ``application/json``""" def _render(value, system): - request = system['request'] + request = system.get('request') default = self._make_default(request) val = self.serializer(value, default=default, **self.kw) - callback = request.GET.get(self.param_name) - if callback is None: - ct = 'application/json' - body = val - else: - ct = 'application/javascript' - body = '%s(%s);' % (callback, val) - response = request.response - if response.content_type == response.default_content_type: - response.content_type = ct + ct = 'application/json' + body = val + if request is not None: + callback = request.GET.get(self.param_name) + if callback is not None: + ct = 'application/javascript' + body = '%s(%s);' % (callback, val) + response = request.response + if response.content_type == response.default_content_type: + response.content_type = ct return body return _render @@ -448,14 +447,16 @@ class RendererHelper(object): 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_factory = _get_response_factory(registry) + response = response_factory(request) if result is not None: if isinstance(result, text_type): response.text = result + elif isinstance(result, bytes): + response.body = result + elif hasattr(result, '__iter__'): + response.app_iter = result else: response.body = result diff --git a/pyramid/request.py b/pyramid/request.py index bc2889310..b2e2efe05 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -10,7 +10,6 @@ from pyramid.interfaces import ( IRequest, IResponse, ISessionFactory, - IResponseFactory, ) from pyramid.compat import ( @@ -21,7 +20,7 @@ from pyramid.compat import ( from pyramid.decorator import reify from pyramid.i18n import LocalizerRequestMixin -from pyramid.response import Response +from pyramid.response import Response, _get_response_factory from pyramid.security import ( AuthenticationAPIMixin, AuthorizationAPIMixin, @@ -214,10 +213,8 @@ class Request( 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() + response_factory = _get_response_factory(self.registry) + return response_factory(self) def is_response(self, ob): """ Return ``True`` if the object passed as ``ob`` is a valid diff --git a/pyramid/response.py b/pyramid/response.py index d11fd0123..892e5dfff 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -8,7 +8,8 @@ import venusian from webob import Response as _Response from zope.interface import implementer -from pyramid.interfaces import IResponse +from pyramid.interfaces import IResponse, IResponseFactory + def init_mimetypes(mimetypes): # this is a function so it can be unittested @@ -143,7 +144,7 @@ class response_adapter(object): @response_adapter(dict, list) def myadapter(ob): return Response(json.dumps(ob)) - + This method will have no effect until a :term:`scan` is performed agains the package or module which contains it, ala: @@ -167,3 +168,15 @@ class response_adapter(object): def __call__(self, wrapped): self.venusian.attach(wrapped, self.register, category='pyramid') return wrapped + + +def _get_response_factory(registry): + """ Obtain a :class: `pyramid.response.Response` using the + `pyramid.interfaces.IResponseFactory`. + """ + response_factory = registry.queryUtility( + IResponseFactory, + default=lambda r: Response() + ) + + return response_factory diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py index 2ab3b8bb9..34eeadf32 100644 --- a/pyramid/scripts/prequest.py +++ b/pyramid/scripts/prequest.py @@ -5,7 +5,7 @@ import textwrap from pyramid.compat import url_unquote from pyramid.request import Request -from pyramid.paster import get_app +from pyramid.paster import get_app, setup_logging from pyramid.scripts.common import parse_vars def main(argv=sys.argv, quiet=False): @@ -97,12 +97,18 @@ class PRequestCommand(object): if not self.quiet: print(msg) + def configure_logging(self, app_spec): + setup_logging(app_spec) + def run(self): if not len(self.args) >= 2: self.out('You must provide at least two arguments') return 2 app_spec = self.args[0] path = self.args[1] + + self.configure_logging(app_spec) + if not path.startswith('/'): path = '/' + path diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index d0c1aa13e..544947724 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -1,12 +1,26 @@ +import fnmatch import optparse import sys import textwrap +import re from pyramid.paster import bootstrap +from pyramid.compat import (string_types, configparser) +from pyramid.interfaces import ( + IRouteRequest, + IViewClassifier, + IView, +) +from pyramid.config import not_ + from pyramid.scripts.common import parse_vars +from pyramid.static import static_view +from zope.interface import Interface PAD = 3 +ANY_KEY = '*' +UNKNOWN_KEY = '<unknown>' def main(argv=sys.argv, quiet=False): @@ -14,6 +28,206 @@ def main(argv=sys.argv, quiet=False): return command.run() +def _get_pattern(route): + pattern = route.pattern + + if not pattern.startswith('/'): + pattern = '/%s' % pattern + return pattern + + +def _get_print_format(fmt, max_name, max_pattern, max_view, max_method): + print_fmt = '' + max_map = { + 'name': max_name, + 'pattern': max_pattern, + 'view': max_view, + 'method': max_method, + } + sizes = [] + + for index, col in enumerate(fmt): + size = max_map[col] + PAD + print_fmt += '{{%s: <{%s}}} ' % (col, index) + sizes.append(size) + + return print_fmt.format(*sizes) + + +def _get_request_methods(route_request_methods, view_request_methods): + excludes = set() + + if route_request_methods: + route_request_methods = set(route_request_methods) + + if view_request_methods: + view_request_methods = set(view_request_methods) + + for method in view_request_methods.copy(): + if method.startswith('!'): + view_request_methods.remove(method) + excludes.add(method[1:]) + + has_route_methods = route_request_methods is not None + has_view_methods = len(view_request_methods) > 0 + has_methods = has_route_methods or has_view_methods + + if has_route_methods is False and has_view_methods is False: + request_methods = [ANY_KEY] + elif has_route_methods is False and has_view_methods is True: + request_methods = view_request_methods + elif has_route_methods is True and has_view_methods is False: + request_methods = route_request_methods + else: + request_methods = route_request_methods.intersection( + view_request_methods + ) + + request_methods = set(request_methods).difference(excludes) + + if has_methods and not request_methods: + request_methods = '<route mismatch>' + elif request_methods: + if excludes and request_methods == set([ANY_KEY]): + for exclude in excludes: + request_methods.add('!%s' % exclude) + + request_methods = ','.join(sorted(request_methods)) + + return request_methods + + +def _get_view_module(view_callable): + if view_callable is None: + return UNKNOWN_KEY + + if hasattr(view_callable, '__name__'): + if hasattr(view_callable, '__original_view__'): + original_view = view_callable.__original_view__ + else: + original_view = None + + if isinstance(original_view, static_view): + if original_view.package_name is not None: + return '%s:%s' % ( + original_view.package_name, + original_view.docroot + ) + else: + return original_view.docroot + else: + view_name = view_callable.__name__ + else: + # Currently only MultiView hits this, + # we could just not run _get_view_module + # for them and remove this logic + view_name = str(view_callable) + + view_module = '%s.%s' % ( + view_callable.__module__, + view_name, + ) + + # If pyramid wraps something in wsgiapp or wsgiapp2 decorators + # that is currently returned as pyramid.router.decorator, lets + # hack a nice name in: + if view_module == 'pyramid.router.decorator': + view_module = '<wsgiapp>' + + return view_module + + +def get_route_data(route, registry): + pattern = _get_pattern(route) + + request_iface = registry.queryUtility( + IRouteRequest, + name=route.name + ) + + route_request_methods = None + view_request_methods_order = [] + view_request_methods = {} + view_callable = None + + route_intr = registry.introspector.get( + 'routes', route.name + ) + + if request_iface is None: + return [ + (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY) + ] + + view_callable = registry.adapters.lookup( + (IViewClassifier, request_iface, Interface), + IView, + name='', + default=None + ) + view_module = _get_view_module(view_callable) + + # Introspectables can be turned off, so there could be a chance + # that we have no `route_intr` but we do have a route + callable + if route_intr is None: + view_request_methods[view_module] = [] + view_request_methods_order.append(view_module) + else: + if route_intr.get('static', False) is True: + return [ + (route.name, route_intr['external_url'], UNKNOWN_KEY, ANY_KEY) + ] + + + route_request_methods = route_intr['request_methods'] + view_intr = registry.introspector.related(route_intr) + + if view_intr: + for view in view_intr: + request_method = view.get('request_methods') + + if request_method is not None: + view_callable = view['callable'] + view_module = _get_view_module(view_callable) + + if view_module not in view_request_methods: + view_request_methods[view_module] = [] + view_request_methods_order.append(view_module) + + if isinstance(request_method, string_types): + request_method = (request_method,) + elif isinstance(request_method, not_): + request_method = ('!%s' % request_method.value,) + + view_request_methods[view_module].extend(request_method) + else: + if view_module not in view_request_methods: + view_request_methods[view_module] = [] + view_request_methods_order.append(view_module) + + else: + view_request_methods[view_module] = [] + view_request_methods_order.append(view_module) + + final_routes = [] + + for view_module in view_request_methods_order: + methods = view_request_methods[view_module] + request_methods = _get_request_methods( + route_request_methods, + methods + ) + + final_routes.append(( + route.name, + pattern, + view_module, + request_methods, + )) + + return final_routes + + class PRoutesCommand(object): description = """\ Print all URL dispatch routes used by a Pyramid application in the @@ -30,111 +244,153 @@ class PRoutesCommand(object): bootstrap = (bootstrap,) stdout = sys.stdout usage = '%prog config_uri' - + ConfigParser = configparser.ConfigParser # testing parser = optparse.OptionParser( usage, description=textwrap.dedent(description) - ) + ) + parser.add_option('-g', '--glob', + action='store', type='string', dest='glob', + default='', help='Display routes matching glob pattern') + + parser.add_option('-f', '--format', + action='store', type='string', dest='format', + default='', help=('Choose which columns to display, this ' + 'will override the format key in the ' + '[proutes] ini section')) def __init__(self, argv, quiet=False): self.options, self.args = self.parser.parse_args(argv[1:]) self.quiet = quiet + self.available_formats = [ + 'name', 'pattern', 'view', 'method' + ] + self.column_format = self.available_formats + + def validate_formats(self, formats): + invalid_formats = [] + for fmt in formats: + if fmt not in self.available_formats: + invalid_formats.append(fmt) + + msg = ( + 'You provided invalid formats %s, ' + 'Available formats are %s' + ) - def _get_mapper(self, registry): - from pyramid.config import Configurator - config = Configurator(registry = registry) - return config.get_routes_mapper() + if invalid_formats: + msg = msg % (invalid_formats, self.available_formats) + self.out(msg) + return False + + return True + + def proutes_file_config(self, filename): + config = self.ConfigParser() + config.read(filename) + try: + items = config.items('proutes') + for k, v in items: + if 'format' == k: + cols = re.split(r'[,|\s|\n]*', v) + self.column_format = [x.strip() for x in cols] - def out(self, msg): # pragma: no cover + except configparser.NoSectionError: + return + + def out(self, msg): # pragma: no cover if not self.quiet: print(msg) + def _get_mapper(self, registry): + from pyramid.config import Configurator + config = Configurator(registry=registry) + return config.get_routes_mapper() + def run(self, quiet=False): if not self.args: self.out('requires a config file argument') return 2 - from pyramid.interfaces import IRouteRequest - from pyramid.interfaces import IViewClassifier - from pyramid.interfaces import IView - from pyramid.interfaces import IMultiView - - from zope.interface import Interface config_uri = self.args[0] - env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) registry = env['registry'] mapper = self._get_mapper(registry) - if mapper is not None: - mapped_routes = [('Name', 'Pattern', 'View')] - - max_name = len('Name') - max_pattern = len('Pattern') - max_view = len('View') - - routes = mapper.get_routes() - - if not routes: - return 0 - - mapped_routes.append(( - '-' * max_name, - '-' * max_pattern, - '-' * max_view, - )) - - for route in routes: - pattern = route.pattern - if not pattern.startswith('/'): - pattern = '/' + pattern - request_iface = registry.queryUtility(IRouteRequest, - name=route.name) - view_callable = None - - if (request_iface is None) or (route.factory is not None): - view_callable = '<unknown>' - else: - view_callable = registry.adapters.lookup( - (IViewClassifier, request_iface, Interface), - IView, name='', default=None) - - if view_callable is not None: - if IMultiView.providedBy(view_callable): - view_callables = [ - x[1] for x in view_callable.views - ] - else: - view_callables = [view_callable] - - for view_func in view_callables: - view_callable = '%s.%s' % ( - view_func.__module__, - view_func.__name__, - ) - else: - view_callable = str(None) - - if len(route.name) > max_name: - max_name = len(route.name) + + self.proutes_file_config(config_uri) + + if self.options.format: + columns = self.options.format.split(',') + self.column_format = [x.strip() for x in columns] + + is_valid = self.validate_formats(self.column_format) + + if is_valid is False: + return 2 + + if mapper is None: + return 0 + + max_name = len('Name') + max_pattern = len('Pattern') + max_view = len('View') + max_method = len('Method') + + routes = mapper.get_routes(include_static=True) + + if len(routes) == 0: + return 0 + + mapped_routes = [{ + 'name': 'Name', + 'pattern': 'Pattern', + 'view': 'View', + 'method': 'Method' + },{ + 'name': '----', + 'pattern': '-------', + 'view': '----', + 'method': '------' + }] + + for route in routes: + route_data = get_route_data(route, registry) + + for name, pattern, view, method in route_data: + if self.options.glob: + match = (fnmatch.fnmatch(name, self.options.glob) or + fnmatch.fnmatch(pattern, self.options.glob)) + if not match: + continue + + if len(name) > max_name: + max_name = len(name) if len(pattern) > max_pattern: max_pattern = len(pattern) - if len(view_callable) > max_view: - max_view = len(view_callable) + if len(view) > max_view: + max_view = len(view) - mapped_routes.append((route.name, pattern, view_callable)) + if len(method) > max_method: + max_method = len(method) - fmt = '%-{0}s %-{1}s %-{2}s'.format( - max_name + PAD, - max_pattern + PAD, - max_view + PAD, - ) + mapped_routes.append({ + 'name': name, + 'pattern': pattern, + 'view': view, + 'method': method + }) - for route_data in mapped_routes: - self.out(fmt % route_data) + fmt = _get_print_format( + self.column_format, max_name, max_pattern, max_view, max_method + ) + + for route in mapped_routes: + self.out(fmt.format(**route)) return 0 -if __name__ == '__main__': # pragma: no cover + +if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index ea125a0dd..314efd839 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -21,9 +21,11 @@ import textwrap import threading import time import traceback +import webbrowser from paste.deploy import loadserver from paste.deploy import loadapp +from paste.deploy.loadwsgi import loadcontext, SERVER from pyramid.compat import PY3 from pyramid.compat import WIN @@ -122,6 +124,11 @@ class PServeCommand(object): action='store_true', help="Auto-restart server if it dies") parser.add_option( + '-b', '--browser', + dest='browser', + action='store_true', + help="Open a web browser to server url") + parser.add_option( '--status', action='store_true', dest='show_status', @@ -334,6 +341,17 @@ class PServeCommand(object): msg = '' self.out('Exiting%s (-v to see traceback)' % msg) + if self.options.browser: + def open_browser(): + context = loadcontext(SERVER, app_spec, name=app_name, relative_to=base, + global_conf=vars) + url = 'http://{host}:{port}/'.format(**context.config()) + time.sleep(1) + webbrowser.open(url) + t = threading.Thread(target=open_browser) + t.setDaemon(True) + t.start() + serve() def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover diff --git a/pyramid/security.py b/pyramid/security.py index cbb4b895f..f993ef353 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -126,7 +126,7 @@ def remember(request, userid=_marker, **kw): current :term:`authentication policy`. Common usage might look like so within the body of a view function (``response`` is assumed to be a :term:`WebOb` -style :term:`response` object - computed previously by the view code):: + computed previously by the view code): .. code-block:: python @@ -170,12 +170,14 @@ def forget(request): possessed by the currently authenticated user. A common usage might look like so within the body of a view function (``response`` is assumed to be an :term:`WebOb` -style - :term:`response` object computed previously by the view code):: + :term:`response` object computed previously by the view code): - from pyramid.security import forget - headers = forget(request) - response.headerlist.extend(headers) - return response + .. code-block:: python + + from pyramid.security import forget + headers = forget(request) + response.headerlist.extend(headers) + return response If no :term:`authentication policy` is in use, this function will always return an empty sequence. diff --git a/pyramid/testing.py b/pyramid/testing.py index f77889e72..667e6af4e 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -9,7 +9,6 @@ from zope.interface import ( from pyramid.interfaces import ( IRequest, - IResponseFactory, ISession, ) @@ -22,7 +21,7 @@ from pyramid.compat import ( from pyramid.config import Configurator from pyramid.decorator import reify from pyramid.path import caller_package -from pyramid.response import Response +from pyramid.response import Response, _get_response_factory from pyramid.registry import Registry from pyramid.security import ( @@ -42,6 +41,7 @@ from pyramid.request import CallbackMethodsMixin from pyramid.url import URLMethodsMixin from pyramid.util import InstancePropertyMixin + _marker = object() class DummyRootFactory(object): @@ -383,8 +383,8 @@ class DummyRequest( @reify def response(self): - f = self.registry.queryUtility(IResponseFactory, default=Response) - return f() + f = _get_response_factory(self.registry) + return f(self) have_zca = True diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index 6e679397f..0bd5336ff 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -23,6 +23,21 @@ class TestFactoriesMixin(unittest.TestCase): self.assertEqual(config.registry.getUtility(IRequestFactory), dummyfactory) + def test_set_response_factory(self): + from pyramid.interfaces import IResponseFactory + config = self._makeOne(autocommit=True) + factory = lambda r: object() + config.set_response_factory(factory) + self.assertEqual(config.registry.getUtility(IResponseFactory), factory) + + def test_set_response_factory_dottedname(self): + from pyramid.interfaces import IResponseFactory + config = self._makeOne(autocommit=True) + config.set_response_factory( + 'pyramid.tests.test_config.dummyfactory') + self.assertEqual(config.registry.getUtility(IResponseFactory), + dummyfactory) + def test_set_root_factory(self): from pyramid.interfaces import IRootFactory config = self._makeOne() diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 40cc83885..2930734fa 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -546,6 +546,18 @@ class ConfiguratorTests(unittest.TestCase): utility = reg.getUtility(IRequestFactory) self.assertEqual(utility, factory) + def test_setup_registry_response_factory(self): + from pyramid.registry import Registry + from pyramid.interfaces import IResponseFactory + reg = Registry() + config = self._makeOne(reg) + factory = lambda r: object() + config.setup_registry(response_factory=factory) + self.assertEqual(reg.queryUtility(IResponseFactory), None) + config.commit() + utility = reg.getUtility(IResponseFactory) + self.assertEqual(utility, factory) + def test_setup_registry_request_factory_dottedname(self): from pyramid.registry import Registry from pyramid.interfaces import IRequestFactory diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py index bb61714ae..ccf7fa260 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -568,6 +568,13 @@ class Test_takes_one_arg(unittest.TestCase): foo = Foo() self.assertTrue(self._callFUT(foo.method)) + def test_function_annotations(self): + def foo(bar): + """ """ + # avoid SyntaxErrors in python2, this if effectively nop + getattr(foo, '__annotations__', {}).update({'bar': 'baz'}) + self.assertTrue(self._callFUT(foo)) + class TestNotted(unittest.TestCase): def _makeOne(self, predicate): from pyramid.config.util import Notted diff --git a/pyramid/tests/test_decorator.py b/pyramid/tests/test_decorator.py index 9ab1b7229..0a98a512d 100644 --- a/pyramid/tests/test_decorator.py +++ b/pyramid/tests/test_decorator.py @@ -15,15 +15,19 @@ class TestReify(unittest.TestCase): self.assertEqual(inst.__dict__['wrapped'], 'a') def test___get__noinst(self): - decorator = self._makeOne(None) + def wrapped(inst): + return 'a' # pragma: no cover + decorator = self._makeOne(wrapped) result = decorator.__get__(None) self.assertEqual(result, decorator) - def test___doc__copied(self): - def wrapped(inst): - """My doc""" - decorator = self._makeOne(wrapped) - self.assertEqual(decorator.__doc__, "My doc") - + def test_dunder_attrs_copied(self): + from pyramid.util import viewdefaults + decorator = self._makeOne(viewdefaults) + self.assertEqual(decorator.__doc__, viewdefaults.__doc__) + self.assertEqual(decorator.__name__, viewdefaults.__name__) + self.assertEqual(decorator.__module__, viewdefaults.__module__) + + class Dummy(object): pass diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 2bddd2318..6d79cc291 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -182,7 +182,10 @@ class TestRendererHelper(unittest.TestCase): from pyramid.interfaces import IResponseFactory class ResponseFactory(object): pass - self.config.registry.registerUtility(ResponseFactory, IResponseFactory) + + self.config.registry.registerUtility( + lambda r: ResponseFactory(), IResponseFactory + ) def test_render_to_response(self): self._registerRendererFactory() @@ -191,8 +194,8 @@ class TestRendererHelper(unittest.TestCase): helper = self._makeOne('loo.foo') response = helper.render_to_response('values', {}, request=request) - self.assertEqual(response.body[0], 'values') - self.assertEqual(response.body[1], {}) + self.assertEqual(response.app_iter[0], 'values') + self.assertEqual(response.app_iter[1], {}) def test_get_renderer(self): factory = self._registerRendererFactory() @@ -209,8 +212,8 @@ class TestRendererHelper(unittest.TestCase): request = testing.DummyRequest() response = 'response' response = helper.render_view(request, response, view, context) - self.assertEqual(response.body[0], 'response') - self.assertEqual(response.body[1], + self.assertEqual(response.app_iter[0], 'response') + self.assertEqual(response.app_iter[1], {'renderer_info': helper, 'renderer_name': 'loo.foo', 'request': request, @@ -287,6 +290,23 @@ class TestRendererHelper(unittest.TestCase): response = helper._make_response(la.encode('utf-8'), request) self.assertEqual(response.body, la.encode('utf-8')) + def test__make_response_result_is_iterable(self): + from pyramid.response import Response + request = testing.DummyRequest() + request.response = Response() + helper = self._makeOne('loo.foo') + la = text_('/La Pe\xc3\xb1a', 'utf-8') + response = helper._make_response([la.encode('utf-8')], request) + self.assertEqual(response.body, la.encode('utf-8')) + + def test__make_response_result_is_other(self): + self._registerResponseFactory() + request = None + helper = self._makeOne('loo.foo') + result = object() + response = helper._make_response(result, request) + self.assertEqual(response.body, result) + def test__make_response_result_is_None_no_body(self): from pyramid.response import Response request = testing.DummyRequest() @@ -310,7 +330,9 @@ class TestRendererHelper(unittest.TestCase): class ResponseFactory(object): def __init__(self): pass - self.config.registry.registerUtility(ResponseFactory, IResponseFactory) + self.config.registry.registerUtility( + lambda r: ResponseFactory(), IResponseFactory + ) request = testing.DummyRequest() helper = self._makeOne('loo.foo') response = helper._make_response(b'abc', request) @@ -580,6 +602,12 @@ class TestJSONP(unittest.TestCase): self.assertEqual(request.response.content_type, 'application/json') + def test_render_without_request(self): + renderer_factory = self._makeOne() + renderer = renderer_factory(None) + result = renderer({'a':'1'}, {}) + self.assertEqual(result, '{"a": "1"}') + class Dummy: pass diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py index 84ec57757..ad55882c9 100644 --- a/pyramid/tests/test_response.py +++ b/pyramid/tests/test_response.py @@ -8,7 +8,7 @@ class TestResponse(unittest.TestCase): def _getTargetClass(self): from pyramid.response import Response return Response - + def test_implements_IResponse(self): from pyramid.interfaces import IResponse cls = self._getTargetClass() @@ -119,7 +119,7 @@ class Test_patch_mimetypes(unittest.TestCase): result = self._callFUT(module) self.assertEqual(result, True) self.assertEqual(module.initted, True) - + def test_missing_init(self): class DummyMimetypes(object): pass @@ -174,6 +174,17 @@ class TestResponseAdapter(unittest.TestCase): self.assertEqual(dummy_venusian.attached, [(foo, dec.register, 'pyramid')]) + +class TestGetResponseFactory(unittest.TestCase): + def test_get_factory(self): + from pyramid.registry import Registry + from pyramid.response import Response, _get_response_factory + + registry = Registry() + response = _get_response_factory(registry)(None) + self.assertTrue(isinstance(response, Response)) + + class Dummy(object): pass @@ -190,5 +201,3 @@ class DummyVenusian(object): def attach(self, wrapped, fn, category=None): self.attached.append((wrapped, fn, category)) - - diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index c6c6eea1c..30ebd5918 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -599,17 +599,19 @@ class TestRouter(unittest.TestCase): environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, None, None) request_events = self._registerEventListener(INewRequest) - aftertraversal_events = self._registerEventListener(IContextFound) + context_found_events = self._registerEventListener(IContextFound) response_events = self._registerEventListener(INewResponse) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(len(request_events), 1) self.assertEqual(request_events[0].request.environ, environ) - self.assertEqual(len(aftertraversal_events), 1) - self.assertEqual(aftertraversal_events[0].request.environ, environ) + self.assertEqual(len(context_found_events), 1) + self.assertEqual(context_found_events[0].request.environ, environ) + self.assertEqual(context_found_events[0].request.context, context) self.assertEqual(len(response_events), 1) self.assertEqual(response_events[0].response, response) + self.assertEqual(response_events[0].request.context, context) self.assertEqual(result, response.app_iter) def test_call_newrequest_evllist_exc_can_be_caught_by_exceptionview(self): diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index 366aa00b5..930b9ed64 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -60,7 +60,7 @@ class DummyMapper(object): def __init__(self, *routes): self.routes = routes - def get_routes(self): + def get_routes(self, include_static=False): return self.routes class DummyRoute(object): diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py index 37f1d3c0f..95cec0518 100644 --- a/pyramid/tests/test_scripts/test_prequest.py +++ b/pyramid/tests/test_scripts/test_prequest.py @@ -210,8 +210,21 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) + self.assertEqual(self._out, [b'abc']) + def test_command_method_configures_logging(self): + command = self._makeOne(['', 'development.ini', '/']) + called_args = [] + + def configure_logging(app_spec): + called_args.append(app_spec) + + command.configure_logging = configure_logging + command.run() + self.assertEqual(called_args, ['development.ini']) + + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.prequest import main diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 32202af4b..e426eee73 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -1,6 +1,16 @@ import unittest from pyramid.tests.test_scripts import dummy + +class DummyIntrospector(object): + def __init__(self): + self.relations = {} + self.introspectables = {} + + def get(self, name, discrim): + pass + + class TestPRoutesCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.scripts.proutes import PRoutesCommand @@ -10,8 +20,20 @@ class TestPRoutesCommand(unittest.TestCase): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) cmd.args = ('/foo/bar/myapp.ini#myapp',) + return cmd + def _makeRegistry(self): + from pyramid.registry import Registry + registry = Registry() + registry.introspector = DummyIntrospector() + return registry + + def _makeConfig(self, *arg, **kw): + from pyramid.config import Configurator + config = Configurator(*arg, **kw) + return config + def test_good_args(self): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) @@ -19,6 +41,8 @@ class TestPRoutesCommand(unittest.TestCase): route = dummy.DummyRoute('a', '/a') mapper = dummy.DummyMapper(route) cmd._get_mapper = lambda *arg: mapper + registry = self._makeRegistry() + cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),) L = [] cmd.out = lambda msg: L.append(msg) cmd.run() @@ -58,12 +82,15 @@ class TestPRoutesCommand(unittest.TestCase): route = dummy.DummyRoute('a', '/a') mapper = dummy.DummyMapper(route) command._get_mapper = lambda *arg: mapper + registry = self._makeRegistry() + command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + L = [] command.out = L.append result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) - self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>']) + self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '*']) def test_route_with_no_slash_prefix(self): command = self._makeOne() @@ -72,16 +99,18 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append + registry = self._makeRegistry() + command.bootstrap = (dummy.DummyBootstrap(registry=registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) - self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>']) + self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '*']) def test_single_route_no_views_registered(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest - registry = Registry() + registry = self._makeRegistry() + def view():pass class IMyRoute(Interface): pass @@ -96,15 +125,15 @@ class TestPRoutesCommand(unittest.TestCase): result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) - self.assertEqual(L[-1].split()[:3], ['a', '/a', 'None']) + self.assertEqual(L[-1].split()[:3], ['a', '/a', '<unknown>']) def test_single_route_one_view_registered(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView - registry = Registry() + registry = self._makeRegistry() + def view():pass class IMyRoute(Interface): pass @@ -130,11 +159,11 @@ class TestPRoutesCommand(unittest.TestCase): def test_one_route_with_long_name_one_view_registered(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView - registry = Registry() + registry = self._makeRegistry() + def view():pass class IMyRoute(Interface): @@ -172,11 +201,11 @@ class TestPRoutesCommand(unittest.TestCase): def test_single_route_one_view_registered_with_factory(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView - registry = Registry() + registry = self._makeRegistry() + def view():pass class IMyRoot(Interface): pass @@ -201,12 +230,11 @@ class TestPRoutesCommand(unittest.TestCase): def test_single_route_multiview_registered(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView - registry = Registry() + registry = self._makeRegistry() def view(): pass @@ -235,19 +263,494 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split()[:3] + view_module = 'pyramid.tests.test_scripts.dummy' + view_str = '<pyramid.tests.test_scripts.dummy.DummyMultiView' + final = '%s.%s' % (view_module, view_str) + self.assertEqual( compare_to, - ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view'] + ['a', '/a', final] ) def test__get_mapper(self): - from pyramid.registry import Registry from pyramid.urldispatch import RoutesMapper command = self._makeOne() - registry = Registry() + registry = self._makeRegistry() + result = command._get_mapper(registry) self.assertEqual(result.__class__, RoutesMapper) + def test_one_route_all_methods_view_only_post(self): + from pyramid.renderers import null_renderer as nr + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method='POST' + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + 'pyramid.tests.test_scripts.test_proutes.view1', 'POST' + ] + self.assertEqual(compare_to, expected) + + def test_one_route_only_post_view_all_methods(self): + from pyramid.renderers import null_renderer as nr + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='POST') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + 'pyramid.tests.test_scripts.test_proutes.view1', 'POST' + ] + self.assertEqual(compare_to, expected) + + def test_one_route_only_post_view_post_and_get(self): + from pyramid.renderers import null_renderer as nr + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='POST') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=('POST', 'GET') + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + 'pyramid.tests.test_scripts.test_proutes.view1', 'POST' + ] + self.assertEqual(compare_to, expected) + + def test_route_request_method_mismatch(self): + from pyramid.renderers import null_renderer as nr + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='POST') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method='GET' + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + 'pyramid.tests.test_scripts.test_proutes.view1', + '<route', 'mismatch>' + ] + self.assertEqual(compare_to, expected) + + def test_route_static_views(self): + from pyramid.renderers import null_renderer as nr + config = self._makeConfig(autocommit=True) + config.add_static_view('static', 'static', cache_max_age=3600) + config.add_static_view(name='static2', path='/var/www/static') + config.add_static_view( + name='pyramid_scaffold', + path='pyramid:scaffolds/starter/+package+/static' + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 5) + + expected = [ + ['__static/', '/static/*subpath', + 'pyramid.tests.test_scripts:static/', '*'], + ['__static2/', '/static2/*subpath', '/var/www/static/', '*'], + ['__pyramid_scaffold/', '/pyramid_scaffold/*subpath', + 'pyramid:scaffolds/starter/+package+/static/', '*'], + ] + + for index, line in enumerate(L[2:]): + data = line.split() + self.assertEqual(data, expected[index]) + + def test_route_no_view(self): + from pyramid.renderers import null_renderer as nr + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='POST') + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + '<unknown>', + 'POST', + ] + self.assertEqual(compare_to, expected) + + def test_route_as_wsgiapp(self): + from pyramid.wsgi import wsgiapp2 + + config1 = self._makeConfig(autocommit=True) + def view1(context, request): return 'view1' + config1.add_route('foo', '/a/b', request_method='POST') + config1.add_view(view=view1, route_name='foo') + + config2 = self._makeConfig(autocommit=True) + config2.add_route('foo', '/a/b', request_method='POST') + config2.add_view( + wsgiapp2(config1.make_wsgi_app()), + route_name='foo', + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config2.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + '<wsgiapp>', + 'POST', + ] + self.assertEqual(compare_to, expected) + + def test_route_is_get_view_request_method_not_post(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='GET') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + 'pyramid.tests.test_scripts.test_proutes.view1', + 'GET' + ] + self.assertEqual(compare_to, expected) + + def test_view_request_method_not_post(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + 'pyramid.tests.test_scripts.test_proutes.view1', + '!POST,*' + ] + self.assertEqual(compare_to, expected) + + def test_view_glob(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + def view2(context, request): return 'view2' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + config.add_route('bar', '/b/a') + config.add_view( + route_name='bar', + view=view2, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + command.options.glob = '*foo*' + + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + 'pyramid.tests.test_scripts.test_proutes.view1', + '!POST,*' + ] + self.assertEqual(compare_to, expected) + + def test_good_format(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + command.options.glob = '*foo*' + command.options.format = 'method,name' + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = ['!POST,*', 'foo'] + + self.assertEqual(compare_to, expected) + self.assertEqual(L[0].split(), ['Method', 'Name']) + + def test_bad_format(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + command.options.glob = '*foo*' + command.options.format = 'predicates,name,pattern' + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + expected = ( + "You provided invalid formats ['predicates'], " + "Available formats are ['name', 'pattern', 'view', 'method']" + ) + result = command.run() + self.assertEqual(result, 2) + self.assertEqual(L[0], expected) + + def test_config_format_ini_newlines(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + config_factory = dummy.DummyConfigParserFactory() + command.ConfigParser = config_factory + config_factory.items = [('format', 'method\nname')] + + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = ['!POST,*', 'foo'] + + self.assertEqual(compare_to, expected) + self.assertEqual(L[0].split(), ['Method', 'Name']) + + def test_config_format_ini_spaces(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + config_factory = dummy.DummyConfigParserFactory() + command.ConfigParser = config_factory + config_factory.items = [('format', 'method name')] + + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = ['!POST,*', 'foo'] + + self.assertEqual(compare_to, expected) + self.assertEqual(L[0].split(), ['Method', 'Name']) + + def test_config_format_ini_commas(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + config_factory = dummy.DummyConfigParserFactory() + command.ConfigParser = config_factory + config_factory.items = [('format', 'method,name')] + + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = ['!POST,*', 'foo'] + + self.assertEqual(compare_to, expected) + self.assertEqual(L[0].split(), ['Method', 'Name']) + + def test_static_routes_included_in_list(self): + from pyramid.renderers import null_renderer as nr + + config = self._makeConfig(autocommit=True) + config.add_route('foo', 'http://example.com/bar.aspx', static=True) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', 'http://example.com/bar.aspx', + '<unknown>', '*', + ] + self.assertEqual(compare_to, expected) + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py index dfcad2a0c..113f7e5f4 100644 --- a/pyramid/tests/test_testing.py +++ b/pyramid/tests/test_testing.py @@ -259,7 +259,9 @@ class TestDummyRequest(unittest.TestCase): registry = Registry('this_test') class ResponseFactory(object): pass - registry.registerUtility(ResponseFactory, IResponseFactory) + registry.registerUtility( + lambda r: ResponseFactory(), IResponseFactory + ) request = self._makeOne() request.registry = registry resp = request.response diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index a18fa8d16..ac5ea0683 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -324,7 +324,7 @@ class Test_object_description(unittest.TestCase): self.assertEqual( self._callFUT(inst), "object %s" % str(inst)) - + def test_shortened_repr(self): inst = ['1'] * 1000 self.assertEqual( @@ -592,7 +592,7 @@ class TestActionInfo(unittest.TestCase): def _getTargetClass(self): from pyramid.util import ActionInfo return ActionInfo - + def _makeOne(self, filename, lineno, function, linerepr): return self._getTargetClass()(filename, lineno, function, linerepr) diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py index fe4d433c3..349742c4a 100644 --- a/pyramid/urldispatch.py +++ b/pyramid/urldispatch.py @@ -42,12 +42,17 @@ class Route(object): class RoutesMapper(object): def __init__(self): self.routelist = [] + self.static_routes = [] + self.routes = {} def has_routes(self): return bool(self.routelist) - def get_routes(self): + def get_routes(self, include_static=False): + if include_static is True: + return self.routelist + self.static_routes + return self.routelist def get_route(self, name): @@ -59,9 +64,13 @@ class RoutesMapper(object): oldroute = self.routes[name] if oldroute in self.routelist: self.routelist.remove(oldroute) + route = Route(name, pattern, factory, predicates, pregenerator) if not static: self.routelist.append(route) + else: + self.static_routes.append(route) + self.routes[name] = route return route diff --git a/pyramid/util.py b/pyramid/util.py index 6de53d559..4ca2937a1 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -15,6 +15,10 @@ from pyramid.exceptions import ( CyclicDependencyError, ) +from pyramid.interfaces import ( + IResponseFactory, + ) + from pyramid.compat import ( iteritems_, is_nonstr_iter, @@ -25,6 +29,7 @@ from pyramid.compat import ( ) from pyramid.interfaces import IActionInfo +from pyramid.response import Response from pyramid.path import DottedNameResolver as _DottedNameResolver class DottedNameResolver(_DottedNameResolver): @@ -550,4 +555,3 @@ def action_method(wrapped): functools.update_wrapper(wrapper, wrapped) wrapper.__docobj__ = wrapped return wrapper - @@ -68,7 +68,7 @@ testing_extras = tests_require + [ ] setup(name='pyramid', - version='1.6dev', + version='1.6.dev0', description='The Pyramid Web Framework, a Pylons project', long_description=README + '\n\n' + CHANGES, classifiers=[ @@ -4,15 +4,15 @@ envlist = [testenv] commands = - python setup.py dev - python setup.py test -q + python setup.py -q dev + python setup.py -q test -q [testenv:cover] basepython = python2.6 commands = - python setup.py dev - python setup.py nosetests --with-xunit --with-xcoverage + python setup.py -q dev + nosetests --with-xunit --with-xcoverage deps = nosexcover |
