Merge branch '1.3-branch'
1 files deleted
82 files modified
| | |
| | | Next release |
| | | ============ |
| | | 1.3a5 (2012-01-09) |
| | | ================== |
| | | |
| | | Bug Fixes |
| | | --------- |
| | | |
| | | - The ``pyramid.view.view_defaults`` decorator did not work properly when |
| | | more than one view relied on the defaults being different for configuration |
| | | conflict resolution. See https://github.com/Pylons/pyramid/issues/394. |
| | | |
| | | Backwards Incompatibilities |
| | | --------------------------- |
| | | |
| | | - The ``path_info`` route and view predicates now match against |
| | | ``request.upath_info`` (Unicode) rather than ``request.path_info`` |
| | | (indeterminate value based on Python 3 vs. Python 2). This has to be done |
| | | to normalize matching on Python 2 and Python 3. |
| | | |
| | | 1.3a4 (2012-01-05) |
| | | ================== |
| | | |
| | | Features |
| | | -------- |
| | | |
| | | - New API: ``pyramid.request.Request.set_property``. Add lazy property |
| | | descriptors to a request without changing the request factory. New |
| | | properties may be reified, effectively caching the value for the lifetime |
| | | of the instance. Common use-cases for this would be to get a database |
| | | connection for the request or identify the current user. |
| | | |
| | | - Use the ``waitress`` WSGI server instead of ``wsgiref`` in scaffolding. |
| | | |
| | | Bug Fixes |
| | | --------- |
| | | |
| | | - The documentation of ``pyramid.events.subscriber`` indicated that using it |
| | | as a decorator with no arguments like this:: |
| | | |
| | | @subscriber() |
| | | def somefunc(event): |
| | | pass |
| | | |
| | | Would register ``somefunc`` to receive all events sent via the registry, |
| | | but this was untrue. Instead, it would receive no events at all. This has |
| | | now been fixed and the code matches the documentation. See also |
| | | https://github.com/Pylons/pyramid/issues/386 |
| | | |
| | | - Literal portions of route patterns were not URL-quoted when ``route_url`` |
| | | or ``route_path`` was used to generate a URL or path. |
| | | |
| | | - The result of ``route_path`` or ``route_url`` might have been ``unicode`` |
| | | or ``str`` depending on the input. It is now guaranteed to always be |
| | | ``str``. |
| | | |
| | | - URL matching when the pattern contained non-ASCII characters in literal |
| | | parts was indeterminate. Now the pattern supplied to ``add_route`` is |
| | | assumed to be either: a ``unicode`` value, or a ``str`` value that contains |
| | | only ASCII characters. If you now want to match the path info from a URL |
| | | that contains high order characters, you can pass the Unicode |
| | | representation of the decoded path portion in the pattern. |
| | | |
| | | - When using a ``traverse=`` route predicate, traversal would fail with a |
| | | URLDecodeError if there were any high-order characters in the traversal |
| | | pattern or in the matched dynamic segments. |
| | | |
| | | - Using a dynamic segment named ``traverse`` in a route pattern like this:: |
| | | |
| | | config.add_route('trav_route', 'traversal/{traverse:.*}') |
| | | |
| | | Would cause a ``UnicodeDecodeError`` when the route was matched and the |
| | | matched portion of the URL contained any high-order characters. See |
| | | https://github.com/Pylons/pyramid/issues/385 . |
| | | |
| | | - When using a ``*traverse`` stararg in a route pattern, a URL that matched |
| | | that possessed a ``@@`` in its name (signifying a view name) would be |
| | | inappropriately quoted by the traversal machinery during traversal, |
| | | resulting in the view not being found properly. See |
| | | https://github.com/Pylons/pyramid/issues/382 and |
| | | https://github.com/Pylons/pyramid/issues/375 . |
| | | |
| | | Backwards Incompatibilities |
| | | --------------------------- |
| | | |
| | | - String values passed to ``route_url`` or ``route_path`` that are meant to |
| | | replace "remainder" matches will now be URL-quoted except for embedded |
| | | slashes. For example:: |
| | | |
| | | config.add_route('remain', '/foo*remainder') |
| | | request.route_path('remain', remainder='abc / def') |
| | | # -> '/foo/abc%20/%20def' |
| | | |
| | | Previously string values passed as remainder replacements were tacked on |
| | | untouched, without any URL-quoting. But this doesn't really work logically |
| | | if the value passed is Unicode (raw unicode cannot be placed in a URL or in |
| | | a path) and it is inconsistent with the rest of the URL generation |
| | | machinery if the value is a string (it won't be quoted unless by the |
| | | caller). |
| | | |
| | | Some folks will have been relying on the older behavior to tack on query |
| | | string elements and anchor portions of the URL; sorry, you'll need to |
| | | change your code to use the ``_query`` and/or ``_anchor`` arguments to |
| | | ``route_path`` or ``route_url`` to do this now. |
| | | |
| | | - If you pass a bytestring that contains non-ASCII characters to |
| | | ``add_route`` as a pattern, it will now fail at startup time. Use Unicode |
| | | instead. |
| | | |
| | | 1.3a3 (2011-12-21) |
| | | ================== |
| | | |
| | | Features |
| | | -------- |
| | |
| | | - Added a ``prequest`` script (along the lines of ``paster request``). It is |
| | | documented in the "Command-Line Pyramid" chapter in the section entitled |
| | | "Invoking a Request". |
| | | |
| | | - Add undocumented ``__discriminator__`` API to derived view callables. |
| | | e.g. ``adapters.lookup(...).__discriminator__(context, request)``. It will |
| | | be used by superdynamic systems that require the discriminator to be used |
| | | for introspection after manual view lookup. |
| | | |
| | | Bug Fixes |
| | | --------- |
| | |
| | | - Added a section named "Making Your Script into a Console Script" in the |
| | | "Command-Line Pyramid" chapter. |
| | | |
| | | - Removed the "Running Pyramid on Google App Engine" tutorial from the main |
| | | docs. It survives on in the Cookbook |
| | | (http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/gae.html). |
| | | Rationale: it provides the correct info for the Python 2.5 version of GAE |
| | | only, and this version of Pyramid does not support Python 2.5. |
| | | |
| | | 1.3a2 (2011-12-14) |
| | | ================== |
| | | |
| | |
| | | Must-Have |
| | | --------- |
| | | |
| | | - Introspection: |
| | | - Fix deployment recipes in cookbook (discourage proxying without changing |
| | | server). |
| | | |
| | | * Review narrative docs. |
| | | - Use waitress instead of wsgiref. |
| | | |
| | | - pyramid.config.util.ActionInfo.__str__ potentially returns Unicode under |
| | | Py2, fix. |
| | | |
| | | - Tests for view names/route patterns that contain Unicode. |
| | | |
| | | Nice-to-Have |
| | | ------------ |
| | | |
| | | - Modify the urldispatch chapter examples to assume a scan rather than |
| | | ``add_view``. |
| | | |
| | | - Decorator for append_slash_notfound_view_factory? |
| | | |
| | | - Introspection: |
| | | |
| | | * ``default root factory`` category? |
| | | |
| | |
| | | |
| | | Nice-to-Have |
| | | ------------ |
| | | |
| | | - Implement analogue of "paster request"? |
| | | |
| | | - CherryPy server testing / exploded from CherryPy itself. |
| | | |
| | |
| | | as a view renderer argument, Pyramid avoids converting the view callable |
| | | result into a Response object. This is useful if you want to reuse the |
| | | view configuration and lookup machinery outside the context of its use by |
| | | the Pyramid router (e.g. the package named ``pyramid_rpc`` does this). |
| | | the Pyramid router. |
| | | |
| | |
| | | .. autoclass:: Request |
| | | :members: |
| | | :inherited-members: |
| | | :exclude-members: add_response_callback, add_finished_callback, |
| | | route_url, route_path, current_route_url, |
| | | current_route_path, static_url, static_path, |
| | | model_url, resource_url, set_property |
| | | |
| | | .. attribute:: context |
| | | |
| | |
| | | body associated with this request, this property will raise an |
| | | exception. See also :ref:`request_json_body`. |
| | | |
| | | .. method:: set_property(func, name=None, reify=False) |
| | | |
| | | .. versionadded:: 1.3 |
| | | |
| | | Add a callable or a property descriptor to the request instance. |
| | | |
| | | Properties, unlike attributes, are lazily evaluated by executing |
| | | an underlying callable when accessed. They can be useful for |
| | | adding features to an object without any cost if those features |
| | | go unused. |
| | | |
| | | A property may also be reified via the |
| | | :class:`pyramid.decorator.reify` decorator by setting |
| | | ``reify=True``, allowing the result of the evaluation to be |
| | | cached. Thus the value of the property is only computed once for |
| | | the lifetime of the object. |
| | | |
| | | ``func`` can either be a callable that accepts the request as |
| | | its single positional parameter, or it can be a property |
| | | descriptor. |
| | | |
| | | If the ``func`` is a property descriptor a ``ValueError`` will |
| | | be raised if ``name`` is ``None`` or ``reify`` is ``True``. |
| | | |
| | | If ``name`` is None, the name of the property will be computed |
| | | from the name of the ``func``. |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | def _connect(request): |
| | | conn = request.registry.dbsession() |
| | | def cleanup(_): |
| | | conn.close() |
| | | request.add_finished_callback(cleanup) |
| | | return conn |
| | | |
| | | @subscriber(NewRequest) |
| | | def new_request(event): |
| | | request = event.request |
| | | request.set_property(_connect, 'db', reify=True) |
| | | |
| | | The subscriber doesn't actually connect to the database, it just |
| | | provides the API which, when accessed via ``request.db``, will |
| | | create the connection. Thanks to reify, only one connection is |
| | | made per-request even if ``request.db`` is accessed many times. |
| | | |
| | | This pattern provides a way to augment the ``request`` object |
| | | without having to subclass it, which can be useful for extension |
| | | authors. |
| | | |
| | | .. note:: |
| | | |
| | | For information about the API of a :term:`multidict` structure (such as |
| | |
| | | # other places throughout the built documents. |
| | | # |
| | | # The short X.Y version. |
| | | version = '1.3a2' |
| | | version = '1.3a5' |
| | | |
| | | # The full version, including alpha/beta/rc tags. |
| | | release = version |
| | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from pyramid.response import Response # explicit response objects, no TL |
| | | from paste.httpserver import serve # explicitly WSGI |
| | | from pyramid.response import Response # explicit response, no TL |
| | | from wsgiref.simple_server import make_server # explicitly WSGI |
| | | |
| | | def hello_world(request): # accepts a request; no request thread local reqd |
| | | # explicit response object means no response threadlocal |
| | |
| | | config = Configurator() # no global application object. |
| | | config.add_view(hello_world) # explicit non-decorator registration |
| | | app = config.make_wsgi_app() # explicitly WSGI |
| | | serve(app, host='0.0.0.0') # explicitly WSGI |
| | | server = make_server('0.0.0.0', 8080, app) |
| | | server.serve_forever() # explicitly WSGI |
| | | |
| | | Pyramid Doesn't Offer Pluggable Apps |
| | | ------------------------------------ |
| | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from wsgiref.simple_server import make_server |
| | | from pyramid.config import Configurator |
| | | from pyramid.response import Response |
| | | |
| | |
| | | config = Configurator() |
| | | config.add_view(hello_world) |
| | | app = config.make_wsgi_app() |
| | | serve(app) |
| | | server = make_server('0.0.0.0', 8080, app) |
| | | server.serve_forever() |
| | | |
| | | Pyramid has ~ 650 pages of documentation (printed), covering topics from the |
| | | very basic to the most advanced. *Nothing* is left undocumented, quite |
| | |
| | | tutorials/wiki2/index.rst |
| | | tutorials/wiki/index.rst |
| | | tutorials/bfg/index.rst |
| | | tutorials/gae/index.rst |
| | | tutorials/modwsgi/index.rst |
| | | |
| | | Reference Material |
| | |
| | | tutorials/wiki/index.rst |
| | | tutorials/wiki2/index.rst |
| | | tutorials/bfg/index.rst |
| | | tutorials/gae/index.rst |
| | | tutorials/modwsgi/index.rst |
| | | |
| | | .. _api_reference: |
| | |
| | | pyramid.includes = pyramid_debugtoolbar |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | pyramid.default_locale_name = en |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | README = open(os.path.join(here, 'README.txt')).read() |
| | | CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() |
| | | |
| | | requires = ['pyramid', 'pyramid_debugtoolbar'] |
| | | requires = [ |
| | | 'pyramid', |
| | | 'pyramid_debugtoolbar', |
| | | 'waitress', |
| | | ] |
| | | |
| | | setup(name='MyProject', |
| | | version='0.0', |
| | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from wsgiref.simple_server import make_server |
| | | from pyramid.config import Configurator |
| | | from pyramid.response import Response |
| | | |
| | |
| | | config = Configurator() |
| | | config.add_view(hello_world) |
| | | app = config.make_wsgi_app() |
| | | serve(app, host='0.0.0.0') |
| | | server = make_server('0.0.0.0', 8080, app) |
| | | server.serve_forever() |
| | | |
| | | When you start this application, all will be OK. However, what happens if we |
| | | try to add another view to the configuration with the same set of |
| | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from wsgiref.simple_server import make_server |
| | | from pyramid.config import Configurator |
| | | from pyramid.response import Response |
| | | |
| | |
| | | config.add_view(goodbye_world, name='hello') |
| | | |
| | | app = config.make_wsgi_app() |
| | | serve(app, host='0.0.0.0') |
| | | server = make_server('0.0.0.0', 8080, app) |
| | | server.serve_forever() |
| | | |
| | | The application now has two conflicting view configuration statements. When |
| | | we try to start it again, it won't start. Instead, we'll receive a traceback |
| | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from wsgiref.simple_server import make_server |
| | | from pyramid.config import Configurator |
| | | from pyramid.response import Response |
| | | |
| | |
| | | config.add_view(goodbye_world, name='hello') |
| | | |
| | | app = config.make_wsgi_app() |
| | | serve(app, host='0.0.0.0') |
| | | server = make_server('0.0.0.0', 8080, app) |
| | | server.serve_forever() |
| | | |
| | | We can prevent the two ``add_view`` calls from conflicting by issuing a call |
| | | to :meth:`~pyramid.config.Configurator.commit` between them: |
| | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from wsgiref.simple_server import make_server |
| | | from pyramid.config import Configurator |
| | | from pyramid.response import Response |
| | | |
| | |
| | | config.add_view(goodbye_world, name='hello') |
| | | |
| | | app = config.make_wsgi_app() |
| | | serve(app, host='0.0.0.0') |
| | | server = make_server('0.0.0.0', 8080, app) |
| | | server.serve_forever() |
| | | |
| | | In the above example we've issued a call to |
| | | :meth:`~pyramid.config.Configurator.commit` between the two ``add_view`` |
| | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from wsgiref.simple_server import make_server |
| | | from pyramid.config import Configurator |
| | | from pyramid.response import Response |
| | | |
| | |
| | | config = Configurator() |
| | | config.add_view(hello_world) |
| | | app = config.make_wsgi_app() |
| | | serve(app, host='0.0.0.0') |
| | | server = make_server('0.0.0.0', 8080, app) |
| | | server.serve_forever() |
| | | |
| | | We won't talk much about what this application does yet. Just note that the |
| | | "configuration' statements take place underneath the ``if __name__ == |
| | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from wsgiref.simple_server import make_server |
| | | from pyramid.config import Configurator |
| | | from pyramid.response import Response |
| | | from pyramid.view import view_config |
| | | |
| | |
| | | config = Configurator() |
| | | config.scan() |
| | | app = config.make_wsgi_app() |
| | | serve(app, host='0.0.0.0') |
| | | server = make_server('0.0.0.0', 8080, app) |
| | | server.serve_forever() |
| | | |
| | | The scanning machinery imports each module and subpackage in a package or |
| | | module recursively, looking for special attributes attached to objects |
| | |
| | | |
| | | Like many other Python web frameworks, :app:`Pyramid` uses the :term:`WSGI` |
| | | protocol to connect an application and a web server together. The |
| | | :mod:`paste.httpserver` server is used in this example as a WSGI server for |
| | | convenience, as the ``paste`` package is a dependency of :app:`Pyramid` |
| | | itself. |
| | | :mod:`wsgiref` server is used in this example as a WSGI server for |
| | | convenience, as it is shipped within the Python standard library. |
| | | |
| | | The script also imports the :class:`pyramid.response.Response` class for |
| | | later use. An instance of this class will be used to create a web response. |
| | |
| | | :lines: 13 |
| | | |
| | | Finally, we actually serve the application to requestors by starting up a |
| | | WSGI server. We happen to use the :func:`paste.httpserver.serve` WSGI server |
| | | runner, passing it the ``app`` object (a :term:`router`) as the application |
| | | we wish to serve. We also pass in an argument ``host='0.0.0.0'``, meaning |
| | | "listen on all TCP interfaces." By default, the HTTP server listens |
| | | only on the ``127.0.0.1`` interface, which is problematic if you're running |
| | | the server on a remote system and you wish to access it with a web browser |
| | | from a local system. We don't specify a TCP port number to listen on; this |
| | | means we want to use the default TCP port, which is 8080. |
| | | WSGI server. We happen to use the :mod:`wsgiref` ``make_server`` server |
| | | maker for this purpose. We pass in as the first argument ``'0.0.0.0'``, |
| | | which means "listen on all TCP interfaces." By default, the HTTP server |
| | | listens only on the ``127.0.0.1`` interface, which is problematic if you're |
| | | running the server on a remote system and you wish to access it with a web |
| | | browser from a local system. We also specify a TCP port number to listen on, |
| | | which is 8080, passing it as the second argument. The final argument ios , |
| | | passing it the ``app`` object (a :term:`router`), which is the the |
| | | application we wish to serve. Finally, we call the server's |
| | | ``serve_forever`` method, which starts the main loop in which it will wait |
| | | for requests from the outside world. |
| | | |
| | | When this line is invoked, it causes the server to start listening on TCP |
| | | port 8080. The server will serve requests forever, or at least until we stop |
| | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from wsgiref.simple_server import make_server |
| | | from pyramid.config import Configurator |
| | | from mypackage.interfaces import IMyUtility |
| | | |
| | |
| | | config.registry.registerUtility(UtilityImplementation()) |
| | | config.scan() |
| | | app = config.make_wsgi_app() |
| | | serve(app, host='0.0.0.0') |
| | | server = make_server('0.0.0.0', 8080, app) |
| | | server.serve_forever() |
| | | |
| | | For full details, please read the `Venusian documentation |
| | | <http://docs.repoze.org/venusian>`_. |
| | |
| | | |
| | | :app:`Pyramid` is known to run on all popular UNIX-like systems such as |
| | | Linux, MacOS X, and FreeBSD as well as on Windows platforms. It is also |
| | | known to run on Google's App Engine, and :term:`PyPy` (1.6+). |
| | | known to run on :term:`PyPy` (1.6+). |
| | | |
| | | :app:`Pyramid` installation does not require the compilation of any |
| | | C code, so you need only a Python interpreter that meets the |
| | |
| | | .. code-block:: text |
| | | |
| | | c:\env> Scripts\easy_install pyramid |
| | | |
| | | .. index:: |
| | | single: installing on Google App Engine |
| | | |
| | | Installing :app:`Pyramid` on Google App Engine |
| | | ------------------------------------------------- |
| | | |
| | | :ref:`appengine_tutorial` documents the steps required to install a |
| | | :app:`Pyramid` application on Google App Engine. |
| | | |
| | | What Gets Installed |
| | | ------------------- |
| | |
| | | :ref:`firstapp_chapter`. However, all Pyramid scaffolds render PasteDeploy |
| | | configuration files, to provide new developers with a standardized way of |
| | | setting deployment values, and to provide new users with a standardized way |
| | | of starting, stopping, and debugging an application. |
| | | of starting, stopping, and debugging an application. |
| | | |
| | | This chapter is not a replacement for documentation about PasteDeploy; it |
| | | only contextualizes the use of PasteDeploy within Pyramid. For detailed |
| | |
| | | $ ../bin/pserve development.ini --reload |
| | | Starting subprocess with file monitor |
| | | Starting server in PID 16601. |
| | | serving on 0.0.0.0:6543 view at http://127.0.0.1:6543 |
| | | Starting HTTP server on http://0.0.0.0:6543 |
| | | |
| | | For more detailed information about the startup process, see |
| | | :ref:`startup_chapter`. For more information about environment variables and |
| | |
| | | |
| | | The code generated by :app:`Pyramid` scaffolding assumes that you will be |
| | | using the ``pserve`` command to start your application while you do |
| | | development. The default rendering of Pyramid scaffolding uses the *wsgiref* |
| | | WSGI server, which is a server that is ill-suited for production usage: its |
| | | main feature is that it works on all platforms and all systems, making it a |
| | | good choice as a default server from the perspective of Pyramid's developers. |
| | | |
| | | To use a server more suitable for production, you have a number of choices. |
| | | Replace the ``use = egg:pyramid#wsgref`` line in your ``production.ini`` with |
| | | one of the following. |
| | | |
| | | ``use = egg:Paste#http`` |
| | | |
| | | ``paste.httpserver`` is Windows, UNIX, and Python 2 compatible. You'll |
| | | need to ``easy_install Paste`` into your Pyramid virtualenv for this server |
| | | to work. |
| | | |
| | | ``use = egg:pyramid#cherrypy`` |
| | | |
| | | The ``CherryPy`` WSGI server is Windows, UNIX, Python 2, and Python 3 |
| | | compatible. You'll need to ``easy_install CherryPy`` into your Pyramid |
| | | virtualenv for this server to work. |
| | | development. The default rendering of Pyramid scaffolding uses the |
| | | *waitress* WSGI server, which is a server that is suited for production |
| | | usage. It's not very fast, or very featureful: its main feature is that it |
| | | works on all platforms and all systems, making it a good choice as a default |
| | | server from the perspective of Pyramid's developers. |
| | | |
| | | ``pserve`` is by no means the only way to start up and serve a :app:`Pyramid` |
| | | application. As we saw in :ref:`firstapp_chapter`, ``pserve`` needn't be |
| | |
| | | far as ``pserve`` is concerned, it is "just another WSGI application". |
| | | |
| | | #. ``pserve`` starts the WSGI *server* defined within the ``[server:main]`` |
| | | section. In our case, this is the ``egg:pyramid#wsgiref`` server (``use = |
| | | egg:pyramid#wsgiref``), and it will listen on all interfaces (``host = |
| | | section. In our case, this is the Waitress server (``use = |
| | | egg:waitress#main``), and it will listen on all interfaces (``host = |
| | | 0.0.0.0``), on port number 6543 (``port = 6543``). The server code itself |
| | | is what prints ``serving on 0.0.0.0:6543 view at http://127.0.0.1:6543``. |
| | | The server serves the application, and the application is running, waiting |
| | |
| | | |
| | | .. code-block:: text |
| | | |
| | | foo/La%20Pe%C3%B1a |
| | | http://example.com/foo/La%20Pe%C3%B1a |
| | | |
| | | The matchdict will look like so (the value is URL-decoded / UTF-8 decoded): |
| | | |
| | | .. code-block:: text |
| | | |
| | | {'bar':u'La Pe\xf1a'} |
| | | |
| | | Literal strings in the path segment should represent the *decoded* value of |
| | | the ``PATH_INFO`` provided to Pyramid. You don't want to use a URL-encoded |
| | | value or a bytestring representing the literal's UTF-8 in the pattern. For |
| | | example, rather than this: |
| | | |
| | | .. code-block:: text |
| | | |
| | | /Foo%20Bar/{baz} |
| | | |
| | | You'll want to use something like this: |
| | | |
| | | .. code-block:: text |
| | | |
| | | /Foo Bar/{baz} |
| | | |
| | | For patterns that contain "high-order" characters in its literals, you'll |
| | | want to use a Unicode value as the pattern as opposed to any URL-encoded or |
| | | UTF-8-encoded value. For example, you might be tempted to use a bytestring |
| | | pattern like this: |
| | | |
| | | .. code-block:: text |
| | | |
| | | /La Pe\xc3\xb1a/{x} |
| | | |
| | | But this will either cause an error at startup time or it won't match |
| | | properly. You'll want to use a Unicode value as the pattern instead rather |
| | | than raw bytestring escapes. You can use a high-order Unicode value as the |
| | | pattern by using `Python source file encoding |
| | | <http://www.python.org/dev/peps/pep-0263/>`_ plus the "real" character in the |
| | | Unicode pattern in the source, like so: |
| | | |
| | | .. code-block:: text |
| | | |
| | | /La Peña/{x} |
| | | |
| | | Or you can ignore source file encoding and use equivalent Unicode escape |
| | | characters in the pattern. |
| | | |
| | | .. code-block:: text |
| | | |
| | | /La Pe\xf1a/{x} |
| | | |
| | | Dynamic segment names cannot contain high-order characters, so this applies |
| | | only to literals in the pattern. |
| | | |
| | | If the pattern has a ``*`` in it, the name which follows it is considered a |
| | | "remainder match". A remainder match *must* come at the end of the pattern. |
| | |
| | | based on route patterns. For example, if you've configured a route with the |
| | | ``name`` "foo" and the ``pattern`` "{a}/{b}/{c}", you might do this. |
| | | |
| | | .. ignore-next-block |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | |
| | | |
| | | This would return something like the string ``http://example.com/1/2/3`` (at |
| | | least if the current protocol and hostname implied ``http://example.com``). |
| | | See the :meth:`~pyramid.request.Request.route_url` API documentation for more |
| | | information. |
| | | |
| | | To generate only the *path* portion of a URL from a route, use the |
| | | :meth:`pyramid.request.Request.route_path` API instead of |
| | | :meth:`~pyramid.request.Request.route_url`. |
| | | |
| | | .. code-block:: python |
| | | |
| | | url = request.route_path('foo', a='1', b='2', c='3') |
| | | |
| | | This will return the string ``/1/2/3`` rather than a full URL. |
| | | |
| | | Replacement values passed to ``route_url`` or ``route_path`` must be Unicode |
| | | or bytestrings encoded in UTF-8. One exception to this rule exists: if |
| | | you're trying to replace a "remainder" match value (a ``*stararg`` |
| | | replacement value), the value may be a tuple containing Unicode strings or |
| | | UTF-8 strings. |
| | | |
| | | Note that URLs and paths generated by ``route_path`` and ``route_url`` are |
| | | always URL-quoted string types (they contain no non-ASCII characters). |
| | | Therefore, if you've added a route like so: |
| | | |
| | | .. code-block:: python |
| | | |
| | | config.add_route('la', u'/La Peña/{city}') |
| | | |
| | | And you later generate a URL using ``route_path`` or ``route_url`` like so: |
| | | |
| | | .. code-block:: python |
| | | |
| | | url = request.route_path('la', city=u'Québec') |
| | | |
| | | You will wind up with the path encoded to UTF-8 and URL quoted like so: |
| | | |
| | | .. code-block:: text |
| | | |
| | | /La%20Pe%C3%B1a/Qu%C3%A9bec |
| | | |
| | | If you have a ``*stararg`` remainder dynamic part of your route pattern: |
| | | |
| | | .. code-block:: python |
| | | |
| | | config.add_route('abc', 'a/b/c/*foo') |
| | | |
| | | And you later generate a URL using ``route_path`` or ``route_url`` using a |
| | | *string* as the replacement value: |
| | | |
| | | .. code-block:: python |
| | | |
| | | url = request.route_path('abc', foo=u'Québec/biz') |
| | | |
| | | The value you pass will be URL-quoted except for embedded slashes in the |
| | | result: |
| | | |
| | | .. code-block:: text |
| | | |
| | | /a/b/c/Qu%C3%A9bec/biz |
| | | |
| | | You can get a similar result by passing a tuple composed of path elements: |
| | | |
| | | .. code-block:: python |
| | | |
| | | url = request.route_path('abc', foo=(u'Québec', u'biz')) |
| | | |
| | | Each value in the tuple will be url-quoted and joined by slashes in this case: |
| | | |
| | | .. code-block:: text |
| | | |
| | | /a/b/c/Qu%C3%A9bec/biz |
| | | |
| | | .. index:: |
| | | single: static routes |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'ZODB3', |
| | | 'waitress', |
| | | 'docutils', |
| | | ] |
| | | |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'ZODB3', |
| | | 'waitress', |
| | | ] |
| | | |
| | | setup(name='tutorial', |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'ZODB3', |
| | | 'waitress', |
| | | ] |
| | | |
| | | setup(name='tutorial', |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'ZODB3', |
| | | 'waitress', |
| | | 'docutils', |
| | | 'WebTest', # add this |
| | | ] |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'ZODB3', |
| | | 'waitress', |
| | | 'docutils', |
| | | ] |
| | | |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | import os |
| | | import sys |
| | | |
| | | from setuptools import setup, find_packages |
| | | |
| | |
| | | 'pyramid_debugtoolbar', |
| | | 'zope.sqlalchemy', |
| | | 'docutils', |
| | | 'waitress', |
| | | ] |
| | | |
| | | if sys.version_info[:3] < (2,5,0): |
| | | requires.append('pysqlite') |
| | | |
| | | setup(name='tutorial', |
| | | version='0.0', |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | import os |
| | | import sys |
| | | |
| | | from setuptools import setup, find_packages |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'zope.sqlalchemy', |
| | | 'waitress', |
| | | ] |
| | | |
| | | if sys.version_info[:3] < (2,5,0): |
| | | requires.append('pysqlite') |
| | | |
| | | setup(name='tutorial', |
| | | version='0.0', |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | import os |
| | | import sys |
| | | |
| | | from setuptools import setup, find_packages |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'zope.sqlalchemy', |
| | | 'waitress', |
| | | ] |
| | | |
| | | if sys.version_info[:3] < (2,5,0): |
| | | requires.append('pysqlite') |
| | | |
| | | setup(name='tutorial', |
| | | version='0.0', |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | import os |
| | | import sys |
| | | |
| | | from setuptools import setup, find_packages |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'zope.sqlalchemy', |
| | | 'waitress', |
| | | 'docutils', |
| | | 'WebTest', # add this |
| | | ] |
| | | |
| | | if sys.version_info[:3] < (2,5,0): |
| | | requires.append('pysqlite') |
| | | |
| | | setup(name='tutorial', |
| | | version='0.0', |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/tutorial.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | import os |
| | | import sys |
| | | |
| | | from setuptools import setup, find_packages |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'zope.sqlalchemy', |
| | | 'waitress', |
| | | 'docutils', |
| | | ] |
| | | |
| | | if sys.version_info[:3] < (2,5,0): |
| | | requires.append('pysqlite') |
| | | |
| | | setup(name='tutorial', |
| | | version='0.0', |
| | |
| | | .. literalinclude:: src/tests/setup.py |
| | | :linenos: |
| | | :language: python |
| | | :lines: 10-19 |
| | | :lines: 9-20 |
| | | |
| | | After we've added a dependency on WebTest in ``setup.py``, we need to rerun |
| | | ``setup.py develop`` to get WebTest installed into our virtualenv. Assuming |
| | |
| | | |
| | | - We've replaced the ``paster`` command with Pyramid-specific analogues. |
| | | |
| | | - We've made the default WSGI server the ``wsgiref`` server. |
| | | - We've made the default WSGI server the ``waitress`` server. |
| | | |
| | | Previously (in Pyramid 1.0, 1.1 and 1.2), you created a Pyramid application |
| | | using ``paster create``, like so:: |
| | |
| | | ``paster ptweens`` also exist under the respective console script names |
| | | ``pshell``, ``pviews``, ``prequest`` and ``ptweens``. |
| | | |
| | | We've replaced use of the Paste ``httpserver`` with the ``wsgiref`` server in |
| | | We've replaced use of the Paste ``httpserver`` with the ``waitress`` server in |
| | | the scaffolds, so once you create a project from a scaffold, its |
| | | ``development.ini`` and ``production.ini`` will have the following line:: |
| | | |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | |
| | | Instead of this (which was the default in older versions):: |
| | | |
| | | use = egg:Paste#http |
| | | |
| | | Using ``wsgiref`` as the default WSGI server is purely a default to make it |
| | | possible to use the same scaffolding under Python 2 and Python 3; people |
| | | running Pyramid under Python 2 can still manually install ``Paste`` and use |
| | | the Paste ``httpserver`` by replacing the former line with the latter. This is |
| | | actually recommended if you rely on proxying from Apache or Nginx to a |
| | | ``pserve`` -invoked application. **The wsgiref server is not a production |
| | | quality server.** See :ref:`alternate_wsgi_server` for more information. |
| | | |
| | | New releases in every older major Pyramid series (1.0.2, 1.1.3, 1.2.5) also |
| | | have the ``egg:pyramid#wsgiref`` entry point, so scaffold-writers can depend |
| | | on it being there even in older major Pyramid versions. |
| | | |
| | | .. warning:: |
| | | |
| | | Previously, paste.httpserver "helped" by converting header values that weren't |
| | | strings to strings. The wsgiref server, on the other hand implements the spec |
| | | more fully. This specifically may affect you if you are modifying headers on |
| | | your response. The following error might be an indicator of this problem: |
| | | **AssertionError: Header values must be strings, please check the type of |
| | | the header being returned.** A common case would be returning unicode headers |
| | | instead of string headers. |
| | | Previously, paste.httpserver "helped" by converting header values that |
| | | weren't strings to strings. The ``waitress`` server, on the other hand |
| | | implements the spec more fully. This specifically may affect you if you |
| | | are modifying headers on your response. The following error might be an |
| | | indicator of this problem: **AssertionError: Header values must be |
| | | strings, please check the type of the header being returned.** A common |
| | | case would be returning unicode headers instead of string headers. |
| | | |
| | | A new :mod:`pyramid.compat` module was added which provides Python 2/3 |
| | | straddling support for Pyramid add-ons and development environments. |
| | |
| | | |
| | | See :ref:`view_defaults` for more information. |
| | | |
| | | Extending a Request without Subclassing |
| | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| | | |
| | | It is now possible to extend a :class:`pyramid.request.Request` object |
| | | with property descriptors without having to create a subclass via |
| | | :meth:`pyramid.request.Request.set_property`. New properties may be |
| | | reified, effectively caching the value for the lifetime of the instance. |
| | | Common use-cases for this would be to get a database connection for the |
| | | request or identify the current user. |
| | | |
| | | Minor Feature Additions |
| | | ----------------------- |
| | | |
| | |
| | | Python 3. |
| | | |
| | | - The default WSGI server run as the result of ``pserve`` from newly rendered |
| | | scaffolding is now the ``wsgiref`` WSGI server instead of the |
| | | ``paste.httpserver`` server. ``wsgiref``, unlike the server it replaced |
| | | (``paste.httpserver``) is not a production quality server. See |
| | | :ref:`alternate_wsgi_server` for information about how to use another WSGI |
| | | server in production. Rationale: the Paste and PasteScript packages do not |
| | | run under Python 3. |
| | | scaffolding is now the ``waitress`` WSGI server instead of the |
| | | ``paste.httpserver`` server. Rationale: the Paste and PasteScript packages |
| | | do not run under Python 3. |
| | | |
| | | - The ``pshell`` command (see "paster pshell") no longer accepts a |
| | | ``--disable-ipython`` command-line argument. Instead, it accepts a ``-p`` |
| | |
| | | Pyramid 1.0, so you won't be warned if you have older versions installed |
| | | and upgrade Pyramid itself "in-place"; it may simply break instead |
| | | (particularly if you use ZCML's ``includeOverrides`` directive). |
| | | |
| | | - String values passed to ``route_url`` or ``route_path`` that are meant to |
| | | replace "remainder" matches will now be URL-quoted except for embedded |
| | | slashes. For example:: |
| | | |
| | | config.add_route('remain', '/foo*remainder') |
| | | request.route_path('remain', remainder='abc / def') |
| | | # -> '/foo/abc%20/%20def' |
| | | |
| | | Previously string values passed as remainder replacements were tacked on |
| | | untouched, without any URL-quoting. But this doesn't really work logically |
| | | if the value passed is Unicode (raw unicode cannot be placed in a URL or in |
| | | a path) and it is inconsistent with the rest of the URL generation |
| | | machinery if the value is a string (it won't be quoted unless by the |
| | | caller). |
| | | |
| | | Some folks will have been relying on the older behavior to tack on query |
| | | string elements and anchor portions of the URL; sorry, you'll need to |
| | | change your code to use the ``_query`` and/or ``_anchor`` arguments to |
| | | ``route_path`` or ``route_url`` to do this now. |
| | | |
| | | - If you pass a bytestring that contains non-ASCII characters to |
| | | ``add_route`` as a pattern, it will now fail at startup time. Use Unicode |
| | | instead. |
| | | |
| | | Documentation Enhancements |
| | | -------------------------- |
| | |
| | | - Added a section to the "Command-Line Pyramid" chapter named |
| | | :ref:`making_a_console_script`. |
| | | |
| | | - Removed the "Running Pyramid on Google App Engine" tutorial from the main |
| | | docs. It survives on in the Cookbook |
| | | (http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/gae.html). |
| | | Rationale: it provides the correct info for the Python 2.5 version of GAE |
| | | only, and this version of Pyramid does not support Python 2.5. |
| | | |
| | | Dependency Changes |
| | | ------------------ |
| | | |
| | |
| | | |
| | | def text_(s, encoding='latin-1', errors='strict'): |
| | | """ If ``s`` is an instance of ``binary_type``, return |
| | | ``s.encode(encoding, errors)``, otherwise return ``s``""" |
| | | ``s.decode(encoding, errors)``, otherwise return ``s``""" |
| | | if isinstance(s, binary_type): |
| | | return s.decode(encoding, errors) |
| | | return s # pragma: no cover |
| | |
| | | def bytes_(s, encoding='latin-1', errors='strict'): |
| | | """ If ``s`` is an instance of ``text_type``, return |
| | | ``s.encode(encoding, errors)``, otherwise return ``s``""" |
| | | if isinstance(s, text_type): |
| | | if isinstance(s, text_type): # pragma: no cover |
| | | return s.encode(encoding, errors) |
| | | return s |
| | | |
| | |
| | | from urllib import unquote as url_unquote |
| | | from urllib import urlencode as url_encode |
| | | from urllib2 import urlopen as url_open |
| | | def url_unquote_text(v, encoding='utf-8', errors='replace'): |
| | | def url_unquote_text(v, encoding='utf-8', errors='replace'): # pragma: no cover |
| | | v = url_unquote(v) |
| | | return v.decode(encoding, errors) |
| | | def url_unquote_native(v, encoding='utf-8', errors='replace'): |
| | | def url_unquote_native(v, encoding='utf-8', errors='replace'): # pragma: no cover |
| | | return native_(url_unquote_text(v, encoding, errors)) |
| | | |
| | | |
| | |
| | | import json |
| | | |
| | | |
| | | if PY3: # pragma: no cover |
| | | # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before |
| | | # decoding it to utf-8 |
| | | def decode_path_info(path): |
| | | return path.encode('latin-1').decode('utf-8') |
| | | else: |
| | | def decode_path_info(path): |
| | | return path.decode('utf-8') |
| | | |
| | | if PY3: # pragma: no cover |
| | | # see PEP 3333 for why we decode the path to latin-1 |
| | | from urllib.parse import unquote_to_bytes |
| | | def unquote_bytes_to_wsgi(bytestring): |
| | | return unquote_to_bytes(bytestring).decode('latin-1') |
| | | else: |
| | | from urlparse import unquote as unquote_to_bytes |
| | | def unquote_bytes_to_wsgi(bytestring): |
| | | return unquote_to_bytes(bytestring) |
| | |
| | | ) |
| | | |
| | | from pyramid.renderers import RendererHelper |
| | | from pyramid.traversal import traversal_path_info |
| | | |
| | | from pyramid.traversal import ( |
| | | decode_path_info, |
| | | split_path_info, |
| | | ) |
| | | |
| | | from pyramid.config.util import action_method |
| | | |
| | |
| | | self.context = context |
| | | |
| | | def __call__(self, request): |
| | | path = request.environ['PATH_INFO'] |
| | | path = decode_path_info(request.environ['PATH_INFO']) |
| | | ob = resources[path] |
| | | traversed = traversal_path_info(path) |
| | | traversed = split_path_info(path) |
| | | return {'context':ob, 'view_name':'','subpath':(), |
| | | 'traversed':traversed, 'virtual_root':ob, |
| | | 'virtual_root_path':(), 'root':ob} |
| | |
| | | |
| | | from pyramid.traversal import ( |
| | | find_interface, |
| | | traversal_path_info, |
| | | traversal_path, |
| | | ) |
| | | |
| | | from hashlib import md5 |
| | |
| | | except re.error as why: |
| | | raise ConfigurationError(why.args[0]) |
| | | def path_info_predicate(context, request): |
| | | return path_info_val.match(request.path_info) is not None |
| | | return path_info_val.match(request.upath_info) is not None |
| | | text = "path_info = %s" |
| | | path_info_predicate.__text__ = text % path_info |
| | | weights.append(1 << 3) |
| | |
| | | if 'traverse' in context: |
| | | return True |
| | | m = context['match'] |
| | | tvalue = tgenerate(m) |
| | | m['traverse'] = traversal_path_info(tvalue) |
| | | tvalue = tgenerate(m) # tvalue will be urlquoted string |
| | | m['traverse'] = traversal_path(tvalue) # will be seq of unicode |
| | | return True |
| | | # This isn't actually a predicate, it's just a infodict |
| | | # modifier that injects ``traverse`` into the matchdict. As a |
| | |
| | | self.views = [] |
| | | self.accepts = [] |
| | | |
| | | def __discriminator__(self, context, request): |
| | | # used by introspection systems like so: |
| | | # view = adapters.lookup(....) |
| | | # view.__discriminator__(context, request) -> view's discriminator |
| | | # so that superdynamic systems can feed the discriminator to |
| | | # the introspection system to get info about it |
| | | view = self.match(context, request) |
| | | return view.__discriminator__(context, request) |
| | | |
| | | def add(self, view, order, accept=None, phash=None): |
| | | if phash is not None: |
| | | for i, (s, v, h) in enumerate(list(self.views)): |
| | |
| | | decorator=decorator, |
| | | http_cache=http_cache) |
| | | derived_view = deriver(view) |
| | | derived_view.__discriminator__ = lambda *arg: discriminator |
| | | # __discriminator__ is used by superdynamic systems |
| | | # that require it for introspection after manual view lookup; |
| | | # see also MultiView.__discriminator__ |
| | | view_intr['derived_callable'] = derived_view |
| | | |
| | | registered = self.registry.adapters.registered |
| | |
| | | import venusian |
| | | |
| | | from zope.interface import implementer |
| | | from zope.interface import ( |
| | | implementer, |
| | | Interface |
| | | ) |
| | | |
| | | from pyramid.interfaces import ( |
| | | IContextFound, |
| | |
| | | def mysubscriber(event): |
| | | event.request.foo = 1 |
| | | |
| | | More than one event type can be passed as a construtor argument. The |
| | | More than one event type can be passed as a constructor argument. The |
| | | decorated subscriber will be called for each event type. |
| | | |
| | | .. code-block:: python |
| | |
| | | |
| | | def register(self, scanner, name, wrapped): |
| | | config = scanner.config |
| | | for iface in self.ifaces: |
| | | for iface in self.ifaces or (Interface,): |
| | | config.add_subscriber(wrapped, iface) |
| | | |
| | | def __call__(self, wrapped): |
| | |
| | | from pyramid.decorator import reify |
| | | from pyramid.response import Response |
| | | from pyramid.url import URLMethodsMixin |
| | | from pyramid.util import InstancePropertyMixin |
| | | |
| | | class TemplateContext(object): |
| | | pass |
| | |
| | | |
| | | @implementer(IRequest) |
| | | class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin, |
| | | CallbackMethodsMixin): |
| | | CallbackMethodsMixin, InstancePropertyMixin): |
| | | """ |
| | | A subclass of the :term:`WebOb` Request class. An instance of |
| | | this class is created by the :term:`router` and is provided to a |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/{{project}}.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | sqlalchemy.url = sqlite:///%(here)s/{{project}}.db |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | import os |
| | | import sys |
| | | |
| | | from setuptools import setup, find_packages |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'zope.sqlalchemy', |
| | | 'waitress', |
| | | ] |
| | | |
| | | setup(name='{{project}}', |
| | |
| | | pyramid.includes = pyramid_debugtoolbar |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | pyramid.default_locale_name = en |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | README = open(os.path.join(here, 'README.txt')).read() |
| | | CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() |
| | | |
| | | requires = ['pyramid', 'pyramid_debugtoolbar'] |
| | | requires = [ |
| | | 'pyramid', |
| | | 'pyramid_debugtoolbar', |
| | | 'waitress', |
| | | ] |
| | | |
| | | setup(name='{{project}}', |
| | | version='0.0', |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 |
| | | |
| | | [server:main] |
| | | use = egg:pyramid#wsgiref |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | port = 6543 |
| | | |
| | |
| | | 'pyramid_tm', |
| | | 'pyramid_debugtoolbar', |
| | | 'ZODB3', |
| | | 'waitress', |
| | | ] |
| | | |
| | | setup(name='{{project}}', |
| | |
| | | |
| | | from pyramid.tests.test_config import dummyfactory |
| | | from pyramid.tests.test_config import DummyContext |
| | | from pyramid.compat import text_ |
| | | |
| | | class RoutesConfiguratorMixinTests(unittest.TestCase): |
| | | def _makeOne(self, *arg, **kw): |
| | |
| | | route = self._assertRoute(config, 'name', 'path', 1) |
| | | predicate = route.predicates[0] |
| | | request = self._makeRequest(config) |
| | | request.path_info = '/foo' |
| | | request.upath_info = '/foo' |
| | | self.assertEqual(predicate(None, request), True) |
| | | request = self._makeRequest(config) |
| | | request.path_info = '/' |
| | | request.upath_info = '/' |
| | | self.assertEqual(predicate(None, request), False) |
| | | |
| | | def test_add_route_with_path_info_highorder(self): |
| | | config = self._makeOne(autocommit=True) |
| | | config.add_route('name', 'path', |
| | | path_info=text_(b'/La Pe\xc3\xb1a', 'utf-8')) |
| | | route = self._assertRoute(config, 'name', 'path', 1) |
| | | predicate = route.predicates[0] |
| | | request = self._makeRequest(config) |
| | | request.upath_info = text_(b'/La Pe\xc3\xb1a', 'utf-8') |
| | | self.assertEqual(predicate(None, request), True) |
| | | request = self._makeRequest(config) |
| | | request.upath_info = text_('/') |
| | | self.assertEqual(predicate(None, request), False) |
| | | |
| | | def test_add_route_with_path_info_regex(self): |
| | | config = self._makeOne(autocommit=True) |
| | | config.add_route('name', 'path', |
| | | path_info=text_(br'/La Pe\w*', 'utf-8')) |
| | | route = self._assertRoute(config, 'name', 'path', 1) |
| | | predicate = route.predicates[0] |
| | | request = self._makeRequest(config) |
| | | request.upath_info = text_(b'/La Pe\xc3\xb1a', 'utf-8') |
| | | self.assertEqual(predicate(None, request), True) |
| | | request = self._makeRequest(config) |
| | | request.upath_info = text_('/') |
| | | self.assertEqual(predicate(None, request), False) |
| | | |
| | | def test_add_route_with_request_param(self): |
| | |
| | | import unittest |
| | | from pyramid.compat import text_ |
| | | |
| | | class Test__make_predicates(unittest.TestCase): |
| | | def _callFUT(self, **kw): |
| | |
| | | self.assertEqual(info, {'match': |
| | | {'a':'a', 'b':'b', 'traverse':('1', 'a', 'b')}}) |
| | | |
| | | def test_traverse_matches_with_highorder_chars(self): |
| | | order, predicates, phash = self._callFUT( |
| | | traverse=text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8')) |
| | | self.assertEqual(len(predicates), 1) |
| | | pred = predicates[0] |
| | | info = {'match':{'x':text_(b'Qu\xc3\xa9bec', 'utf-8')}} |
| | | request = DummyRequest() |
| | | result = pred(info, request) |
| | | self.assertEqual(result, True) |
| | | self.assertEqual( |
| | | info['match']['traverse'], |
| | | (text_(b'La Pe\xc3\xb1a', 'utf-8'), |
| | | text_(b'Qu\xc3\xa9bec', 'utf-8')) |
| | | ) |
| | | |
| | | def test_custom_predicates_can_affect_traversal(self): |
| | | def custom(info, request): |
| | | m = info['match'] |
| | |
| | | |
| | | from pyramid.tests.test_config import dummy_view |
| | | |
| | | from pyramid.compat import im_func |
| | | from pyramid.compat import ( |
| | | im_func, |
| | | text_, |
| | | ) |
| | | from pyramid.exceptions import ConfigurationError |
| | | from pyramid.exceptions import ConfigurationExecutionError |
| | | from pyramid.exceptions import ConfigurationConflictError |
| | |
| | | self.assertEqual(wrapper.__module__, view.__module__) |
| | | self.assertEqual(wrapper.__name__, view.__name__) |
| | | self.assertEqual(wrapper.__doc__, view.__doc__) |
| | | self.assertEqual(wrapper.__discriminator__(None, None)[0], 'view') |
| | | |
| | | def test_add_view_view_callable_dottedname(self): |
| | | from pyramid.renderers import null_renderer |
| | |
| | | config = self._makeOne(autocommit=True) |
| | | config.add_view(view=view, renderer=null_renderer) |
| | | wrapper = self._getViewCallable(config) |
| | | result = wrapper(None, None) |
| | | self.assertEqual(result, 'OK') |
| | | |
| | | def test_add_view_with_name(self): |
| | | from pyramid.renderers import null_renderer |
| | | view = lambda *arg: 'OK' |
| | | config = self._makeOne(autocommit=True) |
| | | config.add_view(view=view, name='abc', renderer=null_renderer) |
| | | wrapper = self._getViewCallable(config, name='abc') |
| | | result = wrapper(None, None) |
| | | self.assertEqual(result, 'OK') |
| | | |
| | | def test_add_view_with_name_unicode(self): |
| | | from pyramid.renderers import null_renderer |
| | | view = lambda *arg: 'OK' |
| | | config = self._makeOne(autocommit=True) |
| | | name = text_(b'La Pe\xc3\xb1a', 'utf-8') |
| | | config.add_view(view=view, name=name, renderer=null_renderer) |
| | | wrapper = self._getViewCallable(config, name=name) |
| | | result = wrapper(None, None) |
| | | self.assertEqual(result, 'OK') |
| | | |
| | |
| | | request.params = {'param':'1'} |
| | | self.assertEqual(wrapper(ctx, request), 'view8') |
| | | |
| | | def test_add_view_multiview___discriminator__(self): |
| | | from pyramid.renderers import null_renderer |
| | | from zope.interface import Interface |
| | | class IFoo(Interface): |
| | | pass |
| | | class IBar(Interface): |
| | | pass |
| | | @implementer(IFoo) |
| | | class Foo(object): |
| | | pass |
| | | @implementer(IBar) |
| | | class Bar(object): |
| | | pass |
| | | foo = Foo() |
| | | bar = Bar() |
| | | |
| | | from pyramid.interfaces import IRequest |
| | | from pyramid.interfaces import IView |
| | | from pyramid.interfaces import IViewClassifier |
| | | from pyramid.interfaces import IMultiView |
| | | view = lambda *arg: 'OK' |
| | | view.__phash__ = 'abc' |
| | | config = self._makeOne(autocommit=True) |
| | | config.registry.registerAdapter( |
| | | view, (IViewClassifier, IRequest, Interface), IView, name='') |
| | | config.add_view(view=view, renderer=null_renderer, |
| | | containment=IFoo) |
| | | config.add_view(view=view, renderer=null_renderer, |
| | | containment=IBar) |
| | | wrapper = self._getViewCallable(config) |
| | | self.assertTrue(IMultiView.providedBy(wrapper)) |
| | | request = self._makeRequest(config) |
| | | self.assertEqual(wrapper.__discriminator__(foo, request)[5], IFoo) |
| | | self.assertEqual(wrapper.__discriminator__(bar, request)[5], IBar) |
| | | |
| | | def test_add_view_with_template_renderer(self): |
| | | from pyramid.tests import test_config |
| | | from pyramid.interfaces import ISettings |
| | |
| | | config.add_view(view=view, path_info='/foo', renderer=null_renderer) |
| | | wrapper = self._getViewCallable(config) |
| | | request = self._makeRequest(config) |
| | | request.path_info = '/foo' |
| | | request.upath_info = text_(b'/foo') |
| | | self.assertEqual(wrapper(None, request), 'OK') |
| | | |
| | | def test_add_view_with_path_info_nomatch(self): |
| | |
| | | config.add_view(view=view, path_info='/foo') |
| | | wrapper = self._getViewCallable(config) |
| | | request = self._makeRequest(config) |
| | | request.path_info = '/' |
| | | request.upath_info = text_('/') |
| | | self._assertNotFound(wrapper, None, request) |
| | | |
| | | def test_add_view_with_custom_predicates_match(self): |
| | |
| | | directlyProvides(context, IDummy) |
| | | request = self._makeRequest(config) |
| | | self.assertEqual(wrapper(context, request), 'OK') |
| | | |
| | | context = DummyContext() |
| | | request = self._makeRequest(config) |
| | | self.assertRaises(PredicateMismatch, wrapper, context, request) |
| | | |
| | | def test_add_view_with_view_config_and_view_defaults_doesnt_conflict(self): |
| | | from pyramid.renderers import null_renderer |
| | | class view(object): |
| | | __view_defaults__ = { |
| | | 'containment':'pyramid.tests.test_config.IDummy' |
| | | } |
| | | class view2(object): |
| | | __view_defaults__ = { |
| | | 'containment':'pyramid.tests.test_config.IFactory' |
| | | } |
| | | config = self._makeOne(autocommit=False) |
| | | config.add_view( |
| | | view=view, |
| | | renderer=null_renderer) |
| | | config.add_view( |
| | | view=view2, |
| | | renderer=null_renderer) |
| | | config.commit() # does not raise |
| | | |
| | | def test_add_view_with_view_config_and_view_defaults_conflicts(self): |
| | | from pyramid.renderers import null_renderer |
| | | class view(object): |
| | | __view_defaults__ = { |
| | | 'containment':'pyramid.tests.test_config.IDummy' |
| | | } |
| | | class view2(object): |
| | | __view_defaults__ = { |
| | | 'containment':'pyramid.tests.test_config.IDummy' |
| | | } |
| | | config = self._makeOne(autocommit=False) |
| | | config.add_view( |
| | | view=view, |
| | | renderer=null_renderer) |
| | | config.add_view( |
| | | view=view2, |
| | | renderer=null_renderer) |
| | | self.assertRaises(ConfigurationConflictError, config.commit) |
| | | |
| | | def test_derive_view_function(self): |
| | | from pyramid.renderers import null_renderer |
| | |
| | | dec.register(scanner, None, foo) |
| | | self.assertEqual(config.subscribed, [(foo, IFoo), (foo, IBar)]) |
| | | |
| | | def test_register_none_means_all(self): |
| | | from zope.interface import Interface |
| | | dec = self._makeOne() |
| | | def foo(): pass |
| | | config = DummyConfigurator() |
| | | scanner = Dummy() |
| | | scanner.config = config |
| | | dec.register(scanner, None, foo) |
| | | self.assertEqual(config.subscribed, [(foo, Interface)]) |
| | | |
| | | def test_register_objectevent(self): |
| | | from zope.interface import Interface |
| | | class IFoo(Interface): pass |
| | |
| | | request = self._makeOne({'REQUEST_METHOD':'GET'}) |
| | | self.assertRaises(ValueError, getattr, request, 'json_body') |
| | | |
| | | def test_set_property(self): |
| | | request = self._makeOne({}) |
| | | opts = [2, 1] |
| | | def connect(obj): |
| | | return opts.pop() |
| | | request.set_property(connect, name='db') |
| | | self.assertEqual(1, request.db) |
| | | self.assertEqual(2, request.db) |
| | | |
| | | def test_set_property_reify(self): |
| | | request = self._makeOne({}) |
| | | opts = [2, 1] |
| | | def connect(obj): |
| | | return opts.pop() |
| | | request.set_property(connect, name='db', reify=True) |
| | | self.assertEqual(1, request.db) |
| | | self.assertEqual(1, request.db) |
| | | |
| | | class TestRequestDeprecatedMethods(unittest.TestCase): |
| | | def setUp(self): |
| | | self.config = testing.setUp() |
| | |
| | | import unittest |
| | | |
| | | from pyramid.testing import cleanUp |
| | | from pyramid.compat import text_ |
| | | from pyramid.compat import native_ |
| | | from pyramid.compat import text_type |
| | | from pyramid.compat import url_quote |
| | | from pyramid.compat import ( |
| | | text_, |
| | | native_, |
| | | text_type, |
| | | url_quote, |
| | | PY3, |
| | | ) |
| | | |
| | | class TraversalPathTests(unittest.TestCase): |
| | | def _callFUT(self, path): |
| | |
| | | self.assertEqual(result['view_name'], '') |
| | | self.assertEqual(result['subpath'], ()) |
| | | self.assertEqual(result['traversed'], ()) |
| | | self.assertEqual(result['root'], policy.root) |
| | | self.assertEqual(result['virtual_root'], policy.root) |
| | | self.assertEqual(result['virtual_root_path'], ()) |
| | | |
| | | def test_call_with_pathinfo_highorder(self): |
| | | foo = DummyContext(None, text_(b'Qu\xc3\xa9bec', 'utf-8')) |
| | | root = DummyContext(foo, 'root') |
| | | policy = self._makeOne(root) |
| | | if PY3: # pragma: no cover |
| | | path_info = b'/Qu\xc3\xa9bec'.decode('latin-1') |
| | | else: |
| | | path_info = b'/Qu\xc3\xa9bec' |
| | | environ = self._getEnviron(PATH_INFO=path_info) |
| | | request = DummyRequest(environ) |
| | | result = policy(request) |
| | | self.assertEqual(result['context'], foo) |
| | | self.assertEqual(result['view_name'], '') |
| | | self.assertEqual(result['subpath'], ()) |
| | | self.assertEqual( |
| | | result['traversed'], |
| | | (text_(b'Qu\xc3\xa9bec', 'utf-8'),) |
| | | ) |
| | | self.assertEqual(result['root'], policy.root) |
| | | self.assertEqual(result['virtual_root'], policy.root) |
| | | self.assertEqual(result['virtual_root_path'], ()) |
| | |
| | | self.assertEqual(result['virtual_root'], policy.root) |
| | | self.assertEqual(result['virtual_root_path'], ()) |
| | | |
| | | def test_call_with_vh_root_highorder(self): |
| | | bar = DummyContext(None, 'bar') |
| | | foo = DummyContext(bar, text_(b'Qu\xc3\xa9bec', 'utf-8')) |
| | | root = DummyContext(foo, 'root') |
| | | policy = self._makeOne(root) |
| | | if PY3: # pragma: no cover |
| | | vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1') |
| | | else: |
| | | vhm_root = b'/Qu\xc3\xa9bec' |
| | | environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root, |
| | | PATH_INFO='/bar') |
| | | request = DummyRequest(environ) |
| | | result = policy(request) |
| | | self.assertEqual(result['context'], bar) |
| | | self.assertEqual(result['view_name'], '') |
| | | self.assertEqual(result['subpath'], ()) |
| | | self.assertEqual( |
| | | result['traversed'], |
| | | (text_(b'Qu\xc3\xa9bec', 'utf-8'), text_('bar')) |
| | | ) |
| | | self.assertEqual(result['root'], policy.root) |
| | | self.assertEqual(result['virtual_root'], foo) |
| | | self.assertEqual( |
| | | result['virtual_root_path'], |
| | | (text_(b'Qu\xc3\xa9bec', 'utf-8'),) |
| | | ) |
| | | |
| | | def test_non_utf8_path_segment_unicode_path_segments_fails(self): |
| | | from pyramid.exceptions import URLDecodeError |
| | | foo = DummyContext() |
| | |
| | | import unittest |
| | | from pyramid import testing |
| | | from pyramid.compat import text_ |
| | | from pyramid.compat import native_ |
| | | from pyramid.compat import ( |
| | | text_, |
| | | PY3, |
| | | ) |
| | | |
| | | class TestRoute(unittest.TestCase): |
| | | def _getTargetClass(self): |
| | |
| | | 'archives/:action/:article2') |
| | | self.assertEqual(mapper.routelist[0].pattern, |
| | | 'archives/:action/:article2') |
| | | |
| | | def test___call__pathinfo_cant_be_decoded(self): |
| | | from pyramid.exceptions import URLDecodeError |
| | | mapper = self._makeOne() |
| | | if PY3: # pragma: no cover |
| | | path_info = b'\xff\xfe\xe6\x00'.decode('latin-1') |
| | | else: |
| | | path_info = b'\xff\xfe\xe6\x00' |
| | | request = self._getRequest(PATH_INFO=path_info) |
| | | self.assertRaises(URLDecodeError, mapper, request) |
| | | |
| | | def test___call__route_matches(self): |
| | | mapper = self._makeOne() |
| | |
| | | self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) |
| | | self.assertEqual(generator({'baz':1, 'buz':2}), '/foo/1/biz/2/bar') |
| | | |
| | | def test_url_decode_error(self): |
| | | from pyramid.exceptions import URLDecodeError |
| | | matcher, generator = self._callFUT('/:foo') |
| | | self.assertRaises(URLDecodeError, matcher, |
| | | native_(b'/\xff\xfe\x8b\x00')) |
| | | |
| | | def test_custom_regex(self): |
| | | matcher, generator = self._callFUT('foo/{baz}/biz/{buz:[^/\.]+}.{bar}') |
| | | self.assertEqual(matcher('/foo/baz/biz/buz.bar'), |
| | |
| | | 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}}') |
| | | 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('/2001-No-15'), None) |
| | | self.assertEqual(generator({'buz':'2001-Nov-15'}), '/2001-Nov-15') |
| | | self.assertEqual(generator({'buz':'99-June-10'}), '/99-June-10') |
| | | |
| | | def test_pattern_with_high_order_literal(self): |
| | | pattern = text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8') |
| | | matcher, generator = self._callFUT(pattern) |
| | | self.assertEqual(matcher(text_(b'/La Pe\xc3\xb1a/x', 'utf-8')), |
| | | {'x':'x'}) |
| | | self.assertEqual(generator({'x':'1'}), '/La%20Pe%C3%B1a/1') |
| | | |
| | | def test_pattern_generate_with_high_order_dynamic(self): |
| | | pattern = '/{x}' |
| | | _, generator = self._callFUT(pattern) |
| | | self.assertEqual( |
| | | generator({'x':text_(b'La Pe\xc3\xb1a', 'utf-8')}), |
| | | '/La%20Pe%C3%B1a') |
| | | |
| | | def test_docs_sample_generate(self): |
| | | # sample from urldispatch.rst |
| | | pattern = text_(b'/La Pe\xc3\xb1a/{city}', 'utf-8') |
| | | _, generator = self._callFUT(pattern) |
| | | self.assertEqual( |
| | | generator({'city':text_(b'Qu\xc3\xa9bec', 'utf-8')}), |
| | | '/La%20Pe%C3%B1a/Qu%C3%A9bec') |
| | | |
| | | def test_generate_with_mixedtype_values(self): |
| | | pattern = '/{city}/{state}' |
| | | _, generator = self._callFUT(pattern) |
| | | result = generator( |
| | | {'city': text_(b'Qu\xc3\xa9bec', 'utf-8'), |
| | | 'state': b'La Pe\xc3\xb1a'} |
| | | ) |
| | | self.assertEqual(result, '/Qu%C3%A9bec/La%20Pe%C3%B1a') |
| | | # should be a native string |
| | | self.assertEqual(type(result), str) |
| | | |
| | | def test_highorder_pattern_utf8(self): |
| | | pattern = b'/La Pe\xc3\xb1a/{city}' |
| | | self.assertRaises(ValueError, self._callFUT, pattern) |
| | | |
| | | def test_generate_with_string_remainder_and_unicode_replacement(self): |
| | | pattern = text_(b'/abc*remainder', 'utf-8') |
| | | _, generator = self._callFUT(pattern) |
| | | result = generator( |
| | | {'remainder': text_(b'/Qu\xc3\xa9bec/La Pe\xc3\xb1a', 'utf-8')} |
| | | ) |
| | | self.assertEqual(result, '/abc/Qu%C3%A9bec/La%20Pe%C3%B1a') |
| | | # should be a native string |
| | | self.assertEqual(type(result), str) |
| | | |
| | | def test_generate_with_string_remainder_and_nonstring_replacement(self): |
| | | pattern = text_(b'/abc/*remainder', 'utf-8') |
| | | _, generator = self._callFUT(pattern) |
| | | result = generator( |
| | | {'remainder': None} |
| | | ) |
| | | self.assertEqual(result, '/abc/None') |
| | | # should be a native string |
| | | self.assertEqual(type(result), str) |
| | | |
| | | class TestCompileRouteFunctional(unittest.TestCase): |
| | | def matches(self, pattern, path, expected): |
| | |
| | | self.matches('*traverse', '/zzz/abc', {'traverse':('zzz', 'abc')}) |
| | | self.matches('*traverse', '/zzz/ abc', {'traverse':('zzz', ' abc')}) |
| | | #'/La%20Pe%C3%B1a' |
| | | self.matches('{x}', native_(b'/La Pe\xc3\xb1a'), |
| | | {'x':text_(b'La Pe\xf1a')}) |
| | | self.matches('{x}', text_(b'/La Pe\xc3\xb1a', 'utf-8'), |
| | | {'x':text_(b'La Pe\xc3\xb1a', 'utf-8')}) |
| | | # '/La%20Pe%C3%B1a/x' |
| | | self.matches('*traverse', native_(b'/La Pe\xc3\xb1a/x'), |
| | | {'traverse':(text_(b'La Pe\xf1a'), 'x')}) |
| | | self.matches('*traverse', text_(b'/La Pe\xc3\xb1a/x'), |
| | | {'traverse':(text_(b'La Pe\xc3\xb1a'), 'x')}) |
| | | self.matches('/foo/{id}.html', '/foo/bar.html', {'id':'bar'}) |
| | | self.matches('/{num:[0-9]+}/*traverse', '/555/abc/def', |
| | | {'num':'555', 'traverse':('abc', 'def')}) |
| | |
| | | self.matches('*traverse', '/zzz/abc', {'traverse':('zzz', 'abc')}) |
| | | self.matches('*traverse', '/zzz/ abc', {'traverse':('zzz', ' abc')}) |
| | | #'/La%20Pe%C3%B1a' |
| | | self.matches(':x', native_(b'/La Pe\xc3\xb1a'), |
| | | {'x':text_(b'La Pe\xf1a')}) |
| | | # pattern, path, expected |
| | | self.matches(':x', text_(b'/La Pe\xc3\xb1a', 'utf-8'), |
| | | {'x':text_(b'La Pe\xc3\xb1a', 'utf-8')}) |
| | | # '/La%20Pe%C3%B1a/x' |
| | | self.matches('*traverse', native_(b'/La Pe\xc3\xb1a/x'), |
| | | {'traverse':(text_(b'La Pe\xf1a'), 'x')}) |
| | | self.matches('*traverse', text_(b'/La Pe\xc3\xb1a/x', 'utf-8'), |
| | | {'traverse':(text_(b'La Pe\xc3\xb1a', 'utf-8'), 'x')}) |
| | | self.matches('/foo/:id.html', '/foo/bar.html', {'id':'bar'}) |
| | | self.matches('/foo/:id_html', '/foo/bar_html', {'id_html':'bar_html'}) |
| | | self.matches('zzz/:_', '/zzz/abc', {'_':'abc'}) |
| | |
| | | import unittest |
| | | from pyramid.compat import PY3 |
| | | |
| | | class Test_InstancePropertyMixin(unittest.TestCase): |
| | | def _makeOne(self): |
| | | cls = self._targetClass() |
| | | class Foo(cls): |
| | | pass |
| | | return Foo() |
| | | |
| | | def _targetClass(self): |
| | | from pyramid.util import InstancePropertyMixin |
| | | return InstancePropertyMixin |
| | | |
| | | def test_callable(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(worker) |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.worker) |
| | | foo.bar = 2 |
| | | self.assertEqual(2, foo.worker) |
| | | |
| | | def test_callable_with_name(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, name='x') |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.x) |
| | | foo.bar = 2 |
| | | self.assertEqual(2, foo.x) |
| | | |
| | | def test_callable_with_reify(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, reify=True) |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.worker) |
| | | foo.bar = 2 |
| | | self.assertEqual(1, foo.worker) |
| | | |
| | | def test_callable_with_name_reify(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, name='x') |
| | | foo.set_property(worker, name='y', reify=True) |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.y) |
| | | self.assertEqual(1, foo.x) |
| | | foo.bar = 2 |
| | | self.assertEqual(2, foo.x) |
| | | self.assertEqual(1, foo.y) |
| | | |
| | | def test_property_without_name(self): |
| | | def worker(obj): pass |
| | | foo = self._makeOne() |
| | | self.assertRaises(ValueError, foo.set_property, property(worker)) |
| | | |
| | | def test_property_with_name(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(property(worker), name='x') |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.x) |
| | | foo.bar = 2 |
| | | self.assertEqual(2, foo.x) |
| | | |
| | | def test_property_with_reify(self): |
| | | def worker(obj): pass |
| | | foo = self._makeOne() |
| | | self.assertRaises(ValueError, foo.set_property, |
| | | property(worker), name='x', reify=True) |
| | | |
| | | def test_override_property(self): |
| | | def worker(obj): pass |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, name='x') |
| | | def doit(): |
| | | foo.x = 1 |
| | | self.assertRaises(AttributeError, doit) |
| | | |
| | | def test_override_reify(self): |
| | | def worker(obj): pass |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, name='x', reify=True) |
| | | foo.x = 1 |
| | | self.assertEqual(1, foo.x) |
| | | foo.x = 2 |
| | | self.assertEqual(2, foo.x) |
| | | |
| | | def test_reset_property(self): |
| | | foo = self._makeOne() |
| | | foo.set_property(lambda _: 1, name='x') |
| | | self.assertEqual(1, foo.x) |
| | | foo.set_property(lambda _: 2, name='x') |
| | | self.assertEqual(2, foo.x) |
| | | |
| | | def test_reset_reify(self): |
| | | """ This is questionable behavior, but may as well get notified |
| | | if it changes.""" |
| | | foo = self._makeOne() |
| | | foo.set_property(lambda _: 1, name='x', reify=True) |
| | | self.assertEqual(1, foo.x) |
| | | foo.set_property(lambda _: 2, name='x', reify=True) |
| | | self.assertEqual(1, foo.x) |
| | | |
| | | class Test_WeakOrderedSet(unittest.TestCase): |
| | | def _makeOne(self): |
| | | from pyramid.config import WeakOrderedSet |
| | |
| | | |
| | | def test_create_defaults(self): |
| | | decorator = self._makeOne() |
| | | self.assertEqual(decorator.name, '') |
| | | self.assertEqual(decorator.request_type, None) |
| | | self.assertEqual(decorator.context, None) |
| | | self.assertEqual(decorator.permission, None) |
| | | self.assertEqual(decorator.__dict__, {}) |
| | | |
| | | def test_create_context_trumps_for(self): |
| | | decorator = self._makeOne(context='123', for_='456') |
| | |
| | | self.assertEqual(decorator.context, '456') |
| | | |
| | | def test_create_nondefaults(self): |
| | | decorator = self._makeOne(name=None, request_type=None, for_=None, |
| | | permission='foo', mapper='mapper', |
| | | decorator='decorator', match_param='match_param') |
| | | decorator = self._makeOne( |
| | | name=None, request_type=None, for_=None, |
| | | permission='foo', mapper='mapper', |
| | | decorator='decorator', match_param='match_param' |
| | | ) |
| | | self.assertEqual(decorator.name, None) |
| | | self.assertEqual(decorator.request_type, None) |
| | | self.assertEqual(decorator.context, None) |
| | |
| | | config = call_venusian(venusian) |
| | | settings = config.settings |
| | | self.assertEqual(len(settings), 1) |
| | | self.assertEqual(settings[0]['permission'], None) |
| | | self.assertEqual(settings[0]['context'], None) |
| | | self.assertEqual(settings[0]['request_type'], None) |
| | | self.assertEqual(len(settings), 1) |
| | | self.assertEqual(len(settings[0]), 3) |
| | | self.assertEqual(settings[0]['venusian'], venusian) |
| | | self.assertEqual(settings[0]['view'], None) # comes from call_venusian |
| | | self.assertEqual(settings[0]['_info'], 'codeinfo') |
| | | |
| | | def test_call_class(self): |
| | | decorator = self._makeOne() |
| | |
| | | config = call_venusian(venusian) |
| | | settings = config.settings |
| | | self.assertEqual(len(settings), 1) |
| | | self.assertEqual(settings[0]['permission'], None) |
| | | self.assertEqual(settings[0]['context'], None) |
| | | self.assertEqual(settings[0]['request_type'], None) |
| | | self.assertEqual(len(settings[0]), 4) |
| | | self.assertEqual(settings[0]['venusian'], venusian) |
| | | self.assertEqual(settings[0]['view'], None) # comes from call_venusian |
| | | self.assertEqual(settings[0]['attr'], 'foo') |
| | | self.assertEqual(settings[0]['_info'], 'codeinfo') |
| | | |
| | | def test_call_class_attr_already_set(self): |
| | | decorator = self._makeOne(attr='abc') |
| | |
| | | config = call_venusian(venusian) |
| | | settings = config.settings |
| | | self.assertEqual(len(settings), 1) |
| | | self.assertEqual(settings[0]['permission'], None) |
| | | self.assertEqual(settings[0]['context'], None) |
| | | self.assertEqual(settings[0]['request_type'], None) |
| | | self.assertEqual(len(settings[0]), 4) |
| | | self.assertEqual(settings[0]['venusian'], venusian) |
| | | self.assertEqual(settings[0]['view'], None) # comes from call_venusian |
| | | self.assertEqual(settings[0]['attr'], 'abc') |
| | | self.assertEqual(settings[0]['_info'], 'codeinfo') |
| | | |
| | | def test_stacking(self): |
| | | decorator1 = self._makeOne(name='1') |
| | |
| | | @view_defaults(route_name='ghi') |
| | | class Bar(Foo): pass |
| | | self.assertEqual(Bar.__view_defaults__['route_name'],'ghi') |
| | | self.assertEqual(Bar.__view_defaults__['renderer'], None) |
| | | self.assertFalse('renderer' in Bar.__view_defaults__) |
| | | |
| | | def test_it_inheritance_overriden_empty(self): |
| | | from pyramid.view import view_defaults |
| | |
| | | class Foo(object): pass |
| | | @view_defaults() |
| | | class Bar(Foo): pass |
| | | self.assertEqual(Bar.__view_defaults__['route_name'], None) |
| | | self.assertEqual(Bar.__view_defaults__['renderer'], None) |
| | | self.assertEqual(Bar.__view_defaults__, {}) |
| | | |
| | | class ExceptionResponse(Exception): |
| | | status = '404 Not Found' |
| | |
| | | PY3, |
| | | native_, |
| | | text_, |
| | | bytes_, |
| | | ascii_native_, |
| | | text_type, |
| | | binary_type, |
| | | url_unquote_native, |
| | | is_nonstr_iter, |
| | | decode_path_info, |
| | | unquote_bytes_to_wsgi, |
| | | ) |
| | | |
| | | from pyramid.encode import url_quote |
| | |
| | | |
| | | def traversal_path(path): |
| | | """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for |
| | | decoding paths that are URL-encoded.""" |
| | | path = ascii_native_(path) |
| | | path = url_unquote_native(path, 'latin-1', 'strict') |
| | | return traversal_path_info(path) |
| | | decoding paths that are URL-encoded. |
| | | |
| | | If this function is passed a Unicode object instead of a sequence of |
| | | bytes as ``path``, that Unicode object *must* directly encodeable to |
| | | ASCII. For example, u'/foo' will work but u'/<unprintable unicode>' (a |
| | | Unicode object with characters that cannot be encoded to ascii) will |
| | | not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be |
| | | encoded directly to ASCII. |
| | | """ |
| | | if isinstance(path, text_type): |
| | | # must not possess characters outside ascii |
| | | path = path.encode('ascii') |
| | | # we unquote this path exactly like a PEP 3333 server would |
| | | path = unquote_bytes_to_wsgi(path) # result will be a native string |
| | | return traversal_path_info(path) # result will be a tuple of unicode |
| | | |
| | | @lru_cache(1000) |
| | | def traversal_path_info(path): |
| | | """ Given a ``PATH_INFO`` environ value (slash-separated path segments), |
| | | return a tuple representing that path which can be used to traverse a |
| | | resource tree. |
| | | """ Given``path``, return a tuple representing that path which can be |
| | | used to traverse a resource tree. ``path`` is assumed to be an |
| | | already-URL-decoded ``str`` type as if it had come to us from an upstream |
| | | WSGI server as the ``PATH_INFO`` environ variable. |
| | | |
| | | ``PATH_INFO`` is assumed to already be URL-decoded. It is encoded to |
| | | bytes using the Latin-1 encoding; the resulting set of bytes is |
| | | subsequently decoded to text using the UTF-8 encoding; a |
| | | :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be |
| | | decoded. |
| | | The ``path`` is first decoded to from its WSGI representation to Unicode; |
| | | it is decoded differently depending on platform: |
| | | |
| | | The ``PATH_INFO`` is split on slashes, creating a list of segments. Each |
| | | segment subsequently decoded into Unicode. If a segment name is empty or |
| | | if it is ``.``, it is ignored. If a segment name is ``..``, the previous |
| | | segment is deleted, and the ``..`` is ignored. |
| | | - On Python 2, ``path`` is decoded to Unicode from bytes using the UTF-8 |
| | | decoding directly; a :exc:`pyramid.exc.URLDecodeError` is raised if a the |
| | | URL cannot be decoded. |
| | | |
| | | If this function is passed a Unicode object instead of a string, that |
| | | Unicode object *must* directly encodeable to ASCII. For example, u'/foo' |
| | | will work but u'/<unprintable unicode>' (a Unicode object with characters |
| | | that cannot be encoded to ascii) will not. A :exc:`UnicodeError` will be |
| | | raised if the Unicode cannot be encoded directly to ASCII. |
| | | - On Python 3, as per the PEP 3333 spec, ``path`` is first encoded to |
| | | bytes using the Latin-1 encoding; the resulting set of bytes is |
| | | subsequently decoded to text using the UTF-8 encoding; a |
| | | :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be |
| | | decoded. |
| | | |
| | | The ``path`` is split on slashes, creating a list of segments. If a |
| | | segment name is empty or if it is ``.``, it is ignored. If a segment |
| | | name is ``..``, the previous segment is deleted, and the ``..`` is |
| | | ignored. |
| | | |
| | | Examples: |
| | | |
| | |
| | | applications in :app:`Pyramid`. |
| | | """ |
| | | try: |
| | | path = bytes_(path, 'latin-1').decode('utf-8') |
| | | path = decode_path_info(path) # result will be Unicode |
| | | except UnicodeDecodeError as e: |
| | | raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) |
| | | return split_path_info(path) # result will be tuple of Unicode |
| | | |
| | | @lru_cache(1000) |
| | | def split_path_info(path): |
| | | # suitable for splitting an already-unquoted-already-decoded (unicode) |
| | | # path value |
| | | path = path.strip('/') |
| | | clean = [] |
| | | for segment in path.split('/'): |
| | |
| | | path = matchdict.get('traverse', '/') or '/' |
| | | if is_nonstr_iter(path): |
| | | # this is a *traverse stararg (not a {traverse}) |
| | | path = '/'.join([quote_path_segment(x) for x in path]) or '/' |
| | | # routing has already decoded these elements, so we just |
| | | # need to join them |
| | | path = '/'.join(path) or '/' |
| | | |
| | | subpath = matchdict.get('subpath', ()) |
| | | if not is_nonstr_iter(subpath): |
| | | # this is not a *subpath stararg (just a {subpath}) |
| | | subpath = traversal_path_info(subpath) |
| | | # routing has already decoded this string, so we just need |
| | | # to split it |
| | | subpath = split_path_info(subpath) |
| | | |
| | | else: |
| | | # this request did not match a route |
| | | subpath = () |
| | | try: |
| | | path = environ['PATH_INFO'] or '/' |
| | | # empty if mounted under a path in mod_wsgi, for example |
| | | path = decode_path_info(environ['PATH_INFO'] or '/') |
| | | except KeyError: |
| | | path = '/' |
| | | except UnicodeDecodeError as e: |
| | | raise URLDecodeError(e.encoding, e.object, e.start, e.end, |
| | | e.reason) |
| | | |
| | | if VH_ROOT_KEY in environ: |
| | | vroot_path = environ[VH_ROOT_KEY] |
| | | vroot_tuple = traversal_path_info(vroot_path) |
| | | vpath = vroot_path + path |
| | | # HTTP_X_VHM_ROOT |
| | | vroot_path = decode_path_info(environ[VH_ROOT_KEY]) |
| | | vroot_tuple = split_path_info(vroot_path) |
| | | vpath = vroot_path + path # both will (must) be unicode or asciistr |
| | | vroot_idx = len(vroot_tuple) -1 |
| | | else: |
| | | vroot_tuple = () |
| | |
| | | # and this hurts readability; apologies |
| | | i = 0 |
| | | view_selector = self.VIEW_SELECTOR |
| | | vpath_tuple = traversal_path_info(vpath) |
| | | vpath_tuple = split_path_info(vpath) |
| | | for segment in vpath_tuple: |
| | | if segment[:2] == view_selector: |
| | | return {'context':ob, |
| | |
| | | encoded to UTF-8. The resulting strings are joined with slashes |
| | | and rendered into the URL. If a string is passed as a |
| | | ``*remainder`` replacement value, it is tacked on to the URL |
| | | untouched. |
| | | after being URL-quoted-except-for-embedded-slashes. |
| | | |
| | | If a keyword argument ``_query`` is present, it will be used to |
| | | compose a query string that will be tacked on to the end of the |
| | |
| | | ) |
| | | |
| | | from pyramid.compat import ( |
| | | PY3, |
| | | native_, |
| | | bytes_, |
| | | text_, |
| | | text_type, |
| | | string_types, |
| | | binary_type, |
| | | is_nonstr_iter, |
| | | url_quote, |
| | | ) |
| | | |
| | | from pyramid.exceptions import URLDecodeError |
| | | |
| | | from pyramid.traversal import ( |
| | | traversal_path_info, |
| | | quote_path_segment, |
| | | decode_path_info, |
| | | split_path_info, |
| | | ) |
| | | |
| | | _marker = object() |
| | |
| | | environ = request.environ |
| | | try: |
| | | # empty if mounted under a path in mod_wsgi, for example |
| | | path = environ['PATH_INFO'] or '/' |
| | | path = decode_path_info(environ['PATH_INFO'] or '/') |
| | | except KeyError: |
| | | path = '/' |
| | | except UnicodeDecodeError as e: |
| | | raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) |
| | | |
| | | for route in self.routelist: |
| | | match = route.match(path) |
| | |
| | | return '{%s}' % name[1:] |
| | | |
| | | def _compile_route(route): |
| | | # This function really wants to consume Unicode patterns natively, but if |
| | | # someone passes us a bytestring, we allow it by converting it to Unicode |
| | | # using the ASCII decoding. We decode it using ASCII because we dont |
| | | # want to accept bytestrings with high-order characters in them here as |
| | | # we have no idea what the encoding represents. |
| | | if route.__class__ is not text_type: |
| | | try: |
| | | route = text_(route, 'ascii') |
| | | except UnicodeDecodeError: |
| | | raise ValueError( |
| | | 'The pattern value passed to add_route must be ' |
| | | 'either a Unicode string or a plain string without ' |
| | | 'any non-ASCII characters (you provided %r).' % route) |
| | | |
| | | if old_route_re.search(route) and not route_re.search(route): |
| | | route = old_route_re.sub(update_pattern, route) |
| | | |
| | | if not route.startswith('/'): |
| | | route = '/' + route |
| | | |
| | | star = None |
| | | remainder = None |
| | | if star_at_end.search(route): |
| | | route, star = route.rsplit('*', 1) |
| | | route, remainder = route.rsplit('*', 1) |
| | | |
| | | pat = route_re.split(route) |
| | | |
| | | # every element in "pat" will be Unicode (regardless of whether the |
| | | # route_re regex pattern is itself Unicode or str) |
| | | pat.reverse() |
| | | rpat = [] |
| | | gen = [] |
| | | prefix = pat.pop() # invar: always at least one element (route='/'+route) |
| | | rpat.append(re.escape(prefix)) |
| | | gen.append(prefix) |
| | | |
| | | # We want to generate URL-encoded URLs, so we url-quote the prefix, being |
| | | # careful not to quote any embedded slashes. We have to replace '%' with |
| | | # '%%' afterwards, as the strings that go into "gen" are used as string |
| | | # replacement targets. |
| | | gen.append(quote_path_segment(prefix, safe='/').replace('%', '%%')) # native |
| | | rpat.append(re.escape(prefix)) # unicode |
| | | |
| | | while pat: |
| | | name = pat.pop() |
| | | name = pat.pop() # unicode |
| | | name = name[1:-1] |
| | | if ':' in name: |
| | | name, reg = name.split(':') |
| | | else: |
| | | reg = '[^/]+' |
| | | gen.append('%%(%s)s' % name) |
| | | name = '(?P<%s>%s)' % (name, reg) |
| | | gen.append('%%(%s)s' % native_(name)) # native |
| | | name = '(?P<%s>%s)' % (name, reg) # unicode |
| | | rpat.append(name) |
| | | s = pat.pop() |
| | | s = pat.pop() # unicode |
| | | if s: |
| | | rpat.append(re.escape(s)) |
| | | gen.append(s) |
| | | rpat.append(re.escape(s)) # unicode |
| | | # We want to generate URL-encoded URLs, so we url-quote this |
| | | # literal in the pattern, being careful not to quote the embedded |
| | | # slashes. We have to replace '%' with '%%' afterwards, as the |
| | | # strings that go into "gen" are used as string replacement |
| | | # targets. What is appended to gen is a native string. |
| | | gen.append(quote_path_segment(s, safe='/').replace('%', '%%')) |
| | | |
| | | if star: |
| | | rpat.append('(?P<%s>.*?)' % star) |
| | | gen.append('%%(%s)s' % star) |
| | | if remainder: |
| | | rpat.append('(?P<%s>.*?)' % remainder) # unicode |
| | | gen.append('%%(%s)s' % native_(remainder)) # native |
| | | |
| | | pattern = ''.join(rpat) + '$' |
| | | pattern = ''.join(rpat) + '$' # unicode |
| | | |
| | | match = re.compile(pattern).match |
| | | def matcher(path): |
| | | # This function really wants to consume Unicode patterns natively, |
| | | # but if someone passes us a bytestring, we allow it by converting it |
| | | # to Unicode using the ASCII decoding. We decode it using ASCII |
| | | # because we dont want to accept bytestrings with high-order |
| | | # characters in them here as we have no idea what the encoding |
| | | # represents. |
| | | if path.__class__ is not text_type: |
| | | path = text_(path, 'ascii') |
| | | m = match(path) |
| | | if m is None: |
| | | return m |
| | | return None |
| | | d = {} |
| | | for k, v in m.groupdict().items(): |
| | | if k == star: |
| | | d[k] = traversal_path_info(v) |
| | | # k and v will be Unicode 2.6.4 and lower doesnt accept unicode |
| | | # kwargs as **kw, so we explicitly cast the keys to native |
| | | # strings in case someone wants to pass the result as **kw |
| | | nk = native_(k, 'ascii') |
| | | if k == remainder: |
| | | d[nk] = split_path_info(v) |
| | | else: |
| | | try: |
| | | val = bytes_(v).decode('utf-8', 'strict') |
| | | d[k] = val |
| | | except UnicodeDecodeError as e: |
| | | raise URLDecodeError( |
| | | e.encoding, e.object, e.start, e.end, e.reason |
| | | ) |
| | | |
| | | |
| | | d[nk] = v |
| | | return d |
| | | |
| | | |
| | | gen = ''.join(gen) |
| | | def generator(dict): |
| | | newdict = {} |
| | | for k, v in dict.items(): |
| | | if v.__class__ is text_type: |
| | | v = native_(v, 'utf-8') |
| | | if k == star and is_nonstr_iter(v): |
| | | v = '/'.join([quote_path_segment(x) for x in v]) |
| | | elif k != star: |
| | | if PY3: # pragma: no cover |
| | | if v.__class__ is binary_type: |
| | | # url_quote below needs a native string, not bytes on Py3 |
| | | v = v.decode('utf-8') |
| | | else: |
| | | if v.__class__ is text_type: |
| | | # url_quote below needs bytes, not unicode on Py2 |
| | | v = v.encode('utf-8') |
| | | |
| | | if k == remainder: |
| | | # a stararg argument |
| | | if is_nonstr_iter(v): |
| | | v = '/'.join([quote_path_segment(x) for x in v]) # native |
| | | else: |
| | | if v.__class__ not in string_types: |
| | | v = str(v) |
| | | v = quote_path_segment(v, safe='/') |
| | | else: |
| | | if v.__class__ not in string_types: |
| | | v = str(v) |
| | | v = url_quote(v, safe='') |
| | | # v may be bytes (py2) or native string (py3) |
| | | v = quote_path_segment(v) |
| | | |
| | | # at this point, the value will be a native string |
| | | newdict[k] = v |
| | | return gen % newdict |
| | | |
| | | result = gen % newdict # native string result |
| | | return result |
| | | |
| | | return matcher, generator |
| | |
| | | def __init__(self, package=None): # default to package = None for bw compat |
| | | return _DottedNameResolver.__init__(self, package) |
| | | |
| | | class InstancePropertyMixin(object): |
| | | """ Mixin that will allow an instance to add properties at |
| | | run-time as if they had been defined via @property or @reify |
| | | on the class itself. |
| | | """ |
| | | |
| | | def set_property(self, func, name=None, reify=False): |
| | | """ Add a callable or a property descriptor to the instance. |
| | | |
| | | Properties, unlike attributes, are lazily evaluated by executing |
| | | an underlying callable when accessed. They can be useful for |
| | | adding features to an object without any cost if those features |
| | | go unused. |
| | | |
| | | A property may also be reified via the |
| | | :class:`pyramid.decorator.reify` decorator by setting |
| | | ``reify=True``, allowing the result of the evaluation to be |
| | | cached. Thus the value of the property is only computed once for |
| | | the lifetime of the object. |
| | | |
| | | ``func`` can either be a callable that accepts the instance as |
| | | its single positional parameter, or it can be a property |
| | | descriptor. |
| | | |
| | | If the ``func`` is a property descriptor, the ``name`` parameter |
| | | must be supplied or a ``ValueError`` will be raised. Also note |
| | | that a property descriptor cannot be reified, so ``reify`` must |
| | | be ``False``. |
| | | |
| | | If ``name`` is None, the name of the property will be computed |
| | | from the name of the ``func``. |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | class Foo(InstancePropertyMixin): |
| | | _x = 1 |
| | | |
| | | def _get_x(self): |
| | | return _x |
| | | |
| | | def _set_x(self, value): |
| | | self._x = value |
| | | |
| | | foo = Foo() |
| | | foo.set_property(property(_get_x, _set_x), name='x') |
| | | foo.set_property(_get_x, name='y', reify=True) |
| | | |
| | | >>> foo.x |
| | | 1 |
| | | >>> foo.y |
| | | 1 |
| | | >>> foo.x = 5 |
| | | >>> foo.x |
| | | 5 |
| | | >>> foo.y # notice y keeps the original value |
| | | 1 |
| | | """ |
| | | |
| | | is_property = isinstance(func, property) |
| | | if is_property: |
| | | fn = func |
| | | if name is None: |
| | | raise ValueError('must specify "name" for a property') |
| | | if reify: |
| | | raise ValueError('cannot reify a property') |
| | | elif name is not None: |
| | | fn = lambda this: func(this) |
| | | fn.__name__ = name |
| | | fn.__doc__ = func.__doc__ |
| | | else: |
| | | name = func.__name__ |
| | | fn = func |
| | | if reify: |
| | | import pyramid.decorator |
| | | fn = pyramid.decorator.reify(fn) |
| | | elif not is_property: |
| | | fn = property(fn) |
| | | attrs = { name: fn } |
| | | parent = self.__class__ |
| | | cls = type(parent.__name__, (parent, object), attrs) |
| | | self.__class__ = cls |
| | | |
| | | class WeakOrderedSet(object): |
| | | """ Maintain a set of items. |
| | | |
| | |
| | | return None |
| | | return ''.join(iterable) |
| | | |
| | | class _default(object): |
| | | def __nonzero__(self): |
| | | return False |
| | | __bool__ = __nonzero__ |
| | | def __repr__(self): # pragma: no cover |
| | | return '(default)' |
| | | |
| | | default = _default() |
| | | |
| | | class view_config(object): |
| | | """ A function, class or method :term:`decorator` which allows a |
| | | developer to create view registrations nearer to a :term:`view |
| | |
| | | and ``match_param``. |
| | | |
| | | The meanings of these arguments are the same as the arguments passed to |
| | | :meth:`pyramid.config.Configurator.add_view`. |
| | | :meth:`pyramid.config.Configurator.add_view`. If any argument is left |
| | | out, its default will be the equivalent ``add_view`` default. |
| | | |
| | | See :ref:`mapping_views_using_a_decorator_section` for details about |
| | | using :class:`view_config`. |
| | | |
| | | """ |
| | | venusian = venusian # for testing injection |
| | | def __init__(self, name='', request_type=None, for_=None, permission=None, |
| | | route_name=None, request_method=None, request_param=None, |
| | | containment=None, attr=None, renderer=None, wrapper=None, |
| | | xhr=False, accept=None, header=None, path_info=None, |
| | | custom_predicates=(), context=None, decorator=None, |
| | | mapper=None, http_cache=None, match_param=None): |
| | | self.name = name |
| | | self.request_type = request_type |
| | | self.context = context or for_ |
| | | self.permission = permission |
| | | self.route_name = route_name |
| | | self.request_method = request_method |
| | | self.request_param = request_param |
| | | self.containment = containment |
| | | self.attr = attr |
| | | self.renderer = renderer |
| | | self.wrapper = wrapper |
| | | self.xhr = xhr |
| | | self.accept = accept |
| | | self.header = header |
| | | self.path_info = path_info |
| | | self.custom_predicates = custom_predicates |
| | | self.decorator = decorator |
| | | self.mapper = mapper |
| | | self.http_cache = http_cache |
| | | self.match_param = match_param |
| | | def __init__(self, name=default, request_type=default, for_=default, |
| | | permission=default, route_name=default, |
| | | request_method=default, request_param=default, |
| | | containment=default, attr=default, renderer=default, |
| | | wrapper=default, xhr=default, accept=default, |
| | | header=default, path_info=default, |
| | | custom_predicates=default, context=default, |
| | | decorator=default, mapper=default, http_cache=default, |
| | | match_param=default): |
| | | L = locals() |
| | | if (context is not default) or (for_ is not default): |
| | | L['context'] = context or for_ |
| | | for k, v in L.items(): |
| | | if k not in ('self', 'L') and v is not default: |
| | | setattr(self, k, v) |
| | | |
| | | def __call__(self, wrapped): |
| | | settings = self.__dict__.copy() |
| | |
| | | # if the decorator was attached to a method in a class, or |
| | | # otherwise executed at class scope, we need to set an |
| | | # 'attr' into the settings if one isn't already in there |
| | | if settings['attr'] is None: |
| | | if settings.get('attr') is None: |
| | | settings['attr'] = wrapped.__name__ |
| | | |
| | | settings['_info'] = info.codeinfo # fbo "action_method" |
| | |
| | | ]) |
| | | |
| | | setup(name='pyramid', |
| | | version='1.3a2', |
| | | version='1.3a5', |
| | | description=('The Pyramid web application development framework, a ' |
| | | 'Pylons project'), |
| | | long_description=README + '\n\n' + CHANGES, |