summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml22
-rw-r--r--CHANGES.txt135
-rw-r--r--CONTRIBUTORS.txt8
-rw-r--r--HACKING.txt5
-rw-r--r--README.rst7
-rw-r--r--RELEASING.txt3
-rw-r--r--TODO.txt5
-rw-r--r--docs/_static/pyramid_request_processing.graffle9748
-rw-r--r--docs/_static/pyramid_request_processing.pngbin0 -> 122854 bytes
-rw-r--r--docs/_static/pyramid_request_processing.svg3
-rw-r--r--docs/_static/pyramid_router.graffle1621
-rw-r--r--docs/_static/pyramid_router.pngbin0 -> 120643 bytes
-rw-r--r--docs/_static/pyramid_router.svg3
m---------docs/_themes0
-rw-r--r--docs/api/exceptions.rst12
-rw-r--r--docs/api/httpexceptions.rst94
-rw-r--r--docs/api/index.rst12
-rw-r--r--docs/api/interfaces.rst2
-rw-r--r--docs/api/registry.rst12
-rw-r--r--docs/api/request.rst49
-rw-r--r--docs/api/security.rst2
-rw-r--r--docs/api/static.rst8
-rw-r--r--docs/conf.py11
-rw-r--r--docs/glossary.rst38
-rw-r--r--docs/index.rst5
-rw-r--r--docs/narr/MyProject/myproject/templates/mytemplate.pt2
-rw-r--r--docs/narr/MyProject/myproject/tests.py37
-rw-r--r--docs/narr/MyProject/setup.py45
-rw-r--r--docs/narr/assets.rst243
-rw-r--r--docs/narr/commandline.rst2
-rw-r--r--docs/narr/configuration.rst5
-rw-r--r--docs/narr/environment.rst170
-rw-r--r--docs/narr/hooks.rst55
-rw-r--r--docs/narr/i18n.rst18
-rw-r--r--docs/narr/install.rst28
-rw-r--r--docs/narr/logging.rst54
-rw-r--r--docs/narr/router.rst6
-rw-r--r--docs/narr/security.rst94
-rw-r--r--docs/narr/startup.rst8
-rw-r--r--docs/narr/templates.rst30
-rw-r--r--docs/narr/testing.rst142
-rw-r--r--docs/narr/viewconfig.rst13
-rw-r--r--docs/narr/webob.rst19
-rw-r--r--docs/quick_tour.rst2
-rw-r--r--docs/quick_tour/views/views.py7
-rw-r--r--docs/quick_tutorial/authentication/development.ini31
-rw-r--r--docs/quick_tutorial/authorization/development.ini31
-rw-r--r--docs/quick_tutorial/databases.rst2
-rw-r--r--docs/quick_tutorial/databases/development.ini36
-rw-r--r--docs/quick_tutorial/debugtoolbar.rst51
-rw-r--r--docs/quick_tutorial/debugtoolbar/development.ini31
-rw-r--r--docs/quick_tutorial/forms.rst2
-rw-r--r--docs/quick_tutorial/forms/development.ini31
-rw-r--r--docs/quick_tutorial/functional_testing.rst7
-rw-r--r--docs/quick_tutorial/functional_testing/development.ini31
-rw-r--r--docs/quick_tutorial/hello_world.rst2
-rw-r--r--docs/quick_tutorial/ini.rst6
-rw-r--r--docs/quick_tutorial/ini/development.ini31
-rw-r--r--docs/quick_tutorial/jinja2.rst19
-rw-r--r--docs/quick_tutorial/jinja2/development.ini31
-rw-r--r--docs/quick_tutorial/jinja2/tutorial/tests.py8
-rw-r--r--docs/quick_tutorial/json.rst2
-rw-r--r--docs/quick_tutorial/json/development.ini31
-rw-r--r--docs/quick_tutorial/logging.rst2
-rw-r--r--docs/quick_tutorial/more_view_classes.rst2
-rw-r--r--docs/quick_tutorial/more_view_classes/development.ini31
-rw-r--r--docs/quick_tutorial/more_view_classes/tutorial/views.py6
-rw-r--r--docs/quick_tutorial/request_response/development.ini31
-rw-r--r--docs/quick_tutorial/requirements.rst12
-rw-r--r--docs/quick_tutorial/retail_forms/development.ini31
-rw-r--r--docs/quick_tutorial/routing/development.ini31
-rw-r--r--docs/quick_tutorial/sessions.rst8
-rw-r--r--docs/quick_tutorial/sessions/development.ini31
-rw-r--r--docs/quick_tutorial/static_assets/development.ini31
-rw-r--r--docs/quick_tutorial/templating/development.ini31
-rw-r--r--docs/quick_tutorial/unit_testing/development.ini31
-rw-r--r--docs/quick_tutorial/view_classes/development.ini31
-rw-r--r--docs/quick_tutorial/views/development.ini31
-rw-r--r--docs/tutorials/wiki/design.rst4
-rw-r--r--docs/tutorials/wiki2/design.rst3
-rw-r--r--docs/whatsnew-1.5.rst6
-rw-r--r--pyramid/authentication.py29
-rw-r--r--pyramid/config/__init__.py36
-rw-r--r--pyramid/config/assets.py216
-rw-r--r--pyramid/config/factories.py36
-rw-r--r--pyramid/config/settings.py9
-rw-r--r--pyramid/config/views.py113
-rw-r--r--pyramid/httpexceptions.py69
-rw-r--r--pyramid/interfaces.py129
-rw-r--r--pyramid/registry.py5
-rw-r--r--pyramid/renderers.py23
-rw-r--r--pyramid/request.py30
-rw-r--r--pyramid/response.py41
-rw-r--r--pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl15
-rw-r--r--pyramid/scaffolds/alchemy/+package+/views.py_tmpl3
-rw-r--r--pyramid/scaffolds/alchemy/development.ini_tmpl6
-rw-r--r--pyramid/scaffolds/alchemy/production.ini_tmpl6
-rw-r--r--pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl15
-rw-r--r--pyramid/scaffolds/starter/development.ini_tmpl8
-rw-r--r--pyramid/scaffolds/starter/production.ini_tmpl6
-rw-r--r--pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl15
-rw-r--r--pyramid/scaffolds/zodb/development.ini_tmpl6
-rw-r--r--pyramid/scaffolds/zodb/production.ini_tmpl6
-rw-r--r--pyramid/scripts/pcreate.py25
-rw-r--r--pyramid/scripts/prequest.py8
-rw-r--r--pyramid/scripts/proutes.py68
-rw-r--r--pyramid/scripts/pshell.py9
-rw-r--r--pyramid/security.py33
-rw-r--r--pyramid/static.py105
-rw-r--r--pyramid/testing.py32
-rw-r--r--pyramid/tests/fixtures/minimal.jpgbin0 -> 631 bytes
-rwxr-xr-xpyramid/tests/fixtures/minimal.pdfbin0 -> 1054 bytes
-rw-r--r--pyramid/tests/fixtures/minimal.xml1
-rw-r--r--pyramid/tests/test_authentication.py16
-rw-r--r--pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt0
-rw-r--r--pyramid/tests/test_config/test_assets.py439
-rw-r--r--pyramid/tests/test_config/test_factories.py15
-rw-r--r--pyramid/tests/test_config/test_init.py24
-rw-r--r--pyramid/tests/test_config/test_settings.py31
-rw-r--r--pyramid/tests/test_config/test_views.py134
-rw-r--r--pyramid/tests/test_integration.py2
-rw-r--r--pyramid/tests/test_registry.py5
-rw-r--r--pyramid/tests/test_renderers.py9
-rw-r--r--pyramid/tests/test_request.py46
-rw-r--r--pyramid/tests/test_response.py74
-rw-r--r--pyramid/tests/test_router.py6
-rw-r--r--pyramid/tests/test_scripts/pystartup.py1
-rw-r--r--pyramid/tests/test_scripts/test_pcreate.py114
-rw-r--r--pyramid/tests/test_scripts/test_prequest.py13
-rw-r--r--pyramid/tests/test_scripts/test_proutes.py93
-rw-r--r--pyramid/tests/test_scripts/test_pshell.py23
-rw-r--r--pyramid/tests/test_security.py21
-rw-r--r--pyramid/tests/test_session.py2
-rw-r--r--pyramid/tests/test_static.py163
-rw-r--r--pyramid/tests/test_testing.py11
-rw-r--r--pyramid/tests/test_util.py47
-rw-r--r--pyramid/tests/test_wsgi.py6
-rw-r--r--pyramid/url.py4
-rw-r--r--pyramid/util.py38
-rw-r--r--pyramid/wsgi.py7
-rw-r--r--rtd.txt2
-rw-r--r--setup.cfg1
-rw-r--r--setup.py7
-rw-r--r--tox.ini2
144 files changed, 14395 insertions, 1585 deletions
diff --git a/.travis.yml b/.travis.yml
index 29e499e76..cb98fddbe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,16 +1,22 @@
# Wire up travis
language: python
+sudo: false
-python:
- - 2.6
- - 2.7
- - pypy
- - 3.2
- - 3.3
+env:
+ - TOXENV=py26
+ - TOXENV=py27
+ - TOXENV=py32
+ - TOXENV=py33
+ - TOXENV=py34
+ - TOXENV=pypy
+ - TOXENV=pypy3
+ - TOXENV=cover
-install: python setup.py dev
+install:
+ - travis_retry pip install tox
-script: python setup.py test -q
+script:
+ - travis_retry tox
notifications:
email:
diff --git a/CHANGES.txt b/CHANGES.txt
index 264497f5b..129ce4616 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,7 +1,140 @@
Next release
============
-- ...
+Features
+--------
+
+- Added support / testing for 'pypy3' under Tox and Travis.
+ See https://github.com/Pylons/pyramid/pull/1469
+
+- Cache busting for static resources has been added and is available via a new
+ argument to ``pyramid.config.Configurator.add_static_view``: ``cachebust``.
+ See https://github.com/Pylons/pyramid/pull/1380
+
+- Add ``pyramid.config.Configurator.root_package`` attribute and init
+ parameter to assist with includeable packages that wish to resolve
+ resources relative to the package in which the ``Configurator`` was created.
+ This is especially useful for addons that need to load asset specs from
+ settings, in which case it is may be natural for a developer to define
+ imports or assets relative to the top-level package.
+ See https://github.com/Pylons/pyramid/pull/1337
+
+- Added line numbers to the log formatters in the scaffolds to assist with
+ debugging. See https://github.com/Pylons/pyramid/pull/1326
+
+- Add new HTTP exception objects for status codes
+ ``428 Precondition Required``, ``429 Too Many Requests`` and
+ ``431 Request Header Fields Too Large`` in ``pyramid.httpexceptions``.
+ See https://github.com/Pylons/pyramid/pull/1372/files
+
+- The ``pshell`` script will now load a ``PYTHONSTARTUP`` file if one is
+ defined in the environment prior to launching the interpreter.
+ See https://github.com/Pylons/pyramid/pull/1448
+
+- Make it simple to define notfound and forbidden views that wish to use
+ the default exception-response view but with altered predicates and other
+ configuration options. The ``view`` argument is now optional in
+ ``config.add_notfound_view`` and ``config.add_forbidden_view``..
+ See https://github.com/Pylons/pyramid/issues/494
+
+- Greatly improve the readability of the ``pcreate`` shell script output.
+ See https://github.com/Pylons/pyramid/pull/1453
+
+- Improve robustness to timing attacks in the ``AuthTktCookieHelper`` and
+ the ``SignedCookieSessionFactory`` classes by using the stdlib's
+ ``hmac.compare_digest`` if it is available (such as Python 2.7.7+ and 3.3+).
+ See https://github.com/Pylons/pyramid/pull/1457
+
+- Assets can now be overidden by an absolute path on the filesystem when using
+ the ``config.override_asset`` API. This makes it possible to fully support
+ serving up static content from a mutable directory while still being able
+ to use the ``request.static_url`` API and ``config.add_static_view``.
+ Previously it was not possible to use ``config.add_static_view`` with an
+ absolute path **and** generate urls to the content. This change replaces
+ the call, ``config.add_static_view('/abs/path', 'static')``, with
+ ``config.add_static_view('myapp:static', 'static')`` and
+ ``config.override_asset(to_override='myapp:static/',
+ override_with='/abs/path/')``. The ``myapp:static`` asset spec is completely
+ made up and does not need to exist - it is used for generating urls
+ via ``request.static_url('myapp:static/foo.png')``.
+ See https://github.com/Pylons/pyramid/issues/1252
+
+- Added ``pyramid.config.Configurator.set_response_factory`` and the
+ ``response_factory`` keyword argument to the ``Configurator`` for defining
+ a factory that will return a custom ``Response`` class.
+ See https://github.com/Pylons/pyramid/pull/1499
+
+Bug Fixes
+---------
+
+- ``pyramid.wsgi.wsgiapp`` and ``pyramid.wsgi.wsgiapp2`` now raise
+ ``ValueError`` when accidentally passed ``None``.
+ See https://github.com/Pylons/pyramid/pull/1320
+
+- Fix an issue whereby predicates would be resolved as maybe_dotted in the
+ introspectable but not when passed for registration. This would mean that
+ ``add_route_predicate`` for example can not take a string and turn it into
+ the actual callable function.
+ See https://github.com/Pylons/pyramid/pull/1306
+
+- Fix ``pyramid.testing.setUp`` to return a ``Configurator`` with a proper
+ package. Previously it was not possible to do package-relative includes
+ using the returned ``Configurator`` during testing. There is now a
+ ``package`` argument that can override this behavior as well.
+ See https://github.com/Pylons/pyramid/pull/1322
+
+- Fix an issue where a ``pyramid.response.FileResponse`` may apply a charset
+ where it does not belong. See https://github.com/Pylons/pyramid/pull/1251
+
+- Work around a bug introduced in Python 2.7.7 on Windows where
+ ``mimetypes.guess_type`` returns Unicode rather than str for the content
+ type, unlike any previous version of Python. See
+ https://github.com/Pylons/pyramid/issues/1360 for more information.
+
+- ``pcreate`` now normalizes the package name by converting hyphens to
+ underscores. See https://github.com/Pylons/pyramid/pull/1376
+
+- Fix an issue with the final response/finished callback being unable to
+ add another callback to the list. See
+ https://github.com/Pylons/pyramid/pull/1373
+
+- Fix a failing unittest caused by differing mimetypes across various OSs.
+ See https://github.com/Pylons/pyramid/issues/1405
+
+- Fix route generation for static view asset specifications having no path.
+ See https://github.com/Pylons/pyramid/pull/1377
+
+Deprecations
+------------
+
+- Renamed the ``principal`` argument to ``pyramid.security.remember()`` to
+ ``userid`` in order to clarify its intended purpose.
+ See https://github.com/Pylons/pyramid/pull/1399
+
+Docs
+----
+
+- Removed logging configuration from Quick Tutorial ini files except for
+ scaffolding- and logging-related chapters to avoid needing to explain it too
+ early.
+
+- Clarify a previously-implied detail of the ``ISession.invalidate`` API
+ documentation.
+
+- Improve and clarify the documentation on what Pyramid defines as a
+ ``principal`` and a ``userid`` in its security APIs.
+ See https://github.com/Pylons/pyramid/pull/1399
+
+Scaffolds
+---------
+
+- Update scaffold generating machinery to return the version of pyramid and
+ pyramid docs for use in scaffolds. Updated starter, alchemy and zodb
+ templates to have links to correctly versioned documentation and reflect
+ which pyramid was used to generate the scaffold.
+
+- Removed non-ascii copyright symbol from templates, as this was
+ causing the scaffolds to fail for project generation.
1.5 (2014-04-08)
================
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index d1ac72df5..e4132cda5 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -230,3 +230,11 @@ Contributors
- Antti Haapala, 2013/11/15
- Amit Mane, 2014/01/23
+
+- Fenton Travers, 2014/05/06
+
+- Randall Leeds, 2014/11/11
+
+- Hugo Branquinho, 2014/11/25
+
+- Adrian Teng, 2014/12/17
diff --git a/HACKING.txt b/HACKING.txt
index 460d02047..16c17699c 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -31,7 +31,7 @@ By Hand
$ cd hack-on-pyramid
# Configure remotes such that you can pull changes from the Pyramid
# repository into your local repository.
- $ git remote add upstream git@github.com:Pylons/pyramid.git
+ $ git remote add upstream https://github.com/Pylons/pyramid.git
# fetch and merge changes from upstream into master
$ git fetch upstream
$ git merge upstream/master
@@ -113,6 +113,7 @@ for this use case) and inside that a simple pyramid application named
``hacking`` that you can then fire up like so:
cd env27/hacking
+ ../bin/python setup.py develop
../bin/pserve development.ini
Adding Features
@@ -149,7 +150,7 @@ Coding Style
------------
- PEP8 compliance. Whitespace rules are relaxed: not necessary to put
- 2 newlines between classes. But 80-column lines, in particular, are
+ 2 newlines between classes. But 79-column lines, in particular, are
mandatory. See
http://docs.pylonsproject.org/en/latest/community/codestyle.html for more
information.
diff --git a/README.rst b/README.rst
index a3458028b..adf7eea5e 100644
--- a/README.rst
+++ b/README.rst
@@ -1,6 +1,13 @@
Pyramid
=======
+.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=master
+ :target: https://travis-ci.org/Pylons/pyramid
+
+.. image:: https://readthedocs.org/projects/pyramid/badge/?version=master
+ :target: http://docs.pylonsproject.org/projects/pyramid/en/master/
+ :alt: Documentation Status
+
Pyramid is a small, fast, down-to-earth, open source Python web framework.
It makes real-world web application development and
deployment more fun, more predictable, and more productive.
diff --git a/RELEASING.txt b/RELEASING.txt
index 553d2dcf2..0adef552c 100644
--- a/RELEASING.txt
+++ b/RELEASING.txt
@@ -26,6 +26,9 @@ Releasing Pyramid
- Copy relevant changes (delta bug fixes) from CHANGES.txt to
docs/whatsnew-X.X (if it's a major release).
+- update README.rst to use correct versions of badges and URLs according to
+ each branch and context, i.e., RTD "latest" == GitHub/Travis "1.x-branch".
+
- Make sure docs render OK::
$ cd docs
diff --git a/TODO.txt b/TODO.txt
index 62b8c39f4..e738b58d8 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -125,7 +125,10 @@ Future
- 1.7: Change ``pyramid.authentication.AuthTktAuthenticationPolicy`` default
``hashalg`` to ``sha512``.
-- 1.8 Remove set_request_property.
+- 1.8: Remove set_request_property.
+
+- 1.9: Remove extra code enabling ``pyramid.security.remember(principal=...)``
+ and force use of ``userid``.
Probably Bad Ideas
------------------
diff --git a/docs/_static/pyramid_request_processing.graffle b/docs/_static/pyramid_request_processing.graffle
new file mode 100644
index 000000000..71319610b
--- /dev/null
+++ b/docs/_static/pyramid_request_processing.graffle
@@ -0,0 +1,9748 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>ActiveLayerIndex</key>
+ <integer>0</integer>
+ <key>ApplicationVersion</key>
+ <array>
+ <string>com.omnigroup.OmniGrafflePro</string>
+ <string>139.18.0.187838</string>
+ </array>
+ <key>AutoAdjust</key>
+ <true/>
+ <key>BackgroundGraphic</key>
+ <dict>
+ <key>Bounds</key>
+ <string>{{0, 0}, {576, 733}}</string>
+ <key>Class</key>
+ <string>SolidGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>ID</key>
+ <integer>2</integer>
+ <key>Style</key>
+ <dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ </dict>
+ <key>BaseZoom</key>
+ <integer>0</integer>
+ <key>CanvasOrigin</key>
+ <string>{0, 0}</string>
+ <key>ColumnAlign</key>
+ <integer>1</integer>
+ <key>ColumnSpacing</key>
+ <real>36</real>
+ <key>CreationDate</key>
+ <string>2014-11-18 08:33:33 +0000</string>
+ <key>Creator</key>
+ <string>Steve Piercy</string>
+ <key>DisplayScale</key>
+ <string>1 0/72 in = 1 0/72 in</string>
+ <key>GraphDocumentVersion</key>
+ <integer>8</integer>
+ <key>GraphicsList</key>
+ <array>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169389</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169504</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{344.41667175292969, 402.88506673894034}</string>
+ <string>{375.5, 402.27232108797347}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169428</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169382</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169433</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{155.00000254313238, 459.27667544230695}</string>
+ <string>{238.5002713470962, 456.52468399152298}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169370</integer>
+ <key>Position</key>
+ <real>0.28820157051086426</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169383</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169432</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{155.00000254313238, 482.12574895537085}</string>
+ <string>{238.52297468463752, 508.35839132916635}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169370</integer>
+ <key>Position</key>
+ <real>0.5668826699256897</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>Group</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.8333613077798, 284.99999999999994}, {105.66668701171875, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169425</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, -0.49999999999999645}</string>
+ <string>{-0.49526813868737474, -0.4689979626999552}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 authorization}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.75000762939453, 412.15071036499205}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169426</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, 0.5}</string>
+ <string>{-0.49999999999999911, 0.49999999999999289}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 decorators egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.75000762939453, 303.65604172230951}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169427</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, -0.49999999999999645}</string>
+ <string>{-0.49526813868737474, -0.4689979626999552}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 decorators ingress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.75000762939453, 393.55704269887212}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169428</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 response adapter}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.75000762939453, 374.90099016834085}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169429</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view mapper egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.75000762939453, 341.36561209044055}, {105.66666412353516, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169430</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.75000762939453, 322.26348241170439}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169431</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view mapper ingress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>ID</key>
+ <integer>169424</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169422</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169423</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{155.00000254313238, 470.25295298442387}</string>
+ <string>{238.33861159880226, 482.4262543949045}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169370</integer>
+ <key>Position</key>
+ <real>0.42701038718223572</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83336130777977, 471.22620192028251}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169422</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 NewResponse}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169420</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169421</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{154.99998733539806, 128.68025330008533}</string>
+ <string>{239.83340199788393, 128.59152244387357}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169386</integer>
+ <key>Position</key>
+ <real>0.35945424437522888</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239.83340199788395, 117.31920169649808}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169420</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 NewRequest}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>TableGroup</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.1666056315114, 148.28868579864499}, {105.66669464111328, 33.08929443359375}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169418</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 URL dispatch}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.1666056315114, 181.37798023223874}, {105.66669464111328, 17.244049072265625}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169419</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 route predicates}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>GridH</key>
+ <array>
+ <integer>169418</integer>
+ <integer>169419</integer>
+ <array/>
+ </array>
+ <key>ID</key>
+ <integer>169417</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>TableGroup</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.16666158040482, 272}, {105.66666412353516, 33.08929443359375}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169412</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view lookup}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.16666158040482, 305.08929443359375}, {105.66666412353516, 17.244049072265625}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169413</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 predicates}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>GridH</key>
+ <array>
+ <integer>169412</integer>
+ <integer>169413</integer>
+ <array/>
+ </array>
+ <key>ID</key>
+ <integer>169411</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169407</integer>
+ <key>Info</key>
+ <integer>7</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169410</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{238.75000762939462, 430.80675844512831}</string>
+ <string>{207.66666666666765, 385.656005859375}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169426</integer>
+ <key>Info</key>
+ <integer>6</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169407</integer>
+ <key>Info</key>
+ <integer>8</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169409</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{239.33336141608385, 285.57837549845181}</string>
+ <string>{207.66666666666777, 353.07514659563753}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169425</integer>
+ <key>Info</key>
+ <integer>6</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -8.9999999999999432}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169381</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169408</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{155.00000254313238, 386.66442959065108}</string>
+ <string>{155.00000254313238, 422.21209462483216}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169407</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.16667048136482, 353.07514659563753}, {105.66666412353516, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169407</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{-0.49211360058019871, -0.49251945318722434}</string>
+ <string>{-0.49211360058019871, 0.49470854679786669}</string>
+ <string>{0.4984227008620481, 0.48463479169597612}</string>
+ <string>{0.49842270086204898, -0.5}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view pipeline}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169380</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169399</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{154.9999936421724, 258.44082431579938}</string>
+ <string>{238.8333613077798, 258.45536063967575}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169372</integer>
+ <key>Position</key>
+ <real>0.51973581314086914</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>Group</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{383.66662216186666, 130.51770718892479}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169393</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 internal process}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{383.66662216186666, 91.940789540609359}, {105.66666412353516, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169394</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 external process (middleware, tween)}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{383.66662216186666, 158.54998334248924}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169395</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{383.66662216186666, 186.58225949605369}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169396</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.327428</string>
+ <key>g</key>
+ <string>0.81823</string>
+ <key>r</key>
+ <string>0.995566</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 callback}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{383.66662216186666, 63.908513387045019}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169397</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 event}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{370.9999504089372, 42.910746256511771}, {132.66667175292969, 184.08924865722656}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169398</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{1, 0.5}</string>
+ <string>{1, -0.5}</string>
+ <string>{-1, 0.5}</string>
+ <string>{-1, -0.5}</string>
+ <string>{0.5, 1}</string>
+ <string>{-0.5, 1}</string>
+ <string>{0.5, -1}</string>
+ <string>{-0.5, -1}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>CornerRadius</key>
+ <real>5</real>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Align</key>
+ <integer>0</integer>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
+
+\f0\b\fs20 \cf0 Legend}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ <key>TextPlacement</key>
+ <integer>0</integer>
+ </dict>
+ </array>
+ <key>ID</key>
+ <integer>169391</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{233.5000012715667, 20.000000000000934}, {116, 14}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>ID</key>
+ <integer>169390</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Pad</key>
+ <integer>0</integer>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
+
+\f0\b\fs24 \cf0 &lt;%Canvas%&gt;}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{375.5, 391}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169389</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 BeforeRender}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 7.05596923828125}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169418</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169386</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{155.00000170434049, 119.22767858295661}</string>
+ <string>{154.99995295206804, 148.28868579864499}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169378</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169378</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169385</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{155.00000254313238, 67.727678571434836}</string>
+ <string>{155.00000254313238, 96.18303707668386}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169377</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.16667048136482, 509.6179466247504}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169384</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 middleware egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239, 497.23589324949899}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169383</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.327428</string>
+ <key>g</key>
+ <string>0.81823</string>
+ <key>r</key>
+ <string>0.995566</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 finished callbacks}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239, 445.23589324949717}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169382</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.327428</string>
+ <key>g</key>
+ <string>0.81823</string>
+ <key>r</key>
+ <string>0.995566</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 response callbacks}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.16667048136482, 422.21209462483216}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169381</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 tween egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83336130777977, 247.18303989230026}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169380</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 ContextFound}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.16667048136482, 222.18303707668389}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169379</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 traversal}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.16667048136482, 96.18303707668386}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169378</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 tween ingress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.16667048136482, 45.18303707668386}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169377</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 middleware ingress }</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169379</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169373</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{154.99995295206804, 198.62202930450437}</string>
+ <string>{155.00000254313238, 222.18303707668389}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169419</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 7.05596923828125}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169412</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169372</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{154.9999936421724, 245.22767856643924}</string>
+ <string>{154.9999936421724, 272}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169379</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -8.9999999999999432}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169407</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169371</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{154.9999936421724, 322.33334350585938}</string>
+ <string>{155.00000254313238, 353.07514659563753}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169413</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9839935302734375}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169384</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169370</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{155.00000254313238, 444.75673611958314}</string>
+ <string>{155.00000254313238, 509.6179466247504}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169381</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169444</integer>
+ <key>Info</key>
+ <integer>6</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169503</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{272.4166717529298, 537.32234122436705}</string>
+ <string>{420.4999504089364, 515.08928491955714}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169494</integer>
+ <key>Info</key>
+ <integer>5</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169444</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169502</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{272.50004831949906, 391.51558277923863}</string>
+ <string>{420.4999504089364, 472.78869058972316}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169493</integer>
+ <key>Info</key>
+ <integer>5</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169450</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169501</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 592.81693102013151}</string>
+ <string>{239, 583.78422005970799}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169438</integer>
+ <key>Position</key>
+ <real>0.28820157051086426</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169451</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169500</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 629.80996162681686}</string>
+ <string>{239, 640.78422005970981}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169438</integer>
+ <key>Position</key>
+ <real>0.5668826699256897</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>Group</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.8333613077798, 391.51558277923863}, {105.66668701171875, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169493</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, -0.49999999999999645}</string>
+ <string>{-0.49526813868737474, -0.4689979626999552}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 authorization}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 518.66629314423074}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169494</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, 0.5}</string>
+ <string>{-0.49999999999999911, 0.49999999999999289}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 decorators egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 410.17162450154819}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169495</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, -0.49999999999999645}</string>
+ <string>{-0.49526813868737474, -0.4689979626999552}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 decorators ingress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 500.07262547811081}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169496</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 response adapter}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 481.41657294757954}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169497</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view mapper egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 447.88119486967923}, {105.66666412353516, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169498</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 428.77906519094307}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169499</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view mapper ingress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>ID</key>
+ <integer>169492</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169490</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169491</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.166643778483959, 611.77452873049333}</string>
+ <string>{238.8333613077798, 611.77452873049333}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83336130777977, 600.50220798311784}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169490</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 NewResponse}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169488</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169489</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999986314263907, 140.3328574622312}</string>
+ <string>{239.83340199788393, 141.59152244387357}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169454</integer>
+ <key>Position</key>
+ <real>0.35945424437522888</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239.83340199788395, 130.31920169649808}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169488</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 NewRequest}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>TableGroup</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166605631511416, 166.28868579864499}, {105.66668701171875, 33.08929443359375}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169486</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 URL dispatch}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166605631511416, 199.37798023223874}, {105.66668701171875, 17.244049072265625}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169487</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 route predicates}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>GridH</key>
+ <array>
+ <integer>169486</integer>
+ <integer>169487</integer>
+ <array/>
+ </array>
+ <key>ID</key>
+ <integer>169485</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>TableGroup</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{420.5000406901047, 338.15028762817326}, {105.66668701171875, 33.08929443359375}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169483</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view lookup}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{420.5000406901047, 371.23958206176701}, {105.66668701171875, 17.244049072265625}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169484</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 predicates}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>GridH</key>
+ <array>
+ <integer>169483</integer>
+ <integer>169484</integer>
+ <array/>
+ </array>
+ <key>ID</key>
+ <integer>169482</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>TableGroup</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166661580404835, 335}, {105.66667175292969, 33.08929443359375}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169480</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view lookup}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166661580404835, 368.08929443359375}, {105.66667175292969, 17.244049072265625}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169481</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 predicates}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>GridH</key>
+ <array>
+ <integer>169480</integer>
+ <integer>169481</integer>
+ <array/>
+ </array>
+ <key>ID</key>
+ <integer>169479</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169475</integer>
+ <key>Info</key>
+ <integer>7</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169478</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{166.75000762939462, 537.32234122436694}</string>
+ <string>{135.66666666666765, 485}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169494</integer>
+ <key>Info</key>
+ <integer>6</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169475</integer>
+ <key>Info</key>
+ <integer>8</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169477</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{167.33336141608385, 392.09395827769049}</string>
+ <string>{135.66666666666777, 452.41914073626253}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169493</integer>
+ <key>Info</key>
+ <integer>6</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -8.9999999999999432}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169449</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169476</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 485.50842372576449}</string>
+ <string>{83.000002543132396, 548.10604731241608}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169475</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 452.41914073626253}, {105.66666412353516, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169475</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{-0.49211360058019871, -0.49251945318722434}</string>
+ <string>{-0.49211360058019871, 0.49470854679786669}</string>
+ <string>{0.4984227008620481, 0.48463479169597612}</string>
+ <string>{0.49842270086204898, -0.5}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view pipeline}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{51.333333333333314, 0}</string>
+ <string>{-0.66666666666662877, 58.666666666666686}</string>
+ <string>{0.66673293066804717, -58.666850540458825}</string>
+ <string>{-16.306719354194399, 0.26652623861849634}</string>
+ </array>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169443</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169474</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{369.66666666666669, 541}</string>
+ <string>{404.00000000000023, 362}</string>
+ <string>{420.36749776329049, 302.42112495959606}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.75663</string>
+ <key>g</key>
+ <string>0.756618</string>
+ <key>r</key>
+ <string>0.75664</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>Pattern</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{69.833333333332462, -0.72767857143483639}</string>
+ <string>{-0.66690523835279691, -51.044605218028948}</string>
+ <string>{0.66666666666674246, 51.044637362162291}</string>
+ <string>{-24.333271383961971, -0.13425428344572765}</string>
+ </array>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169443</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169473</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{310.66666666666754, 118.72767857143484}</string>
+ <string>{399.33333333333417, 216.62202930450439}</string>
+ <string>{420.37955338188368, 301.45369961823752}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.75663</string>
+ <key>g</key>
+ <string>0.756618</string>
+ <key>r</key>
+ <string>0.75664</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>Pattern</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{-3.9999491373696401, 78.910715080442856}</string>
+ <string>{92.666683130060392, 0.22547126950667007}</string>
+ </array>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169490</integer>
+ <key>Info</key>
+ <integer>3</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169472</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{473.33328247070392, 515.08928491955714}</string>
+ <string>{344.50002543131501, 611.77452873049333}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169444</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{31.999987284342428, -14.081351280212308}</string>
+ <string>{-32.166667938232536, 10.244050343831077}</string>
+ </array>
+ <key>ID</key>
+ <integer>169471</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{344.96346869509995, 346.26317428240412}</string>
+ <string>{389.8333346048999, 328.08928298950207}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169456</integer>
+ <key>Info</key>
+ <integer>3</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{31.999987284342428, -14.081351280212308}</string>
+ <string>{-28.500001271565793, 8.3333333333333144}</string>
+ </array>
+ <key>ID</key>
+ <integer>169470</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{344.98861594084059, 323.71068461220347}</string>
+ <string>{391.1666679382335, 313.6666666666664}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169455</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{31.999987284342428, -14.081351280212308}</string>
+ <string>{-40, 1.5446373167492311}</string>
+ </array>
+ <key>ID</key>
+ <integer>169469</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{344.9995533451783, 301.1612218744512}</string>
+ <string>{394.50000127156665, 299.00000000000045}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169442</integer>
+ <key>Info</key>
+ <integer>3</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{8.5833282470703125, -10.244596987647753}</string>
+ <string>{0, 0}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169457</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169468</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{272.41667175292969, 509.40064951817902}</string>
+ <string>{285.5, 503.81699882234847}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>Pattern</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169496</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169448</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169467</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999997456869679, 300.27229985501288}</string>
+ <string>{238.34892458824362, 260.57913893040109}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169440</integer>
+ <key>Position</key>
+ <real>0.51973581314086914</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>Group</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 214.61452811107179}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169460</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.756045</string>
+ <key>g</key>
+ <string>0.75004</string>
+ <key>r</key>
+ <string>0.994455</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 exception}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 130.51770718892479}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169461</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 internal process}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 91.940789540609359}, {105.66666412353516, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169462</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 external process (middleware, tween)}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 158.54998334248924}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169463</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 186.58225949605369}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169464</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.327428</string>
+ <key>g</key>
+ <string>0.81823</string>
+ <key>r</key>
+ <string>0.995566</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 callback}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 63.908513387045019}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169465</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 event}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{406.9999504089372, 42.910746256511771}, {132.66667175292969, 207.81692504882812}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169466</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{1, 0.5}</string>
+ <string>{1, -0.5}</string>
+ <string>{-1, 0.5}</string>
+ <string>{-1, -0.5}</string>
+ <string>{0.5, 1}</string>
+ <string>{-0.5, 1}</string>
+ <string>{0.5, -1}</string>
+ <string>{-0.5, -1}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>CornerRadius</key>
+ <real>5</real>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Align</key>
+ <integer>0</integer>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
+
+\f0\b\fs20 \cf0 Legend}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ <key>TextPlacement</key>
+ <integer>0</integer>
+ </dict>
+ </array>
+ <key>ID</key>
+ <integer>169459</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{233.5000012715667, 20.000000000000934}, {116, 14}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>ID</key>
+ <integer>169458</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Pad</key>
+ <integer>0</integer>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
+
+\f0\b\fs24 \cf0 &lt;%Canvas%&gt;}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{285.5, 492.544677734375}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169457</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 BeforeRender}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83337529500417, 335.17855853126167}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169456</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 HTTPForbidden}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83337529500417, 312.54463200342093}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169455</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 PredicateMismatch}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 7.05596923828125}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169486</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169454</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000001850648417, 128.2276785555359}</string>
+ <string>{82.999949137370791, 166.28868579864502}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169446</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169446</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169453</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 67.727678571434836}</string>
+ <string>{83.000002543132396, 105.18303707668386}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169445</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 671.51189931233432}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169452</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 middleware egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239, 629.51189931233432}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169451</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.327428</string>
+ <key>g</key>
+ <string>0.81823</string>
+ <key>r</key>
+ <string>0.995566</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 finished callbacks}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239, 572.5118993123325}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169450</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.327428</string>
+ <key>g</key>
+ <string>0.81823</string>
+ <key>r</key>
+ <string>0.995566</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 response callbacks}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 548.10604731241608}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169449</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 tween egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83336130777977, 249.18303989230026}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169448</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 ContextFound}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 240.18303707668389}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169447</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 traversal}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 105.18303707668386}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169446</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 tween ingress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 45.18303707668386}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169445</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 middleware ingress }</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{420.49995040893634, 472.78869058972316}, {105.66666412353516, 42.300594329833984}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169444</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{-0.49999999999999956, -0.5}</string>
+ <string>{-0.49999999999999956, 0.5}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 notfound_view / forbidden_view / exception_view}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{420.49995040893634, 290.66666666666691}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169443</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.756045</string>
+ <key>g</key>
+ <string>0.75004</string>
+ <key>r</key>
+ <string>0.994455</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 exception}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83336512247806, 289.91071033477789}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169442</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 HTTPNotFound}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169447</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169441</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999949137370791, 216.62202930450437}</string>
+ <string>{83.000002543132396, 240.18303707668389}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169487</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 7.05596923828125}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169480</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169440</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999997456869679, 263.22767855635425}</string>
+ <string>{82.999997456869679, 335}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169447</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -8.9999999999999432}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169475</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169439</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999997456869679, 385.33334350585938}</string>
+ <string>{83.000002543132396, 452.41914073626253}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169481</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9839935302734375}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169452</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169438</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 571.15068879140836}</string>
+ <string>{83.000002543132396, 671.51189931233421}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169449</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 7.055999755859375}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169483</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169437</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{473.33328247070392, 313.2113088426139}</string>
+ <string>{473.33338419596407, 338.15028762817326}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169443</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840011596679688}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169444</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169436</integer>
+ <key>Layer</key>
+ <integer>1</integer>
+ <key>Points</key>
+ <array>
+ <string>{473.33338359264764, 388.98363112285512}</string>
+ <string>{473.33328247070392, 472.78869058972316}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169484</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169358</integer>
+ <key>Info</key>
+ <integer>6</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169359</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{272.4166717529298, 537.32234122436705}</string>
+ <string>{420.4999504089364, 515.08928491955714}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169206</integer>
+ <key>Info</key>
+ <integer>5</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169358</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169360</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{272.50004831949906, 391.51558277923863}</string>
+ <string>{420.4999504089364, 472.78869058972316}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169205</integer>
+ <key>Info</key>
+ <integer>5</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169044</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169130</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 592.81693102013151}</string>
+ <string>{239, 583.78422005970799}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169128</integer>
+ <key>Position</key>
+ <real>0.28820157051086426</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169045</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169129</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 629.80996162681686}</string>
+ <string>{239, 640.78422005970981}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169128</integer>
+ <key>Position</key>
+ <real>0.5668826699256897</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>Group</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.8333613077798, 391.51558277923863}, {105.66668701171875, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169205</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, -0.49999999999999645}</string>
+ <string>{-0.49526813868737474, -0.4689979626999552}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 authorization}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 518.66629314423074}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169206</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, 0.5}</string>
+ <string>{-0.49999999999999911, 0.49999999999999289}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 decorators egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 410.17162450154819}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169207</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, -0.49999999999999645}</string>
+ <string>{-0.49526813868737474, -0.4689979626999552}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 decorators ingress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 500.07262547811081}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169208</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 response adapter}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 481.41657294757954}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169209</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view mapper egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 447.88119486967923}, {105.66666412353516, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169210</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{166.75000762939453, 428.77906519094307}, {105.66666412353516, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169211</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view mapper ingress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>ID</key>
+ <integer>169204</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169085</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169086</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.166643778483959, 611.77452873049333}</string>
+ <string>{238.8333613077798, 611.77452873049333}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83336130777977, 600.50220798311784}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169085</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 NewResponse}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169083</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169084</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999986314263907, 140.3328574622312}</string>
+ <string>{239.83340199788393, 141.59152244387357}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169048</integer>
+ <key>Position</key>
+ <real>0.35945424437522888</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239.83340199788395, 130.31920169649808}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169083</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 NewRequest}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>TableGroup</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166605631511416, 166.28868579864499}, {105.66668701171875, 33.08929443359375}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169081</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 URL dispatch}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166605631511416, 199.37798023223874}, {105.66668701171875, 17.244049072265625}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169082</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 route predicates}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>GridH</key>
+ <array>
+ <integer>169081</integer>
+ <integer>169082</integer>
+ <array/>
+ </array>
+ <key>ID</key>
+ <integer>169080</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>TableGroup</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{420.5000406901047, 338.15028762817326}, {105.66668701171875, 33.08929443359375}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169355</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view lookup}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{420.5000406901047, 371.23958206176701}, {105.66668701171875, 17.244049072265625}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169356</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 predicates}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>GridH</key>
+ <array>
+ <integer>169355</integer>
+ <integer>169356</integer>
+ <array/>
+ </array>
+ <key>ID</key>
+ <integer>169354</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>TableGroup</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166661580404835, 335}, {105.66667175292969, 33.08929443359375}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169075</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view lookup}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166661580404835, 368.08929443359375}, {105.66667175292969, 17.244049072265625}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169076</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 predicates}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>GridH</key>
+ <array>
+ <integer>169075</integer>
+ <integer>169076</integer>
+ <array/>
+ </array>
+ <key>ID</key>
+ <integer>169074</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169070</integer>
+ <key>Info</key>
+ <integer>7</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169073</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{166.75000762939462, 537.32234122436694}</string>
+ <string>{135.66666666666765, 485}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169206</integer>
+ <key>Info</key>
+ <integer>6</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169070</integer>
+ <key>Info</key>
+ <integer>8</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169072</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{167.33336141608385, 392.09395827769049}</string>
+ <string>{135.66666666666777, 452.41914073626253}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.755269</string>
+ <key>g</key>
+ <string>0.755239</string>
+ <key>r</key>
+ <string>0.75529</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>11</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169205</integer>
+ <key>Info</key>
+ <integer>6</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -8.9999999999999432}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169043</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169071</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 485.50842372576449}</string>
+ <string>{83.000002543132396, 548.10604731241608}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169070</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 452.41914073626253}, {105.66666412353516, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169070</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{-0.49211360058019871, -0.49251945318722434}</string>
+ <string>{-0.49211360058019871, 0.49470854679786669}</string>
+ <string>{0.4984227008620481, 0.48463479169597612}</string>
+ <string>{0.49842270086204898, -0.5}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view pipeline}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{51.333333333333314, 0}</string>
+ <string>{-0.66666666666662877, 58.666666666666686}</string>
+ <string>{0.66673293066804717, -58.666850540458825}</string>
+ <string>{-16.306719354194399, 0.26652623861849634}</string>
+ </array>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169344</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169345</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{369.66666666666669, 541}</string>
+ <string>{404.00000000000023, 362}</string>
+ <string>{420.36749776329049, 302.42112495959606}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.75663</string>
+ <key>g</key>
+ <string>0.756618</string>
+ <key>r</key>
+ <string>0.75664</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>Pattern</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{69.833333333332462, -0.72767857143483639}</string>
+ <string>{-0.66690523835279691, -51.044605218028948}</string>
+ <string>{0.66666666666674246, 51.044637362162291}</string>
+ <string>{-24.333271383961971, -0.13425428344572765}</string>
+ </array>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169344</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169346</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{310.66666666666754, 118.72767857143484}</string>
+ <string>{399.33333333333417, 216.62202930450439}</string>
+ <string>{420.37955338188368, 301.45369961823752}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.75663</string>
+ <key>g</key>
+ <string>0.756618</string>
+ <key>r</key>
+ <string>0.75664</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>Pattern</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{-3.9999491373696401, 78.910715080442856}</string>
+ <string>{92.666683130060392, 0.22547126950667007}</string>
+ </array>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169085</integer>
+ <key>Info</key>
+ <integer>3</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169361</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{473.33328247070392, 515.08928491955714}</string>
+ <string>{344.50002543131501, 611.77452873049333}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169358</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{31.999987284342428, -14.081351280212308}</string>
+ <string>{-32.166667938232536, 10.244050343831077}</string>
+ </array>
+ <key>ID</key>
+ <integer>169341</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{344.96346869509995, 346.26317428240412}</string>
+ <string>{389.8333346048999, 328.08928298950207}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169340</integer>
+ <key>Info</key>
+ <integer>3</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{31.999987284342428, -14.081351280212308}</string>
+ <string>{-28.500001271565793, 8.3333333333333144}</string>
+ </array>
+ <key>ID</key>
+ <integer>169337</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{344.98861594084059, 323.71068461220347}</string>
+ <string>{391.1666679382335, 313.6666666666664}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169336</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{31.999987284342428, -14.081351280212308}</string>
+ <string>{-40, 1.5446373167492311}</string>
+ </array>
+ <key>ID</key>
+ <integer>169333</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{344.9995533451783, 301.1612218744512}</string>
+ <string>{394.50000127156665, 299.00000000000045}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169332</integer>
+ <key>Info</key>
+ <integer>3</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{8.5833282470703125, -10.244596987647753}</string>
+ <string>{0, 0}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169051</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169062</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{272.41667175292969, 509.40064951817902}</string>
+ <string>{285.5, 503.81699882234847}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>Pattern</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169208</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169042</integer>
+ <key>Info</key>
+ <integer>4</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169061</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999997456869679, 300.27229985501288}</string>
+ <string>{238.34892458824362, 260.57913893040109}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169034</integer>
+ <key>Position</key>
+ <real>0.51973581314086914</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>Group</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 214.61452811107179}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169054</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.756045</string>
+ <key>g</key>
+ <string>0.75004</string>
+ <key>r</key>
+ <string>0.994455</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 exception}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 130.51770718892479}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169055</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 internal process}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 91.940789540609359}, {105.66666412353516, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169056</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 external process (middleware, tween)}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 158.54998334248924}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169057</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 view}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 186.58225949605369}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169058</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.327428</string>
+ <key>g</key>
+ <string>0.81823</string>
+ <key>r</key>
+ <string>0.995566</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 callback}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{419.66662216186666, 63.908513387045019}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169059</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 event}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{406.9999504089372, 42.910746256511771}, {132.66667175292969, 207.81692504882812}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169060</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{1, 0.5}</string>
+ <string>{1, -0.5}</string>
+ <string>{-1, 0.5}</string>
+ <string>{-1, -0.5}</string>
+ <string>{0.5, 1}</string>
+ <string>{-0.5, 1}</string>
+ <string>{0.5, -1}</string>
+ <string>{-0.5, -1}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>CornerRadius</key>
+ <real>5</real>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Align</key>
+ <integer>0</integer>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
+
+\f0\b\fs20 \cf0 Legend}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ <key>TextPlacement</key>
+ <integer>0</integer>
+ </dict>
+ </array>
+ <key>ID</key>
+ <integer>169053</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{233.5000012715667, 20.000000000000934}, {116, 14}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>ID</key>
+ <integer>169052</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Pad</key>
+ <integer>0</integer>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
+
+\f0\b\fs24 \cf0 &lt;%Canvas%&gt;}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{285.5, 492.544677734375}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169051</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 BeforeRender}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83337529500417, 335.17855853126167}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169340</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 HTTPForbidden}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83337529500417, 312.54463200342093}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169336</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 PredicateMismatch}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 7.05596923828125}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169081</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169048</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000001850648417, 128.2276785555359}</string>
+ <string>{82.999949137370791, 166.28868579864502}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169040</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169040</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169047</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 67.727678571434836}</string>
+ <string>{83.000002543132396, 105.18303707668386}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169039</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 671.51189931233432}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169046</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 middleware egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239, 629.51189931233432}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169045</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.327428</string>
+ <key>g</key>
+ <string>0.81823</string>
+ <key>r</key>
+ <string>0.995566</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 finished callbacks}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239, 572.5118993123325}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169044</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.327428</string>
+ <key>g</key>
+ <string>0.81823</string>
+ <key>r</key>
+ <string>0.995566</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 response callbacks}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 548.10604731241608}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169043</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 tween egress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83336130777977, 249.18303989230026}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169042</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 ContextFound}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 240.18303707668389}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169041</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 traversal}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 105.18303707668386}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169040</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 tween ingress}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{30.166670481364818, 45.18303707668386}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169039</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 middleware ingress }</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{420.49995040893634, 472.78869058972316}, {105.66666412353516, 42.300594329833984}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169358</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{-0.49999999999999956, -0.5}</string>
+ <string>{-0.49999999999999956, 0.5}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 notfound_view / forbidden_view / exception_view}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{420.49995040893634, 290.66666666666691}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169344</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.756045</string>
+ <key>g</key>
+ <string>0.75004</string>
+ <key>r</key>
+ <string>0.994455</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 exception}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.83336512247806, 289.91071033477789}, {105.66666412353516, 22.544642175946908}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169332</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 HTTPNotFound}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169041</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169035</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999949137370791, 216.62202930450437}</string>
+ <string>{83.000002543132396, 240.18303707668389}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169082</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 7.05596923828125}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169075</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169034</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999997456869679, 263.22767855635425}</string>
+ <string>{82.999997456869679, 335}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169041</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -8.9999999999999432}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169070</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169033</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{82.999997456869679, 385.33334350585938}</string>
+ <string>{83.000002543132396, 452.41914073626253}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169076</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9839935302734375}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169046</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169128</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{83.000002543132396, 571.15068879140836}</string>
+ <string>{83.000002543132396, 671.51189931233421}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169043</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 7.055999755859375}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169355</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169357</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{473.33328247070392, 313.2113088426139}</string>
+ <string>{473.33338419596407, 338.15028762817326}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169344</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840011596679688}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169358</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169362</integer>
+ <key>Layer</key>
+ <integer>2</integer>
+ <key>Points</key>
+ <array>
+ <string>{473.33338359264764, 388.98363112285512}</string>
+ <string>{473.33328247070392, 472.78869058972316}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169356</integer>
+ </dict>
+ </dict>
+ </array>
+ <key>GridInfo</key>
+ <dict/>
+ <key>GuidesLocked</key>
+ <string>NO</string>
+ <key>GuidesVisible</key>
+ <string>YES</string>
+ <key>HPages</key>
+ <integer>1</integer>
+ <key>ImageCounter</key>
+ <integer>3</integer>
+ <key>KeepToScale</key>
+ <false/>
+ <key>Layers</key>
+ <array>
+ <dict>
+ <key>Lock</key>
+ <string>NO</string>
+ <key>Name</key>
+ <string>no exceptions</string>
+ <key>Print</key>
+ <string>YES</string>
+ <key>View</key>
+ <string>YES</string>
+ </dict>
+ <dict>
+ <key>Lock</key>
+ <string>NO</string>
+ <key>Name</key>
+ <string>exceptions only</string>
+ <key>Print</key>
+ <string>YES</string>
+ <key>View</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Lock</key>
+ <string>NO</string>
+ <key>Name</key>
+ <string>all</string>
+ <key>Print</key>
+ <string>YES</string>
+ <key>View</key>
+ <string>NO</string>
+ </dict>
+ </array>
+ <key>LayoutInfo</key>
+ <dict>
+ <key>Animate</key>
+ <string>NO</string>
+ <key>circoMinDist</key>
+ <real>18</real>
+ <key>circoSeparation</key>
+ <real>0.0</real>
+ <key>layoutEngine</key>
+ <string>dot</string>
+ <key>neatoSeparation</key>
+ <real>0.0</real>
+ <key>twopiSeparation</key>
+ <real>0.0</real>
+ </dict>
+ <key>LinksVisible</key>
+ <string>NO</string>
+ <key>MagnetsVisible</key>
+ <string>NO</string>
+ <key>MasterSheets</key>
+ <array/>
+ <key>ModificationDate</key>
+ <string>2014-11-23 07:19:11 +0000</string>
+ <key>Modifier</key>
+ <string>Steve Piercy</string>
+ <key>NotesVisible</key>
+ <string>NO</string>
+ <key>Orientation</key>
+ <integer>2</integer>
+ <key>OriginVisible</key>
+ <string>NO</string>
+ <key>PageBreaks</key>
+ <string>YES</string>
+ <key>PrintInfo</key>
+ <dict>
+ <key>NSBottomMargin</key>
+ <array>
+ <string>float</string>
+ <string>41</string>
+ </array>
+ <key>NSHorizonalPagination</key>
+ <array>
+ <string>coded</string>
+ <string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG</string>
+ </array>
+ <key>NSLeftMargin</key>
+ <array>
+ <string>float</string>
+ <string>18</string>
+ </array>
+ <key>NSPaperSize</key>
+ <array>
+ <string>size</string>
+ <string>{612, 792}</string>
+ </array>
+ <key>NSPrintReverseOrientation</key>
+ <array>
+ <string>int</string>
+ <string>0</string>
+ </array>
+ <key>NSRightMargin</key>
+ <array>
+ <string>float</string>
+ <string>18</string>
+ </array>
+ <key>NSTopMargin</key>
+ <array>
+ <string>float</string>
+ <string>18</string>
+ </array>
+ </dict>
+ <key>PrintOnePage</key>
+ <false/>
+ <key>ReadOnly</key>
+ <string>NO</string>
+ <key>RowAlign</key>
+ <integer>1</integer>
+ <key>RowSpacing</key>
+ <real>36</real>
+ <key>SheetTitle</key>
+ <string>Request Processing</string>
+ <key>SmartAlignmentGuidesActive</key>
+ <string>YES</string>
+ <key>SmartDistanceGuidesActive</key>
+ <string>YES</string>
+ <key>UniqueID</key>
+ <integer>1</integer>
+ <key>UseEntirePage</key>
+ <false/>
+ <key>VPages</key>
+ <integer>1</integer>
+ <key>WindowInfo</key>
+ <dict>
+ <key>CurrentSheet</key>
+ <integer>0</integer>
+ <key>ExpandedCanvases</key>
+ <array>
+ <dict>
+ <key>name</key>
+ <string>Request Processing</string>
+ </dict>
+ </array>
+ <key>Frame</key>
+ <string>{{35, 93}, {1394, 1325}}</string>
+ <key>ListView</key>
+ <true/>
+ <key>OutlineWidth</key>
+ <integer>178</integer>
+ <key>RightSidebar</key>
+ <true/>
+ <key>ShowRuler</key>
+ <true/>
+ <key>Sidebar</key>
+ <true/>
+ <key>SidebarWidth</key>
+ <integer>163</integer>
+ <key>VisibleRegion</key>
+ <string>{{-231, -226}, {1037, 1186}}</string>
+ <key>Zoom</key>
+ <real>1</real>
+ <key>ZoomValues</key>
+ <array>
+ <array>
+ <string>Request Processing</string>
+ <real>1</real>
+ <real>2</real>
+ </array>
+ </array>
+ </dict>
+</dict>
+</plist>
diff --git a/docs/_static/pyramid_request_processing.png b/docs/_static/pyramid_request_processing.png
new file mode 100644
index 000000000..2fbb1e164
--- /dev/null
+++ b/docs/_static/pyramid_request_processing.png
Binary files differ
diff --git a/docs/_static/pyramid_request_processing.svg b/docs/_static/pyramid_request_processing.svg
new file mode 100644
index 000000000..21bbcb532
--- /dev/null
+++ b/docs/_static/pyramid_request_processing.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="91 11 424 533" width="424pt" height="533pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2014-11-23 07:19Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Request Processing</title><rect fill="white" width="576" height="733"/><g><title>no exceptions</title><path d="M 155 444.75674 C 155 450.64061 155 486.2592 155 502.71617" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 322.33334 C 154.99999 327.72413 155 337.74646 155 346.1775" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 245.22768 C 154.99999 250.5417 154.99999 257.93189 154.99999 265.10145" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99995 198.62203 C 154.99995 203.74682 154.99998 209.1909 154.99999 215.28222" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 50.455358)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="4.7596016" y="10" textLength="88.92578">middleware ingress </tspan></text><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 101.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.983723" y="10" textLength="61.69922">tween ingress</tspan></text><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 227.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="28.660969" y="10" textLength="38.344727">traversal</tspan></text><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 252.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.424641" y="10" textLength="62.817383">ContextFound</tspan></text><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 427.48442)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.094563" y="10" textLength="59.47754">tween egress</tspan></text><rect x="239" y="445.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="445.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 450.50821)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3113594" y="10" textLength="85.043945">response callbacks</tspan></text><rect x="239" y="497.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="497.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 502.5082)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.6463203" y="10" textLength="5">fi</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="13.64632" y="10" textLength="73.374023">nished callbacks</tspan></text><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 514.89027)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.8704414" y="10" textLength="83.92578">middleware egress</tspan></text><path d="M 155 67.72768 C 155 73.048893 155 81.55558 155 89.2853" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 155 119.22768 C 155 124.62026 154.99997 133.48763 154.99996 141.38632" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="375.5" y="391" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="391" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 396.27232)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.702961" y="10" textLength="62.260742">BeforeRender</tspan></text><text transform="translate(233.5 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".31445312" y="11" textLength="115.371094">Request Processing</tspan></text><path d="M 375.99995 42.910746 L 498.66662 42.910746 C 501.42805 42.910746 503.66662 45.149323 503.66662 47.910746 L 503.66662 222 C 503.66662 224.76142 501.42805 227 498.66662 227 L 375.99995 227 C 373.23853 227 370.99995 224.76142 370.99995 222 L 370.99995 47.910746 C 370.99995 45.149323 373.23853 42.910746 375.99995 42.910746 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(375.99995 42.910746)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="0" y="10" textLength="35.55664">Legend</tspan></text><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 69.180834)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.601887" y="10" textLength="24.46289">event</tspan></text><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" fill="#fed153"/><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 191.85458)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.769367" y="10" textLength="36.12793">callback</tspan></text><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" fill="#ffff6c"/><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 163.8223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.83089" y="10" textLength="20.004883">view</tspan></text><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" fill="#a4cfff"/><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 96.48543)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.148762" y="10" textLength="76.14746">external process </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="2.8162422" y="22" textLength="90.03418">(middleware, tween)</tspan></text><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 135.79003)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.537922" y="10" textLength="70.59082">internal process</tspan></text><line x1="154.99999" y1="258.44082" x2="238.83336" y2="258.45536" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 363.61979)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.205402" y="10" textLength="57.25586">view pipeline</tspan></text><path d="M 155 386.66443 C 155 392.17252 155 405.5052 155 415.30935" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="239.33336" y1="285.57838" x2="207.66667" y2="353.07515" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><line x1="238.75001" y1="430.80676" x2="207.66667" y2="385.656" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" fill="#d2ffd0"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 307.71132)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="24.764484" y="10" textLength="46.137695">predicates</tspan></text><rect x="102.16666" y="272" width="105.666664" height="33.089294" fill="#d2ffd0"/><rect x="102.16666" y="272" width="105.666664" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 282.54465)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.707844" y="10" textLength="52.250977">view lookup</tspan></text><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" fill="#d2ffd0"/><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 184)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.978855" y="10" textLength="71.708984">route predicates</tspan></text><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" fill="#d2ffd0"/><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 158.83333)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.001804" y="10" textLength="20.004883">URL</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.640476" y="10" textLength="40.024414"> dispatch</tspan></text><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.8334 122.59152)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.207844" y="10" textLength="57.250977">NewRequest</tspan></text><line x1="154.99999" y1="128.68025" x2="239.8334" y2="128.59152" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 476.49852)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="15.316242" y="10" textLength="65.03418">NewResponse</tspan></text><line x1="155" y1="470.25295" x2="238.33861" y2="482.42625" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75001" y="322.26348" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="322.26348" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 325.5915)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="1.9812813" y="10" textLength="91.7041">view mapper ingress</tspan></text><rect x="238.75001" y="341.36561" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="238.75001" y="341.36561" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 351.91025)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.83089" y="10" textLength="20.004883">view</tspan></text><rect x="238.75001" y="374.901" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="374.901" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 378.22901)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="3.0921211" y="10" textLength="89.48242">view mapper egress</tspan></text><rect x="238.75001" y="393.55704" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="393.55704" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 396.88507)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.9173164" y="10" textLength="77.83203">response adapter</tspan></text><rect x="238.75001" y="303.65604" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="303.65604" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 306.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="6.702961" y="10" textLength="82.26074">decorators ingress</tspan></text><rect x="238.75001" y="412.1507" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="412.1507" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 415.47873)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="7.813801" y="10" textLength="80.039062">decorators egress</tspan></text><rect x="238.83336" y="285" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.83336" y="285" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 288.32802)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.202972" y="10" textLength="57.260742">authorization</tspan></text><line x1="155" y1="482.12575" x2="238.52297" y2="508.3584" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="155" y1="459.27668" x2="238.50027" y2="456.52468" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="344.41667" y1="402.88507" x2="375.5" y2="402.27232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/></g></g></svg>
diff --git a/docs/_static/pyramid_router.graffle b/docs/_static/pyramid_router.graffle
new file mode 100644
index 000000000..217878426
--- /dev/null
+++ b/docs/_static/pyramid_router.graffle
@@ -0,0 +1,1621 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>ActiveLayerIndex</key>
+ <integer>0</integer>
+ <key>ApplicationVersion</key>
+ <array>
+ <string>com.omnigroup.OmniGrafflePro</string>
+ <string>139.18.0.187838</string>
+ </array>
+ <key>AutoAdjust</key>
+ <true/>
+ <key>BackgroundGraphic</key>
+ <dict>
+ <key>Bounds</key>
+ <string>{{0, 0}, {576, 733}}</string>
+ <key>Class</key>
+ <string>SolidGraphic</string>
+ <key>ID</key>
+ <integer>2</integer>
+ <key>Style</key>
+ <dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ </dict>
+ <key>BaseZoom</key>
+ <integer>0</integer>
+ <key>CanvasOrigin</key>
+ <string>{0, 0}</string>
+ <key>ColumnAlign</key>
+ <integer>1</integer>
+ <key>ColumnSpacing</key>
+ <real>36</real>
+ <key>CreationDate</key>
+ <string>2014-12-01 08:25:13 +0000</string>
+ <key>Creator</key>
+ <string>Steve Piercy</string>
+ <key>DisplayScale</key>
+ <string>1 0/72 in = 1 0/72 in</string>
+ <key>GraphDocumentVersion</key>
+ <integer>8</integer>
+ <key>GraphicsList</key>
+ <array>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169413</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169414</integer>
+ <key>Points</key>
+ <array>
+ <string>{202.04165903727232, 501.05557886759294}</string>
+ <string>{202.04165903727232, 528.77776209513161}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169412</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{104.41666666666686, 528.77776209513161}, {195.24998474121094, 29}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169413</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 Return the
+\b response}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{104.41666666666657, 471.55557886759294}, {195.24998474121094, 29}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169412</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 Invoke the
+\b view callable
+\b0 ,\
+which returns a
+\b response}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{291.21562524160186, 379.55555343627816}, {26, 24}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>ID</key>
+ <integer>169411</integer>
+ <key>Line</key>
+ <dict>
+ <key>ID</key>
+ <integer>169410</integer>
+ <key>Offset</key>
+ <real>7.3333320617675781</real>
+ <key>Position</key>
+ <real>0.4865129292011261</real>
+ <key>RotationType</key>
+ <integer>0</integer>
+ </dict>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
+
+\f0\fs24 \cf0 No}</string>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{34.791667904111534, 0}</string>
+ <string>{-33.999994913736998, 0}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169409</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169410</integer>
+ <key>Points</key>
+ <array>
+ <string>{280.85416589389337, 398.88888549804574}</string>
+ <string>{327.47912214508739, 398.88888549804574}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169404</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{327.47912214508739, 384.38888549804574}, {156.62496948242188, 29}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169409</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.756045</string>
+ <key>g</key>
+ <string>0.75004</string>
+ <key>r</key>
+ <string>0.994455</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 Return the
+\b Forbidden View}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{175.11595161998204, 438.9999954213917}, {30, 24}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>ID</key>
+ <integer>169408</integer>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
+
+\f0\fs24 \cf0 Yes}</string>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169412</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169407</integer>
+ <key>Points</key>
+ <array>
+ <string>{202.04165267944353, 437.33333079020139}</string>
+ <string>{202.04165903727204, 471.55557886759294}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169404</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{171.708317756653, 329.24978243601743}, {30, 24}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>ID</key>
+ <integer>169406</integer>
+ <key>Line</key>
+ <dict>
+ <key>ID</key>
+ <integer>169405</integer>
+ <key>Offset</key>
+ <real>-15.333334922790527</real>
+ <key>Position</key>
+ <real>0.45895844697952271</real>
+ <key>RotationType</key>
+ <integer>0</integer>
+ </dict>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
+
+\f0\fs24 \cf0 Yes}</string>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169404</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169405</integer>
+ <key>Points</key>
+ <array>
+ <string>{202.04165267944353, 326.72223360222029}</string>
+ <string>{202.04165267944353, 360.44446818033811}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>3</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{123.72916793823259, 360.44446818033811}, {156.62496948242188, 76.888862609863281}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169404</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Diamond</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 Current user has
+\b authorization
+\b0 to invoke the view callable?}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{283.07625736262997, 281.88889694213805}, {26, 24}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>ID</key>
+ <integer>169403</integer>
+ <key>Line</key>
+ <dict>
+ <key>ID</key>
+ <integer>169402</integer>
+ <key>Offset</key>
+ <real>7.3333320617675781</real>
+ <key>Position</key>
+ <real>0.4865129292011261</real>
+ <key>RotationType</key>
+ <integer>0</integer>
+ </dict>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
+
+\f0\fs24 \cf0 No}</string>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{34.791667904111534, 0}</string>
+ <string>{-33.999994913736998, 0}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169401</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169402</integer>
+ <key>Points</key>
+ <array>
+ <string>{265.20833208871704, 301.22222900390562}</string>
+ <string>{327.47911580403627, 301.22222900390562}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>3</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{327.47911580403627, 286.72222900390562}, {156.62496948242188, 29}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169401</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.756045</string>
+ <key>g</key>
+ <string>0.75004</string>
+ <key>r</key>
+ <string>0.994455</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 Return the
+\b Not Found View}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>3</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169400</integer>
+ <key>Points</key>
+ <array>
+ <string>{202.04165903727255, 251}</string>
+ <string>{202.04165776570633, 276.22223154703772}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169393</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{139.37498982747391, 276.22223154703778}, {125.33333587646484, 50}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>3</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Diamond</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 View callable found?}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169393</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169396</integer>
+ <key>Points</key>
+ <array>
+ <string>{202.04165903727255, 196.77777862548834}</string>
+ <string>{202.04165903727255, 222}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169392</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169392</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169395</integer>
+ <key>Points</key>
+ <array>
+ <string>{202.04165903727255, 142.55555725097662}</string>
+ <string>{202.04165903727255, 167.77777862548834}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169391</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169391</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169385</integer>
+ <key>Points</key>
+ <array>
+ <string>{202.04165903727255, 82.666667938232479}</string>
+ <string>{202.04165903727255, 107.88888931274418}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>19</integer>
+ <key>Info</key>
+ <integer>1</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{104.41666666666708, 222}, {195.24998474121094, 29}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169393</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 Look up a
+\b view callable
+\b0 in the
+\b registry
+\b0 using the
+\b context
+\b0 and
+\b view name}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{104.41666666666708, 167.77777862548834}, {195.24998474121094, 29}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169392</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs20 \cf0 Traversal
+\b0 locates\
+the
+\b context
+\b0 and
+\b view name}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{104.41666666666708, 107.88888931274418}, {195.24998474121094, 34.666667938232422}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169391</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 Traverse the model graph\
+from the
+\b root
+\b0 using the
+\b path}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{104.41666666666708, 48.000000000000043}, {195.24998474121094, 34.666667938232422}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>19</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.815377</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>0.820561</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 Obtain a root object from the
+\b root factory}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{229.04165903727255, 20.000000000000934}, {90, 14}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>ID</key>
+ <integer>169390</integer>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Pad</key>
+ <integer>0</integer>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
+
+\f0\b\fs24 \cf0 &lt;%Canvas%&gt;}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ </array>
+ <key>GridInfo</key>
+ <dict/>
+ <key>GuidesLocked</key>
+ <string>NO</string>
+ <key>GuidesVisible</key>
+ <string>YES</string>
+ <key>HPages</key>
+ <integer>1</integer>
+ <key>ImageCounter</key>
+ <integer>1</integer>
+ <key>KeepToScale</key>
+ <false/>
+ <key>Layers</key>
+ <array>
+ <dict>
+ <key>Lock</key>
+ <string>NO</string>
+ <key>Name</key>
+ <string>Layer 1</string>
+ <key>Print</key>
+ <string>YES</string>
+ <key>View</key>
+ <string>YES</string>
+ </dict>
+ </array>
+ <key>LayoutInfo</key>
+ <dict>
+ <key>Animate</key>
+ <string>NO</string>
+ <key>circoMinDist</key>
+ <real>18</real>
+ <key>circoSeparation</key>
+ <real>0.0</real>
+ <key>layoutEngine</key>
+ <string>dot</string>
+ <key>neatoSeparation</key>
+ <real>0.0</real>
+ <key>twopiSeparation</key>
+ <real>0.0</real>
+ </dict>
+ <key>LinksVisible</key>
+ <string>NO</string>
+ <key>MagnetsVisible</key>
+ <string>NO</string>
+ <key>MasterSheets</key>
+ <array/>
+ <key>ModificationDate</key>
+ <string>2014-12-01 09:19:51 +0000</string>
+ <key>Modifier</key>
+ <string>Steve Piercy</string>
+ <key>NotesVisible</key>
+ <string>NO</string>
+ <key>Orientation</key>
+ <integer>2</integer>
+ <key>OriginVisible</key>
+ <string>NO</string>
+ <key>PageBreaks</key>
+ <string>YES</string>
+ <key>PrintInfo</key>
+ <dict>
+ <key>NSBottomMargin</key>
+ <array>
+ <string>float</string>
+ <string>41</string>
+ </array>
+ <key>NSHorizonalPagination</key>
+ <array>
+ <string>coded</string>
+ <string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG</string>
+ </array>
+ <key>NSLeftMargin</key>
+ <array>
+ <string>float</string>
+ <string>18</string>
+ </array>
+ <key>NSPaperSize</key>
+ <array>
+ <string>size</string>
+ <string>{612, 792}</string>
+ </array>
+ <key>NSPrintReverseOrientation</key>
+ <array>
+ <string>int</string>
+ <string>0</string>
+ </array>
+ <key>NSRightMargin</key>
+ <array>
+ <string>float</string>
+ <string>18</string>
+ </array>
+ <key>NSTopMargin</key>
+ <array>
+ <string>float</string>
+ <string>18</string>
+ </array>
+ </dict>
+ <key>PrintOnePage</key>
+ <false/>
+ <key>ReadOnly</key>
+ <string>NO</string>
+ <key>RowAlign</key>
+ <integer>1</integer>
+ <key>RowSpacing</key>
+ <real>36</real>
+ <key>SheetTitle</key>
+ <string>Pyramid Router</string>
+ <key>SmartAlignmentGuidesActive</key>
+ <string>YES</string>
+ <key>SmartDistanceGuidesActive</key>
+ <string>YES</string>
+ <key>UniqueID</key>
+ <integer>1</integer>
+ <key>UseEntirePage</key>
+ <false/>
+ <key>VPages</key>
+ <integer>1</integer>
+ <key>WindowInfo</key>
+ <dict>
+ <key>CurrentSheet</key>
+ <integer>0</integer>
+ <key>ExpandedCanvases</key>
+ <array>
+ <dict>
+ <key>name</key>
+ <string>Pyramid Router</string>
+ </dict>
+ </array>
+ <key>Frame</key>
+ <string>{{96, 20}, {1076, 1286}}</string>
+ <key>ListView</key>
+ <false/>
+ <key>OutlineWidth</key>
+ <integer>142</integer>
+ <key>RightSidebar</key>
+ <true/>
+ <key>ShowRuler</key>
+ <true/>
+ <key>Sidebar</key>
+ <true/>
+ <key>SidebarWidth</key>
+ <integer>120</integer>
+ <key>VisibleRegion</key>
+ <string>{{8, -10}, {532, 754.66666666666663}}</string>
+ <key>Zoom</key>
+ <real>1.5</real>
+ <key>ZoomValues</key>
+ <array>
+ <array>
+ <string>Pyramid Router</string>
+ <real>1.5</real>
+ <real>1</real>
+ </array>
+ </array>
+ </dict>
+</dict>
+</plist>
diff --git a/docs/_static/pyramid_router.png b/docs/_static/pyramid_router.png
new file mode 100644
index 000000000..3c9f81158
--- /dev/null
+++ b/docs/_static/pyramid_router.png
Binary files differ
diff --git a/docs/_static/pyramid_router.svg b/docs/_static/pyramid_router.svg
new file mode 100644
index 000000000..1537777c9
--- /dev/null
+++ b/docs/_static/pyramid_router.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="93 11 403 558" width="403pt" height="558pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2014-12-01 09:19Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Pyramid Router</title><rect fill="white" width="576" height="733"/><g><title>Layer 1</title><text transform="translate(229.04166 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".32226562" y="11" textLength="89.35547">Pyramid Router</tspan></text><rect x="104.416667" y="48" width="195.24998" height="34.666668" fill="#d2ffd0"/><rect x="104.416667" y="48" width="195.24998" height="34.666668" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 59.333334)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x=".088371277" y="10" textLength="129.51172">Obtain a root object from the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="129.60009" y="10" textLength="55.561523">root factory</tspan></text><rect x="104.416667" y="107.88889" width="195.24998" height="34.666668" fill="#d2ffd0"/><rect x="104.416667" y="107.88889" width="195.24998" height="34.666668" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 113.22222)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.557121" y="10" textLength="6.1083984">T</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="41.29931" y="10" textLength="108.393555">raverse the model graph</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.551262" y="22" textLength="39.458008">from the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="69.00927" y="22" textLength="19.438477">root</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="88.447746" y="22" textLength="46.142578"> using the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="134.590324" y="22" textLength="21.108398">path</tspan></text><rect x="104.416667" y="167.77778" width="195.24998" height="29" fill="#d2ffd0"/><rect x="104.416667" y="167.77778" width="195.24998" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 170.27778)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="53.428215" y="10" textLength="6.1083984">T</tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="58.98974" y="10" textLength="38.36914">raversal</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="97.35888" y="10" textLength="34.46289"> locates</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="30.093254" y="22" textLength="16.6796875">the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="46.772942" y="22" textLength="35.561523">context</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="82.334465" y="22" textLength="22.241211"> and </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="104.575676" y="22" textLength="50.581055">view name</tspan></text><rect x="104.416667" y="222" width="195.24998" height="29" fill="#d2ffd0"/><rect x="104.416667" y="222" width="195.24998" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 224.5)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3471603" y="10" textLength="46.704102">Look up a </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="52.051262" y="10" textLength="61.14746">view callable</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="113.19872" y="10" textLength="30.019531"> in the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="143.21825" y="10" textLength="36.68457">registry</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="179.90282" y="10" textLength="2.7783203"> </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.750969" y="22" textLength="43.364258">using the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="60.115227" y="22" textLength="35.561523">context</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="95.67675" y="22" textLength="22.241211"> and </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="117.91796" y="22" textLength="50.581055">view name</tspan></text><path d="M 202.04166 82.66667 C 202.04166 87.86648 202.04166 94.31586 202.04166 100.98615" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 202.04166 142.55556 C 202.04166 147.75537 202.04166 154.20475 202.04166 160.87504" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 202.04166 196.77778 C 202.04166 201.97759 202.04166 208.42697 202.04166 215.09726" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 202.04166 276.22223 L 264.70833 301.22223 L 202.04166 326.22223 L 139.37499 301.22223 Z" fill="#ffff6c"/><path d="M 202.04166 276.22223 L 264.70833 301.22223 L 202.04166 326.22223 L 139.37499 301.22223 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(161.29499 288.72223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.905763" y="10" textLength="6.669922">V</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.399903" y="10" textLength="54.472656">iew callable </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="26.707032" y="22" textLength="30.585938">found?</tspan></text><path d="M 202.04166 251 C 202.04166 256.19981 202.04166 262.6492 202.04166 269.31949" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="327.47912" y="286.72223" width="156.62497" height="29" fill="#fec0c1"/><rect x="327.47912" y="286.72223" width="156.62497" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(332.47912 295.22223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="10.89061" y="10" textLength="49.472656">Return the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="60.363266" y="10" textLength="59.42871">Not Found V</tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="119.616196" y="10" textLength="16.118164">iew</tspan></text><path d="M 265.20833 301.22223 C 297.4314 301.22223 294.2168 301.22223 320.5783 301.22223" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(288.07626 286.8889)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x=".33007812" y="11" textLength="15.339844">No</tspan></text><path d="M 202.04165 360.44447 L 280.35414 398.8889 L 202.04165 437.33333 L 123.72917 398.8889 Z" fill="#ffff6c"/><path d="M 202.04165 360.44447 L 280.35414 398.8889 L 202.04165 437.33333 L 123.72917 398.8889 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(149.87354 380.12)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.495594" y="10" textLength="77.25586">Current user has </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x=".9462776" y="22" textLength="62.773438">authorization</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="63.719715" y="22" textLength="45.581055"> to invoke </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="14.26659" y="34" textLength="78.935547">the view callable?</tspan></text><path d="M 202.04165 326.72223 C 202.04165 332.1894 202.04165 344.2467 202.04165 353.54354" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(176.70832 334.24978)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x=".21191406" y="11" textLength="8.0039062">Y</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x="7.114258" y="11" textLength="12.673828">es</tspan></text><path d="M 202.04165 437.33333 C 202.04165 442.81278 202.04166 455.21977 202.04166 464.65762" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(180.11595 444)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".21191406" y="11" textLength="8.0039062">Y</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="7.114258" y="11" textLength="12.673828">es</tspan></text><rect x="327.47912" y="384.38889" width="156.62497" height="29" fill="#fec0c1"/><rect x="327.47912" y="384.38889" width="156.62497" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(332.47912 392.88889)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.439926" y="10" textLength="49.472656">Return the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="60.912582" y="10" textLength="58.330078">Forbidden V</tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="119.06688" y="10" textLength="16.118164">iew</tspan></text><path d="M 280.85417 398.88889 C 312.9685 398.88889 296.55343 398.88889 320.57617 398.88889" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(296.21563 384.55555)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".33007812" y="11" textLength="15.339844">No</tspan></text><rect x="104.416667" y="471.55558" width="195.24998" height="29" fill="#ffff6c"/><rect x="104.416667" y="471.55558" width="195.24998" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 474.05558)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="36.201653" y="10" textLength="48.920898">Invoke the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="85.12255" y="10" textLength="61.14746">view callable</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="146.27001" y="10" textLength="2.7783203">,</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.100578" y="22" textLength="70.585938">which returns a </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="105.686516" y="22" textLength="44.46289">response</tspan></text><rect x="104.416667" y="528.77776" width="195.24998" height="29" fill="#dfbeff"/><rect x="104.416667" y="528.77776" width="195.24998" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 537.27776)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="45.65722" y="10" textLength="49.472656">Return the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="95.129875" y="10" textLength="44.46289">response</tspan></text><path d="M 202.04166 501.05558 C 202.04166 506.35088 202.04166 514.3792 202.04166 521.8749" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g></g></svg>
diff --git a/docs/_themes b/docs/_themes
-Subproject 3bec9280a6cedb15e97e5899021aa8d723c2538
+Subproject b14bf8c2a0d95ae8e3d38d07ad3721370ae6f3f
diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst
index 0c630571f..faca0fbb6 100644
--- a/docs/api/exceptions.rst
+++ b/docs/api/exceptions.rst
@@ -5,14 +5,14 @@
.. automodule:: pyramid.exceptions
- .. autoclass:: BadCSRFToken
+ .. autoexception:: BadCSRFToken
- .. autoclass:: PredicateMismatch
+ .. autoexception:: PredicateMismatch
- .. autoclass:: Forbidden
+ .. autoexception:: Forbidden
- .. autoclass:: NotFound
+ .. autoexception:: NotFound
- .. autoclass:: ConfigurationError
+ .. autoexception:: ConfigurationError
- .. autoclass:: URLDecodeError
+ .. autoexception:: URLDecodeError
diff --git a/docs/api/httpexceptions.rst b/docs/api/httpexceptions.rst
index b50f10beb..d4cf97f1d 100644
--- a/docs/api/httpexceptions.rst
+++ b/docs/api/httpexceptions.rst
@@ -13,96 +13,96 @@
.. autofunction:: exception_response
- .. autoclass:: HTTPException
+ .. autoexception:: HTTPException
- .. autoclass:: HTTPOk
+ .. autoexception:: HTTPOk
- .. autoclass:: HTTPRedirection
+ .. autoexception:: HTTPRedirection
- .. autoclass:: HTTPError
+ .. autoexception:: HTTPError
- .. autoclass:: HTTPClientError
+ .. autoexception:: HTTPClientError
- .. autoclass:: HTTPServerError
+ .. autoexception:: HTTPServerError
- .. autoclass:: HTTPCreated
+ .. autoexception:: HTTPCreated
- .. autoclass:: HTTPAccepted
+ .. autoexception:: HTTPAccepted
- .. autoclass:: HTTPNonAuthoritativeInformation
+ .. autoexception:: HTTPNonAuthoritativeInformation
- .. autoclass:: HTTPNoContent
+ .. autoexception:: HTTPNoContent
- .. autoclass:: HTTPResetContent
+ .. autoexception:: HTTPResetContent
- .. autoclass:: HTTPPartialContent
+ .. autoexception:: HTTPPartialContent
- .. autoclass:: HTTPMultipleChoices
+ .. autoexception:: HTTPMultipleChoices
- .. autoclass:: HTTPMovedPermanently
+ .. autoexception:: HTTPMovedPermanently
- .. autoclass:: HTTPFound
+ .. autoexception:: HTTPFound
- .. autoclass:: HTTPSeeOther
+ .. autoexception:: HTTPSeeOther
- .. autoclass:: HTTPNotModified
+ .. autoexception:: HTTPNotModified
- .. autoclass:: HTTPUseProxy
+ .. autoexception:: HTTPUseProxy
- .. autoclass:: HTTPTemporaryRedirect
+ .. autoexception:: HTTPTemporaryRedirect
- .. autoclass:: HTTPBadRequest
+ .. autoexception:: HTTPBadRequest
- .. autoclass:: HTTPUnauthorized
+ .. autoexception:: HTTPUnauthorized
- .. autoclass:: HTTPPaymentRequired
+ .. autoexception:: HTTPPaymentRequired
- .. autoclass:: HTTPForbidden
+ .. autoexception:: HTTPForbidden
- .. autoclass:: HTTPNotFound
+ .. autoexception:: HTTPNotFound
- .. autoclass:: HTTPMethodNotAllowed
+ .. autoexception:: HTTPMethodNotAllowed
- .. autoclass:: HTTPNotAcceptable
+ .. autoexception:: HTTPNotAcceptable
- .. autoclass:: HTTPProxyAuthenticationRequired
+ .. autoexception:: HTTPProxyAuthenticationRequired
- .. autoclass:: HTTPRequestTimeout
+ .. autoexception:: HTTPRequestTimeout
- .. autoclass:: HTTPConflict
+ .. autoexception:: HTTPConflict
- .. autoclass:: HTTPGone
+ .. autoexception:: HTTPGone
- .. autoclass:: HTTPLengthRequired
+ .. autoexception:: HTTPLengthRequired
- .. autoclass:: HTTPPreconditionFailed
+ .. autoexception:: HTTPPreconditionFailed
- .. autoclass:: HTTPRequestEntityTooLarge
+ .. autoexception:: HTTPRequestEntityTooLarge
- .. autoclass:: HTTPRequestURITooLong
+ .. autoexception:: HTTPRequestURITooLong
- .. autoclass:: HTTPUnsupportedMediaType
+ .. autoexception:: HTTPUnsupportedMediaType
- .. autoclass:: HTTPRequestRangeNotSatisfiable
+ .. autoexception:: HTTPRequestRangeNotSatisfiable
- .. autoclass:: HTTPExpectationFailed
+ .. autoexception:: HTTPExpectationFailed
- .. autoclass:: HTTPUnprocessableEntity
+ .. autoexception:: HTTPUnprocessableEntity
- .. autoclass:: HTTPLocked
+ .. autoexception:: HTTPLocked
- .. autoclass:: HTTPFailedDependency
+ .. autoexception:: HTTPFailedDependency
- .. autoclass:: HTTPInternalServerError
+ .. autoexception:: HTTPInternalServerError
- .. autoclass:: HTTPNotImplemented
+ .. autoexception:: HTTPNotImplemented
- .. autoclass:: HTTPBadGateway
+ .. autoexception:: HTTPBadGateway
- .. autoclass:: HTTPServiceUnavailable
+ .. autoexception:: HTTPServiceUnavailable
- .. autoclass:: HTTPGatewayTimeout
+ .. autoexception:: HTTPGatewayTimeout
- .. autoclass:: HTTPVersionNotSupported
+ .. autoexception:: HTTPVersionNotSupported
- .. autoclass:: HTTPInsufficientStorage
+ .. autoexception:: HTTPInsufficientStorage
diff --git a/docs/api/index.rst b/docs/api/index.rst
new file mode 100644
index 000000000..cb38aa0b2
--- /dev/null
+++ b/docs/api/index.rst
@@ -0,0 +1,12 @@
+.. _html_api_documentation:
+
+API Documentation
+=================
+
+Comprehensive reference material for every public API exposed by :app:`Pyramid`:
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ *
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst
index d8d935afd..a62976d8a 100644
--- a/docs/api/interfaces.rst
+++ b/docs/api/interfaces.rst
@@ -86,3 +86,5 @@ Other Interfaces
.. autointerface:: IResourceURL
:members:
+ .. autointerface:: ICacheBuster
+ :members:
diff --git a/docs/api/registry.rst b/docs/api/registry.rst
index bab3e26ba..57a80b3f5 100644
--- a/docs/api/registry.rst
+++ b/docs/api/registry.rst
@@ -14,6 +14,18 @@
accessed as ``request.registry.settings`` or
``config.registry.settings`` in a typical Pyramid application.
+ .. attribute:: package_name
+
+ .. versionadded:: 1.6
+
+ When a registry is set up (or created) by a :term:`Configurator`, this
+ attribute will be the shortcut for
+ :attr:`pyramid.config.Configurator.package_name`.
+
+ This attribute is often accessed as ``request.registry.package_name`` or
+ ``config.registry.package_name`` or ``config.package_name``
+ in a typical Pyramid application.
+
.. attribute:: introspector
.. versionadded:: 1.3
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 343d0c022..dd68fa09c 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -167,37 +167,40 @@
.. versionadded:: 1.5
- A property which returns the userid of the currently authenticated user
- or ``None`` if there is no :term:`authentication policy` in effect or
- there is no currently authenticated user. This differs from
- :attr:`~pyramid.request.Request.unauthenticated_userid`, because the
- effective authentication policy will have ensured that a record
- associated with the userid exists in persistent storage; if it has
- not, this value will be ``None``.
+ A property which returns the :term:`userid` of the currently
+ authenticated user or ``None`` if there is no :term:`authentication
+ policy` in effect or there is no currently authenticated user. This
+ differs from :attr:`~pyramid.request.Request.unauthenticated_userid`,
+ because the effective authentication policy will have ensured that a
+ record associated with the :term:`userid` exists in persistent storage;
+ if it has not, this value will be ``None``.
.. attribute:: unauthenticated_userid
.. versionadded:: 1.5
A property which returns a value which represents the *claimed* (not
- verified) user id of the credentials present in the request. ``None`` if
- there is no :term:`authentication policy` in effect or there is no user
- data associated with the current request. This differs from
- :attr:`~pyramid.request.Request.authenticated_userid`, because the
- effective authentication policy will not ensure that a record associated
- with the userid exists in persistent storage. Even if the userid
- does not exist in persistent storage, this value will be the value
- of the userid *claimed* by the request data.
+ verified) :term:`userid` of the credentials present in the
+ request. ``None`` if there is no :term:`authentication policy` in effect
+ or there is no user data associated with the current request. This
+ differs from :attr:`~pyramid.request.Request.authenticated_userid`,
+ because the effective authentication policy will not ensure that a
+ record associated with the :term:`userid` exists in persistent storage.
+ Even if the :term:`userid` does not exist in persistent storage, this
+ value will be the value of the :term:`userid` *claimed* by the request
+ data.
.. attribute:: effective_principals
.. versionadded:: 1.5
A property which returns the list of 'effective' :term:`principal`
- identifiers for this request. This will include the userid of the
- currently authenticated user if a user is currently authenticated. If no
- :term:`authentication policy` is in effect, this will return a sequence
- containing only the :attr:`pyramid.security.Everyone` principal.
+ identifiers for this request. This list typically includes the
+ :term:`userid` of the currently authenticated user if a user is
+ currently authenticated, but this depends on the
+ :term:`authentication policy` in effect. If no :term:`authentication
+ policy` is in effect, this will return a sequence containing only the
+ :attr:`pyramid.security.Everyone` principal.
.. method:: invoke_subrequest(request, use_tweens=False)
@@ -319,7 +322,13 @@
def _connect(request):
conn = request.registry.dbsession()
- def cleanup(_):
+ def cleanup(request):
+ # since version 1.5, request.exception is no
+ # longer eagerly cleared
+ if request.exception is not None:
+ conn.rollback()
+ else:
+ conn.commit()
conn.close()
request.add_finished_callback(cleanup)
return conn
diff --git a/docs/api/security.rst b/docs/api/security.rst
index 814b68e5a..88086dbbf 100644
--- a/docs/api/security.rst
+++ b/docs/api/security.rst
@@ -16,7 +16,7 @@ Authentication API Functions
.. autofunction:: forget
-.. autofunction:: remember
+.. autofunction:: remember(request, userid, **kwargs)
Authorization API Functions
---------------------------
diff --git a/docs/api/static.rst b/docs/api/static.rst
index c28473584..543e526ad 100644
--- a/docs/api/static.rst
+++ b/docs/api/static.rst
@@ -9,3 +9,11 @@
:members:
:inherited-members:
+ .. autoclass:: PathSegmentMd5CacheBuster
+ :members:
+
+ .. autoclass:: QueryStringMd5CacheBuster
+ :members:
+
+ .. autoclass:: QueryStringConstantCacheBuster
+ :members:
diff --git a/docs/conf.py b/docs/conf.py
index eba776628..fa4578275 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -57,8 +57,9 @@ extensions = [
# Looks for objects in external projects
intersphinx_mapping = {
- 'tutorials': ('http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/', None),
- 'jinja2': ('http://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/', None),
+ 'tutorials': ('http://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None),
+ 'cookbook': ('http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None),
+ 'jinja2': ('http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None),
'tm': (
'http://docs.pylonsproject.org/projects/pyramid_tm/en/latest/',
None,
@@ -73,7 +74,7 @@ intersphinx_mapping = {
'http://docs.pylonsproject.org/projects/deform/en/latest',
None),
'sqla': ('http://docs.sqlalchemy.org/en/latest', None),
- 'who': ('http://docs.repoze.org/who/latest', None),
+ 'who': ('http://repozewho.readthedocs.org/en/latest', None),
'python': ('http://docs.python.org', None),
'python3': ('http://docs.python.org/3', None),
'tstring':
@@ -82,10 +83,10 @@ intersphinx_mapping = {
'venusian':
('http://docs.pylonsproject.org/projects/venusian/en/latest', None),
'toolbar':
- ('http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest',
+ ('http://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest',
None),
'zcml':
- ('http://docs.pylonsproject.org/projects/pyramid_zcml/en/latest',
+ ('http://docs.pylonsproject.org/projects/pyramid-zcml/en/latest',
None),
}
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 2cc461a77..911c22075 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -16,6 +16,10 @@ Glossary
An object which, provided a :term:`WSGI` environment as a single
positional argument, returns a Pyramid-compatible request.
+ response factory
+ An object which, provided a :term:`request` as a single positional
+ argument, returns a Pyramid-compatible response.
+
response
An object returned by a :term:`view callable` that represents response
data returned to the requesting user agent. It must implement the
@@ -286,13 +290,22 @@ Glossary
:term:`authorization policy`.
principal
- A *principal* is a string or unicode object representing a userid
- or a group id. It is provided by an :term:`authentication
- policy`. For example, if a user had the user id "bob", and Bob
- was part of two groups named "group foo" and "group bar", the
- request might have information attached to it that would
- indicate that Bob was represented by three principals: "bob",
- "group foo" and "group bar".
+ A *principal* is a string or unicode object representing an
+ entity, typically a user or group. Principals are provided by an
+ :term:`authentication policy`. For example, if a user had the
+ :term:`userid` `"bob"`, and was part of two groups named `"group foo"`
+ and "group bar", the request might have information attached to
+ it that would indicate that Bob was represented by three
+ principals: `"bob"`, `"group foo"` and `"group bar"`.
+
+ userid
+ A *userid* is a string or unicode object used to identify and
+ authenticate a real-world user (or client). A userid is
+ supplied to an :term:`authentication policy` in order to discover
+ the user's :term:`principals <principal>`. The default behavior
+ of the authentication policies :app:`Pyramid` provides is to
+ return the user's userid as a principal, but this is not strictly
+ necessary in custom policies that define their principals differently.
authorization policy
An authorization policy in :app:`Pyramid` terms is a bit of
@@ -749,9 +762,16 @@ Glossary
made. For example the word "java" might be translated
differently if the translation domain is "programming-languages"
than would be if the translation domain was "coffee". A
- translation domain is represnted by a collection of ``.mo`` files
+ translation domain is represented by a collection of ``.mo`` files
within one or more :term:`translation directory` directories.
+ Translation Context
+ A string representing the "context" in which a translation was
+ made within a given :term:`translation domain`. See the gettext
+ documentation, `11.2.5 Using contexts for solving ambiguities
+ <https://www.gnu.org/software/gettext/manual/gettext.html#Contexts>`_
+ for more information.
+
Translator
A callable which receives a :term:`translation string` and returns a
translated Unicode object for the purposes of internationalization. A
@@ -936,7 +956,7 @@ Glossary
`Akhet <http://docs.pylonsproject.org/projects/akhet/en/latest/>`_ is a
Pyramid library and demo application with a Pylons-like feel.
It's most known for its former application scaffold, which helped
- users transition from Pylons and those prefering a more Pylons-like API.
+ users transition from Pylons and those preferring a more Pylons-like API.
The scaffold has been retired but the demo plays a similar role.
Pyramid Cookbook
diff --git a/docs/index.rst b/docs/index.rst
index 78a00966d..fc7560e8f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -119,6 +119,8 @@ Narrative documentation in chapter form explaining how to use
narr/threadlocals
narr/zca
+.. _html_tutorials:
+
Tutorials
=========
@@ -133,8 +135,6 @@ platforms.
tutorials/wiki/index.rst
tutorials/modwsgi/index.rst
-.. _html_api_documentation:
-
API Documentation
=================
@@ -144,6 +144,7 @@ Comprehensive reference material for every public API exposed by :app:`Pyramid`:
:maxdepth: 1
:glob:
+ api/index
api/*
Change History
diff --git a/docs/narr/MyProject/myproject/templates/mytemplate.pt b/docs/narr/MyProject/myproject/templates/mytemplate.pt
index d1af4f42c..e6b00a145 100644
--- a/docs/narr/MyProject/myproject/templates/mytemplate.pt
+++ b/docs/narr/MyProject/myproject/templates/mytemplate.pt
@@ -50,7 +50,7 @@
</div>
<div class="row">
<div class="copyright">
- Copyright © Pylons Project
+ Copyright &copy; Pylons Project
</div>
</div>
</div>
diff --git a/docs/narr/MyProject/myproject/tests.py b/docs/narr/MyProject/myproject/tests.py
index 64dcab1d5..8c60407e5 100644
--- a/docs/narr/MyProject/myproject/tests.py
+++ b/docs/narr/MyProject/myproject/tests.py
@@ -15,3 +15,40 @@ class ViewTests(unittest.TestCase):
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'MyProject')
+
+class ViewIntegrationTests(unittest.TestCase):
+ def setUp(self):
+ """ This sets up the application registry with the
+ registrations your application declares in its ``includeme``
+ function.
+ """
+ self.config = testing.setUp()
+ self.config.include('myproject')
+
+ def tearDown(self):
+ """ Clear out the application registry """
+ testing.tearDown()
+
+ def test_my_view(self):
+ from myproject.views import my_view
+ request = testing.DummyRequest()
+ result = my_view(request)
+ self.assertEqual(result.status, '200 OK')
+ body = result.app_iter[0]
+ self.assertTrue('Welcome to' in body)
+ self.assertEqual(len(result.headerlist), 2)
+ self.assertEqual(result.headerlist[0],
+ ('Content-Type', 'text/html; charset=UTF-8'))
+ self.assertEqual(result.headerlist[1], ('Content-Length',
+ str(len(body))))
+
+class FunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from myproject import main
+ app = main({})
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+
+ def test_root(self):
+ res = self.testapp.get('/', status=200)
+ self.assertTrue('Pyramid' in res.body)
diff --git a/docs/narr/MyProject/setup.py b/docs/narr/MyProject/setup.py
index 8c019af51..9f34540a7 100644
--- a/docs/narr/MyProject/setup.py
+++ b/docs/narr/MyProject/setup.py
@@ -1,30 +1,42 @@
-import os
+"""Setup for the MyProject package.
+"""
+import os
from setuptools import setup, find_packages
-here = os.path.abspath(os.path.dirname(__file__))
-with open(os.path.join(here, 'README.txt')) as f:
- README = f.read()
-with open(os.path.join(here, 'CHANGES.txt')) as f:
- CHANGES = f.read()
-requires = [
+HERE = os.path.abspath(os.path.dirname(__file__))
+
+
+with open(os.path.join(HERE, 'README.txt')) as fp:
+ README = fp.read()
+
+
+with open(os.path.join(HERE, 'CHANGES.txt')) as fp:
+ CHANGES = fp.read()
+
+
+REQUIRES = [
'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
'waitress',
]
+TESTS_REQUIRE = [
+ 'webtest'
+ ]
+
setup(name='MyProject',
version='0.0',
description='MyProject',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
author='',
author_email='',
url='',
@@ -32,11 +44,10 @@ setup(name='MyProject',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- install_requires=requires,
- tests_require=requires,
- test_suite="myproject",
+ install_requires=REQUIRES,
+ tests_require=TESTS_REQUIRE,
+ test_suite='myproject',
entry_points="""\
[paste.app_factory]
main = myproject:main
- """,
- )
+ """)
diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst
index b0a8d18b0..fc908c2b4 100644
--- a/docs/narr/assets.rst
+++ b/docs/narr/assets.rst
@@ -276,15 +276,237 @@ to put static media on a separate webserver during production (if the
``name`` argument to :meth:`~pyramid.config.Configurator.add_static_view` is
a URL), while keeping static media package-internal and served by the
development webserver during development (if the ``name`` argument to
-:meth:`~pyramid.config.Configurator.add_static_view` is a URL prefix). To
-create such a circumstance, we suggest using the
-:attr:`pyramid.registry.Registry.settings` API in conjunction with a setting
-in the application ``.ini`` file named ``media_location``. Then set the
-value of ``media_location`` to either a prefix or a URL depending on whether
-the application is being run in development or in production (use a different
-``.ini`` file for production than you do for development). This is just a
-suggestion for a pattern; any setting name other than ``media_location``
-could be used.
+:meth:`~pyramid.config.Configurator.add_static_view` is a URL prefix).
+
+For example, we may define a :ref:`custom setting <adding_a_custom_setting>`
+named ``media_location`` which we can set to an external URL in production
+when our assets are hosted on a CDN.
+
+.. code-block:: python
+ :linenos:
+
+ media_location = settings.get('media_location', 'static')
+
+ config = Configurator(settings=settings)
+ config.add_static_view(path='myapp:static', name=media_location)
+
+Now we can optionally define the setting in our ini file:
+
+.. code-block:: ini
+ :linenos:
+
+ # production.ini
+ [app:main]
+ use = egg:myapp#main
+
+ media_location = http://static.example.com/
+
+It is also possible to serve assets that live outside of the source by
+referring to an absolute path on the filesystem. There are two ways to
+accomplish this.
+
+First, :meth:`~pyramid.config.Configurator.add_static_view`
+supports taking an absolute path directly instead of an asset spec. This works
+as expected, looking in the file or folder of files and serving them up at
+some URL within your application or externally. Unfortunately, this technique
+has a drawback that it is not possible to use the
+:meth:`~pyramid.request.Request.static_url` method to generate URLs, since it
+works based on an asset spec.
+
+The second approach, available in Pyramid 1.6+, uses the asset overriding
+APIs described in the :ref:`overriding_assets_section` section. It is then
+possible to configure a "dummy" package which then serves its file or folder
+from an absolute path.
+
+.. code-block:: python
+
+ config.add_static_view(path='myapp:static_images', name='static')
+ config.override_asset(to_override='myapp:static_images/',
+ override_with='/abs/path/to/images/')
+
+From this configuration it is now possible to use
+:meth:`~pyramid.request.Request.static_url` to generate URLs to the data
+in the folder by doing something like
+``request.static_url('myapp:static_images/foo.png')``. While it is not
+necessary that the ``static_images`` file or folder actually exist in the
+``myapp`` package, it is important that the ``myapp`` portion points to a
+valid package. If the folder does exist then the overriden folder is given
+priority if the file's name exists in both locations.
+
+.. index::
+ single: Cache Busting
+
+.. _cache_busting:
+
+Cache Busting
+-------------
+
+.. versionadded:: 1.6
+
+In order to maximize performance of a web application, you generally want to
+limit the number of times a particular client requests the same static asset.
+Ideally a client would cache a particular static asset "forever", requiring
+it to be sent to the client a single time. The HTTP protocol allows you to
+send headers with an HTTP response that can instruct a client to cache a
+particular asset for an amount of time. As long as the client has a copy of
+the asset in its cache and that cache hasn't expired, the client will use the
+cached copy rather than request a new copy from the server. The drawback to
+sending cache headers to the client for a static asset is that at some point
+the static asset may change, and then you'll want the client to load a new copy
+of the asset. Under normal circumstances you'd just need to wait for the
+client's cached copy to expire before they get the new version of the static
+resource.
+
+A commonly used workaround to this problem is a technique known as "cache
+busting". Cache busting schemes generally involve generating a URL for a
+static asset that changes when the static asset changes. This way headers can
+be sent along with the static asset instructing the client to cache the asset
+for a very long time. When a static asset is changed, the URL used to refer to
+it in a web page also changes, so the client sees it as a new resource and
+requests a copy, regardless of any caching policy set for the resource's old
+URL.
+
+:app:`Pyramid` can be configured to produce cache busting URLs for static
+assets by passing the optional argument, ``cachebust`` to
+:meth:`~pyramid.config.Configurator.add_static_view`:
+
+.. code-block:: python
+ :linenos:
+
+ # config is an instance of pyramid.config.Configurator
+ config.add_static_view(name='static', path='mypackage:folder/static',
+ cachebust=True)
+
+Setting the ``cachebust`` argument instructs :app:`Pyramid` to use a cache
+busting scheme which adds the md5 checksum for a static asset as a path segment
+in the asset's URL:
+
+.. code-block:: python
+ :linenos:
+
+ js_url = request.static_url('mypackage:folder/static/js/myapp.js')
+ # Returns: 'http://www.example.com/static/c9658b3c0a314a1ca21e5988e662a09e/js/myapp.js`
+
+When the asset changes, so will its md5 checksum, and therefore so will its
+URL. Supplying the ``cachebust`` argument also causes the static view to set
+headers instructing clients to cache the asset for ten years, unless the
+``max_cache_age`` argument is also passed, in which case that value is used.
+
+.. note::
+
+ md5 checksums are cached in RAM so if you change a static resource without
+ restarting your application, you may still generate URLs with a stale md5
+ checksum.
+
+Disabling the Cache Buster
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It can be useful in some situations (e.g. development) to globally disable all
+configured cache busters without changing calls to
+:meth:`~pyramid.config.Configurator.add_static_view`. To do this set the
+``PYRAMID_PREVENT_CACHEBUST`` environment variable or the
+``pyramid.prevent_cachebust`` configuration value to a true value.
+
+Customizing the Cache Buster
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Revisiting from the previous section:
+
+.. code-block:: python
+ :linenos:
+
+ # config is an instance of pyramid.config.Configurator
+ config.add_static_view(name='static', path='mypackage:folder/static',
+ cachebust=True)
+
+Setting ``cachebust`` to ``True`` instructs :app:`Pyramid` to use a default
+cache busting implementation that should work for many situations. The
+``cachebust`` may be set to any object that implements the interface,
+:class:`~pyramid.interfaces.ICacheBuster`. The above configuration is exactly
+equivalent to:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.static import PathSegmentMd5CacheBuster
+
+ # config is an instance of pyramid.config.Configurator
+ config.add_static_view(name='static', path='mypackage:folder/static',
+ cachebust=PathSegmentMd5CacheBuster())
+
+:app:`Pyramid` includes a handful of ready to use cache buster implementations:
+:class:`~pyramid.static.PathSegmentMd5CacheBuster`, which inserts an md5
+checksum token in the path portion of the asset's URL,
+:class:`~pyramid.static.QueryStringMd5CacheBuster`, which adds an md5 checksum
+token to the query string of the asset's URL, and
+:class:`~pyramid.static.QueryStringConstantCacheBuster`, which adds an
+arbitrary token you provide to the query string of the asset's URL.
+
+In order to implement your own cache buster, you can write your own class from
+scratch which implements the :class:`~pyramid.interfaces.ICacheBuster`
+interface. Alternatively you may choose to subclass one of the existing
+implementations. One of the most likely scenarios is you'd want to change the
+way the asset token is generated. To do this just subclass an existing
+implementation and replace the :meth:`~pyramid.interfaces.ICacheBuster.token`
+method. Here is an example which just uses Git to get the hash of the
+currently checked out code:
+
+.. code-block:: python
+ :linenos:
+
+ import os
+ import subprocess
+ from pyramid.static import PathSegmentMd5CacheBuster
+
+ class GitCacheBuster(PathSegmentMd5CacheBuster):
+ """
+ Assuming your code is installed as a Git checkout, as opposed to as an
+ egg from an egg repository like PYPI, you can use this cachebuster to
+ get the current commit's SHA1 to use as the cache bust token.
+ """
+ def __init__(self):
+ here = os.path.dirname(os.path.abspath(__file__))
+ self.sha1 = subprocess.check_output(
+ ['git', 'rev-parse', 'HEAD'],
+ cwd=here).strip()
+
+ def token(self, pathspec):
+ return self.sha1
+
+Choosing a Cache Buster
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The default cache buster implementation,
+:class:`~pyramid.static.PathSegmentMd5CacheBuster`, works very well assuming
+that you're using :app:`Pyramid` to serve your static assets. The md5 checksum
+is fine grained enough that browsers should only request new versions of
+specific assets that have changed. Many caching HTTP proxies will fail to
+cache a resource if the URL contains a query string. In general, therefore,
+you should prefer a cache busting strategy which modifies the path segment to
+a strategy which adds a query string.
+
+It is possible, however, that your static assets are being served by another
+web server or externally on a CDN. In these cases modifying the path segment
+for a static asset URL would cause the external service to fail to find the
+asset, causing your customer to get a 404. In these cases you would need to
+fall back to a cache buster which adds a query string. It is even possible
+that there isn't a copy of your static assets available to the :app:`Pyramid`
+application, so a cache busting implementation that generates md5 checksums
+would fail since it can't access the assets. In such a case,
+:class:`~pyramid.static.QueryStringConstantCacheBuster` is a reasonable
+fallback. The following code would set up a cachebuster that just uses the
+time at start up as a cachebust token:
+
+.. code-block:: python
+ :linenos:
+
+ import time
+ from pyramid.static import QueryStringConstantCacheBuster
+
+ config.add_static_view(
+ name='http://mycdn.example.com/',
+ path='mypackage:static',
+ cachebust=QueryStringConstantCacheBuster(str(time.time())))
.. index::
single: static assets view
@@ -526,3 +748,6 @@ files. Any software which uses the
:func:`pkg_resources.get_resource_string` APIs will obtain an overridden file
when an override is used.
+As of Pyramid 1.6, it is also possible to override an asset by supplying an
+absolute path to a file or directory. This may be useful if the assets are
+not distributed as part of a Python package.
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index 3cabbd8f4..4f16617c4 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -146,7 +146,7 @@ name ``main`` as a section name:
.. code-block:: text
- $ $VENV/bin starter/development.ini#main
+ $ $VENV/bin/pshell starter/development.ini#main
Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32)
[GCC 4.4.3] on linux2
Type "help" for more information.
diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst
index f7a69d613..f7fa94daf 100644
--- a/docs/narr/configuration.rst
+++ b/docs/narr/configuration.rst
@@ -17,6 +17,10 @@ plugging application code that you've written into :app:`Pyramid` is also
referred to within this documentation as "configuration"; you are configuring
:app:`Pyramid` to call the code that makes up your application.
+.. seealso::
+ For information on ``.ini`` files for Pyramid applications see the
+ :ref:`startup_chapter` chapter.
+
There are two ways to configure a :app:`Pyramid` application:
:term:`imperative configuration` and :term:`declarative configuration`. Both
are described below.
@@ -114,7 +118,6 @@ in a package and its subpackages. For example:
return Response('Hello')
if __name__ == '__main__':
- from pyramid.config import Configurator
config = Configurator()
config.scan()
app = config.make_wsgi_app()
diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst
index 412635f08..0b06fb80b 100644
--- a/docs/narr/environment.rst
+++ b/docs/narr/environment.rst
@@ -13,7 +13,6 @@
single: reload settings
single: default_locale_name
single: environment variables
- single: Mako environment settings
single: ini file settings
single: PasteDeploy settings
@@ -158,6 +157,28 @@ feature when this is true.
| | |
+---------------------------------+----------------------------------+
+Preventing Cache Busting
+------------------------
+
+Prevent the ``cachebust`` static view configuration argument from having any
+effect globally in this process when this value is true. No cache buster will
+be configured or used when this is true.
+
+.. versionadded:: 1.6
+
+.. seealso::
+
+ See also :ref:`cache_busting`.
+
++---------------------------------+----------------------------------+
+| Environment Variable Name | Config File Setting Name |
++=================================+==================================+
+| ``PYRAMID_PREVENT_CACHEBUST`` | ``pyramid.prevent_cachebust`` |
+| | or ``prevent_cachebust`` |
+| | |
+| | |
++---------------------------------+----------------------------------+
+
Debugging All
-------------
@@ -396,153 +417,6 @@ Is equivalent to using the following statements in your configuration code:
It is fine to use both or either form.
-.. _mako_template_renderer_settings:
-
-Mako Template Render Settings
------------------------------
-
-Mako derives additional settings to configure its template renderer that
-should be set when using it. Many of these settings are optional and only need
-to be set if they should be different from the default. The Mako Template
-Renderer uses a subclass of Mako's `template lookup
-<http://www.makotemplates.org/docs/usage.html#usage_lookup>`_ and accepts
-several arguments to configure it.
-
-Mako Directories
-~~~~~~~~~~~~~~~~
-
-The value(s) supplied here are passed in as the template directories. They
-should be in :term:`asset specification` format, for example:
-``my.package:templates``.
-
-+-----------------------------+
-| Config File Setting Name |
-+=============================+
-| ``mako.directories`` |
-| |
-| |
-| |
-+-----------------------------+
-
-Mako Module Directory
-~~~~~~~~~~~~~~~~~~~~~
-
-The value supplied here tells Mako where to store compiled Mako templates. If
-omitted, compiled templates will be stored in memory. This value should be an
-absolute path, for example: ``%(here)s/data/templates`` would use a directory
-called ``data/templates`` in the same parent directory as the INI file.
-
-+-----------------------------+
-| Config File Setting Name |
-+=============================+
-| ``mako.module_directory`` |
-| |
-| |
-| |
-+-----------------------------+
-
-Mako Input Encoding
-~~~~~~~~~~~~~~~~~~~
-
-The encoding that Mako templates are assumed to have. By default this is set
-to ``utf-8``. If you wish to use a different template encoding, this value
-should be changed accordingly.
-
-+-----------------------------+
-| Config File Setting Name |
-+=============================+
-| ``mako.input_encoding`` |
-| |
-| |
-| |
-+-----------------------------+
-
-Mako Error Handler
-~~~~~~~~~~~~~~~~~~
-
-A callable (or a :term:`dotted Python name` which names a callable) which is
-called whenever Mako compile or runtime exceptions occur. The callable is
-passed the current context as well as the exception. If the callable returns
-True, the exception is considered to be handled, else it is re-raised after
-the function completes. Is used to provide custom error-rendering functions.
-
-+-----------------------------+
-| Config File Setting Name |
-+=============================+
-| ``mako.error_handler`` |
-| |
-| |
-| |
-+-----------------------------+
-
-Mako Default Filters
-~~~~~~~~~~~~~~~~~~~~
-
-List of string filter names that will be applied to all Mako expressions.
-
-+-----------------------------+
-| Config File Setting Name |
-+=============================+
-| ``mako.default_filters`` |
-| |
-| |
-| |
-+-----------------------------+
-
-Mako Import
-~~~~~~~~~~~
-
-String list of Python statements, typically individual "import" lines, which
-will be placed into the module level preamble of all generated Python modules.
-
-
-+-----------------------------+
-| Config File Setting Name |
-+=============================+
-| ``mako.imports`` |
-| |
-| |
-| |
-+-----------------------------+
-
-
-Mako Strict Undefined
-~~~~~~~~~~~~~~~~~~~~~
-
-``true`` or ``false``, representing the "strict undefined" behavior of Mako
-(see `Mako Context Variables
-<http://www.makotemplates.org/docs/runtime.html#context-variables>`_). By
-default, this is ``false``.
-
-+-----------------------------+
-| Config File Setting Name |
-+=============================+
-| ``mako.strict_undefined`` |
-| |
-| |
-| |
-+-----------------------------+
-
-Mako Preprocessor
-~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 1.1
-
-A callable (or a :term:`dotted Python name` which names a callable) which is
-called to preprocess the source before the template is called. The callable
-will be passed the full template source before it is parsed. The return
-result of the callable will be used as the template source code.
-
-
-+-----------------------------+
-| Config File Setting Name |
-+=============================+
-| ``mako.preprocessor`` |
-| |
-| |
-| |
-+-----------------------------+
-
Examples
--------
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index f2542f1d7..e250c2d7e 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -354,6 +354,53 @@ We attach and cache an object named ``extra`` to the ``request`` object.
.. _beforerender_event:
+.. index::
+ single: response factory
+
+.. _changing_the_response_factory:
+
+Changing the Response Factory
+-------------------------------
+
+.. versionadded:: 1.6
+
+Whenever :app:`Pyramid` returns a response from a view it creates a
+:term:`response` object. By default, an instance of the
+:class:`pyramid.response.Response` class is created to represent the response
+object.
+
+The factory that :app:`Pyramid` uses to create a response object instance can be
+changed by passing a ``response_factory`` argument to the constructor of the
+:term:`configurator`. This argument can be either a callable or a
+:term:`dotted Python name` representing a callable.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+
+ class MyResponse(Response):
+ pass
+
+ config = Configurator(response_factory=lambda r: MyResponse())
+
+If you're doing imperative configuration, and you'd rather do it after you've
+already constructed a :term:`configurator` it can also be registered via the
+:meth:`pyramid.config.Configurator.set_response_factory` method:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+ from pyramid.response import Response
+
+ class MyResponse(Response):
+ pass
+
+ config = Configurator()
+ config.set_response_factory(lambda r: MyResponse())
+
+
Using The Before Render Event
-----------------------------
@@ -985,7 +1032,7 @@ Creating a Tween
To create a tween, you must write a "tween factory". A tween factory
must be a globally importable callable which accepts two arguments:
-``handler`` and ``registry``. ``handler`` will be the either the main
+``handler`` and ``registry``. ``handler`` will be either the main
Pyramid request handling function or another tween. ``registry`` will be the
Pyramid :term:`application registry` represented by this Configurator. A
tween factory must return the tween (a callable object) when it is called.
@@ -1023,7 +1070,7 @@ method:
:linenos:
class simple_tween_factory(object):
- def __init__(handler, registry):
+ def __init__(self, handler, registry):
self.handler = handler
self.registry = registry
@@ -1040,6 +1087,10 @@ method:
return response
+You should avoid mutating any state on the tween instance. The tween is
+invoked once per request and any shared mutable state needs to be carefully
+handled to avoid any race conditions.
+
The closure style performs slightly better and enables you to conditionally
omit the tween from the request processing pipeline (see the following timing
tween example), whereas the class style makes it easier to have shared mutable
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index 1de2c8941..3c804a158 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -326,7 +326,7 @@ application. You run a ``pot-create`` command to extract the messages:
$ cd /place/where/myapplication/setup.py/lives
$ mkdir -p myapplication/locale
- $ $VENV/bin/pot-create src > myapplication/locale/myapplication.pot
+ $ $VENV/bin/pot-create -o myapplication/locale/myapplication.pot src
The message catalog ``.pot`` template will end up in:
@@ -352,9 +352,9 @@ command from Gettext:
$ cd /place/where/myapplication/setup.py/lives
$ cd myapplication/locale
$ mkdir -p es/LC_MESSAGES
- $ msginit -l es es/LC_MESSAGES/myapplication.po
+ $ msginit -l es -o es/LC_MESSAGES/myapplication.po
-This will create a new the message catalog ``.po`` file will in:
+This will create a new message catalog ``.po`` file in:
``myapplication/locale/es/LC_MESSAGES/myapplication.po``.
@@ -402,11 +402,11 @@ command from Gettext:
.. code-block:: text
$ cd /place/where/myapplication/setup.py/lives
- $ msgfmt myapplication/locale/*/LC_MESSAGES/*.po
+ $ msgfmt -o myapplication/locale/es/LC_MESSAGES/myapplication.mo myapplication/locale/es/LC_MESSAGES/myapplication.po
This will create a ``.mo`` file for each ``.po`` file in your
application. As long as the :term:`translation directory` in which
-the ``.mo`` file ends up in is configured into your application, these
+the ``.mo`` file ends up in is configured into your application (see :ref:`adding_a_translation_directory`), these
translations will be available to :app:`Pyramid`.
.. index::
@@ -792,9 +792,11 @@ Then as a part of the code of a custom :term:`locale negotiator`:
.. code-block:: python
:linenos:
- from pyramid.threadlocal import get_current_registry
- settings = get_current_registry().settings
- languages = settings['available_languages'].split()
+ from pyramid.settings import aslist
+
+ def my_locale_negotiator(request):
+ languages = aslist(request.registry.settings['available_languages'])
+ # ...
This is only a suggestion. You can create your own "available
languages" configuration scheme as necessary.
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index e419a8b20..a825b61b9 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -15,8 +15,8 @@ You will need `Python <http://python.org>`_ version 2.6 or better to run
.. sidebar:: Python Versions
As of this writing, :app:`Pyramid` has been tested under Python 2.6, Python
- 2.7, Python 3.2, and Python 3.3. :app:`Pyramid` does not run under any
- version of Python before 2.6.
+ 2.7, Python 3.2, Python 3.3, Python 3.4 and PyPy 2.2. :app:`Pyramid` does
+ not run under any version of Python before 2.6.
:app:`Pyramid` is known to run on all popular UNIX-like systems such as Linux,
Mac OS X, and FreeBSD as well as on Windows platforms. It is also known to run
@@ -32,20 +32,22 @@ dependency will fall back to using pure Python instead.
For Mac OS X Users
~~~~~~~~~~~~~~~~~~
-From `Python.org <http://python.org/download/mac/>`_:
+Python comes pre-installed on Mac OS X, but due to Apple's release cycle,
+it is often out of date. Unless you have a need for a specific earlier version,
+it is recommended to install the latest 2.x or 3.x version of Python.
- Python comes pre-installed on Mac OS X, but due to Apple's release cycle,
- it's often one or even two years old. The overwhelming recommendation of
- the "MacPython" community is to upgrade your Python by downloading and
- installing a newer version from `the Python standard release page
- <http://python.org/download/releases/>`_.
+You can install the latest verion of Python for Mac OS X from the binaries on
+`python.org <https://www.python.org/download/mac/>`_.
-It is recommended to download one of the *installer* versions, unless you
-prefer to install your Python through a packgage manager (e.g., macports or
-homebrew) or to build your Python from source.
+Alternatively, you can use the `homebrew <http://brew.sh/>`_ package manager.
-Unless you have a need for a specific earlier version, it is recommended to
-install the latest 2.x or 3.x version of Python.
+.. code-block:: text
+
+ # for python 2.7
+ $ brew install python
+
+ # for python 3.4
+ $ brew install python3
If you use an installer for your Python, then you can skip to the section
:ref:`installing_unix`.
diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst
index 75428d513..c16673ae6 100644
--- a/docs/narr/logging.rst
+++ b/docs/narr/logging.rst
@@ -16,6 +16,11 @@ how to send log messages to loggers that you've configured.
a third-party scaffold which does not create these files, the
configuration information in this chapter may not be applicable.
+.. index:
+ pair: settings; logging
+ pair: .ini; logging
+ pair: logging; configuration
+
.. _logging_config:
Logging Configuration
@@ -242,7 +247,7 @@ level is set to ``INFO``, whereas the application's log level is set to
[logger_myapp]
level = DEBUG
handlers =
- qualname = helloworld
+ qualname = myapp
All of the child loggers of the ``myapp`` logger will inherit the ``DEBUG``
level unless they're explicitly set differently. Meaning the ``myapp.views``,
@@ -294,15 +299,26 @@ use the :term:`pyramid_exclog` package. Details about its configuration are
in its `documentation
<http://docs.pylonsproject.org/projects/pyramid_exclog/dev/>`_.
+.. index::
+ single: TransLogger
+ single: middleware; TransLogger
+ pair: configuration; middleware
+ single: settings; middleware
+ pair: .ini; middleware
+
+.. _request_logging_with_pastes_translogger:
+
Request Logging with Paste's TransLogger
----------------------------------------
-Paste provides the `TransLogger
-<http://pythonpaste.org/modules/translogger.html>`_ :term:`middleware` for
-logging requests using the `Apache Combined Log Format
-<http://httpd.apache.org/docs/2.2/logs.html#combined>`_. TransLogger combined
-with a FileHandler can be used to create an ``access.log`` file similar to
-Apache's.
+The term:`WSGI` design is modular. Waitress logs error conditions, debugging
+output, etc., but not web traffic. For web traffic logging Paste provides the
+`TransLogger <http://pythonpaste.org/modules/translogger.html>`_
+:term:`middleware`. TransLogger produces logs in the `Apache Combined Log
+Format <http://httpd.apache.org/docs/2.2/logs.html#combined>`_. But
+TransLogger does not write to files, the Python logging system must be
+configured to do this. The Python FileHandler_ logging handler can be used
+alongside TransLogger to create an ``access.log`` file similar to Apache's.
Like any standard :term:`middleware` with a Paste entry point, TransLogger can
be configured to wrap your application using ``.ini`` file syntax. First,
@@ -343,10 +359,12 @@ function of your project's ``__init__`` file:
app = TransLogger(app, setup_console_handler=False)
return app
-TransLogger will automatically setup a logging handler to the console when
-called with no arguments, so it 'just works' in environments that don't
-configure logging. Since we've configured our own logging handlers, we need
-to disable that option via ``setup_console_handler = False``.
+
+.. note::
+ TransLogger will automatically setup a logging handler to the console when
+ called with no arguments, so it 'just works' in environments that don't
+ configure logging. Since our logging handlers are configured we disable
+ the automation via ``setup_console_handler = False``.
With the filter in place, TransLogger's logger (named the ``wsgi`` logger) will
propagate its log messages to the parent logger (the root logger), sending
@@ -361,9 +379,9 @@ its output to the console when we request a page:
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.6) Gecko/20070725
Firefox/2.0.0.6"
-To direct TransLogger to an ``access.log`` FileHandler, we need to add that
-FileHandler to the list of handlers (named ``accesslog``), and ensure that the
-``wsgi`` logger is configured and uses this handler accordingly:
+To direct TransLogger to an ``access.log`` FileHandler, we need the following
+to add a FileHandler (named ``accesslog``) to the list of handlers, and ensure
+that the ``wsgi`` logger is configured and uses this handler accordingly:
.. code-block:: ini
@@ -377,7 +395,7 @@ FileHandler to the list of handlers (named ``accesslog``), and ensure that the
[logger_wsgi]
level = INFO
- handlers = handler_accesslog
+ handlers = accesslog
qualname = wsgi
propagate = 0
@@ -395,7 +413,7 @@ directs its records only to the ``accesslog`` handler.
Finally, there's no need to use the ``generic`` formatter with TransLogger as
TransLogger itself provides all the information we need. We'll use a
formatter that passes-through the log messages as is. Add a new formatter
-called ``accesslog`` by include the following in your configuration file:
+called ``accesslog`` by including the following in your configuration file:
.. code-block:: ini
@@ -405,7 +423,9 @@ called ``accesslog`` by include the following in your configuration file:
[formatter_accesslog]
format = %(message)s
-Then wire this new ``accesslog`` formatter into the FileHandler:
+
+Finally alter the existing configuration to wire this new
+``accesslog`` formatter into the FileHandler:
.. code-block:: ini
diff --git a/docs/narr/router.rst b/docs/narr/router.rst
index ac3deefdc..6f90c70cc 100644
--- a/docs/narr/router.rst
+++ b/docs/narr/router.rst
@@ -9,6 +9,9 @@
Request Processing
==================
+.. image:: ../_static/pyramid_request_processing.*
+ :alt: Request Processing
+
Once a :app:`Pyramid` application is up and running, it is ready to accept
requests and return responses. What happens from the time a :term:`WSGI`
request enters a :app:`Pyramid` application through to the point that
@@ -116,7 +119,8 @@ request enters a :app:`Pyramid` application through to the point that
#. The :term:`thread local` stack is popped.
-.. image:: router.png
+.. image:: ../_static/pyramid_router.*
+ :alt: Pyramid Router
This is a very high-level overview that leaves out various details. For more
detail about subsystems invoked by the :app:`Pyramid` router such as
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index 8db23a33b..2dc0c76af 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -6,13 +6,28 @@
Security
========
-:app:`Pyramid` provides an optional declarative authorization system
-that can prevent a :term:`view` from being invoked based on an
+:app:`Pyramid` provides an optional, declarative, security system.
+Security in :app:`Pyramid` is separated into authentication and
+authorization. The two systems communicate via :term:`principal`
+identifiers. Authentication is merely the mechanism by which credentials
+provided in the :term:`request` are resolved to one or more
+:term:`principal` identifiers. These identifiers represent the users and
+groups that are in effect during the request. Authorization then determines
+access based on the :term:`principal` identifiers, the requested
+:term:`permission`, and a :term:`context`.
+
+The :app:`Pyramid` authorization system
+can prevent a :term:`view` from being invoked based on an
:term:`authorization policy`. Before a view is invoked, the
authorization system can use the credentials in the :term:`request`
along with the :term:`context` resource to determine if access will be
allowed. Here's how it works at a high level:
+- A user may or may not have previously visited the application and
+ supplied authentication credentials, including a :term:`userid`. If
+ so, the application may have called
+ :func:`pyramid.security.remember` to remember these.
+
- A :term:`request` is generated when a user visits the application.
- Based on the request, a :term:`context` resource is located through
@@ -25,7 +40,9 @@ allowed. Here's how it works at a high level:
context as well as other attributes of the request.
- If an :term:`authentication policy` is in effect, it is passed the
- request; it returns some number of :term:`principal` identifiers.
+ request. It will return some number of :term:`principal` identifiers.
+ To do this, the policy would need to determine the authenticated
+ :term:`userid` present in the request.
- If an :term:`authorization policy` is in effect and the :term:`view
configuration` associated with the view callable that was found has
@@ -41,15 +58,6 @@ allowed. Here's how it works at a high level:
- If the authorization policy denies access, the view callable is not
invoked; instead the :term:`forbidden view` is invoked.
-Security in :app:`Pyramid`, unlike many systems, cleanly and explicitly
-separates authentication and authorization. Authentication is merely the
-mechanism by which credentials provided in the :term:`request` are
-resolved to one or more :term:`principal` identifiers. These identifiers
-represent the users and groups in effect during the request.
-Authorization then determines access based on the :term:`principal`
-identifiers, the :term:`view callable` being invoked, and the
-:term:`context` resource.
-
Authorization is enabled by modifying your application to include an
:term:`authentication policy` and :term:`authorization policy`.
:app:`Pyramid` comes with a variety of implementations of these
@@ -104,7 +112,8 @@ For example:
The above configuration enables a policy which compares the value of an "auth
ticket" cookie passed in the request's environment which contains a reference
-to a single :term:`principal` against the principals present in any
+to a single :term:`userid` and matches that userid's
+:term:`principals <principal>` against the principals present in any
:term:`ACL` found in the resource tree when attempting to call some
:term:`view`.
@@ -595,36 +604,53 @@ that implements the following interface:
""" An object representing a Pyramid authentication policy. """
def authenticated_userid(self, request):
- """ Return the authenticated userid or ``None`` if no
- authenticated userid can be found. This method of the policy
- should ensure that a record exists in whatever persistent store is
- used related to the user (the user should not have been deleted);
- if a record associated with the current id does not exist in a
- persistent store, it should return ``None``."""
+ """ Return the authenticated :term:`userid` or ``None`` if
+ no authenticated userid can be found. This method of the
+ policy should ensure that a record exists in whatever
+ persistent store is used related to the user (the user
+ should not have been deleted); if a record associated with
+ the current id does not exist in a persistent store, it
+ should return ``None``.
+
+ """
def unauthenticated_userid(self, request):
- """ Return the *unauthenticated* userid. This method performs the
- same duty as ``authenticated_userid`` but is permitted to return the
- userid based only on data present in the request; it needn't (and
- shouldn't) check any persistent store to ensure that the user record
- related to the request userid exists."""
+ """ Return the *unauthenticated* userid. This method
+ performs the same duty as ``authenticated_userid`` but is
+ permitted to return the userid based only on data present
+ in the request; it needn't (and shouldn't) check any
+ persistent store to ensure that the user record related to
+ the request userid exists.
+
+ This method is intended primarily a helper to assist the
+ ``authenticated_userid`` method in pulling credentials out
+ of the request data, abstracting away the specific headers,
+ query strings, etc that are used to authenticate the request.
+
+ """
def effective_principals(self, request):
""" Return a sequence representing the effective principals
- including the userid and any groups belonged to by the current
- user, including 'system' groups such as
- ``pyramid.security.Everyone`` and
- ``pyramid.security.Authenticated``. """
+ typically including the :term:`userid` and any groups belonged
+ to by the current user, always including 'system' groups such
+ as ``pyramid.security.Everyone`` and
+ ``pyramid.security.Authenticated``.
- def remember(self, request, principal, **kw):
+ """
+
+ def remember(self, request, userid, **kw):
""" Return a set of headers suitable for 'remembering' the
- principal named ``principal`` when set in a response. An
- individual authentication policy and its consumers can decide
- on the composition and meaning of **kw. """
-
+ :term:`userid` named ``userid`` when set in a response. An
+ individual authentication policy and its consumers can
+ decide on the composition and meaning of **kw.
+
+ """
+
def forget(self, request):
""" Return a set of headers suitable for 'forgetting' the
- current user on subsequent requests. """
+ current user on subsequent requests.
+
+ """
After you do so, you can pass an instance of such a class into the
:class:`~pyramid.config.Configurator.set_authentication_policy` method
diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst
index 7b4a7ea08..a1a23ed52 100644
--- a/docs/narr/startup.rst
+++ b/docs/narr/startup.rst
@@ -19,6 +19,7 @@ console.
.. index::
single: startup process
+ pair: settings; .ini
The Startup Process
-------------------
@@ -139,6 +140,13 @@ Here's a high-level time-ordered overview of what happens when you press
The server serves the application, and the application is running, waiting
to receive requests.
+.. seealso::
+ Logging configuration is described in the :ref:`logging_chapter`
+ chapter. There, in :ref:`request_logging_with_pastes_translogger`,
+ you will also find an example of how to configure
+ :term:`middleware` to add pre-packaged functionality to your
+ application.
+
.. index::
pair: settings; deployment
single: custom settings
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index 038dd2594..4c1364493 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -316,8 +316,7 @@ template renderer:
we're using a Chameleon renderer, it means "relative to the directory in
which the file which defines the view configuration lives". In this case,
this is the directory containing the file that defines the ``my_view``
- function. View-configuration-relative asset specifications work only
- in Chameleon, not in Mako templates.
+ function.
Similar renderer configuration can be done imperatively. See
:ref:`views_which_use_a_renderer`.
@@ -450,21 +449,24 @@ Available Add-On Template System Bindings
The Pylons Project maintains several packages providing bindings to different
templating languages including the following:
-+------------------------------+------------------------------+
-| Template Language | Pyramid Bindings |
-+==============================+==============================+
-| Chameleon_ | pyramid_chameleon_ |
-+------------------------------+------------------------------+
-| Jinja2_ | pyramid_jinja2_ |
-+------------------------------+------------------------------+
-| Mako_ | pyramid_mako_ |
-+------------------------------+------------------------------+
++---------------------------+----------------------------+--------------------+
+| Template Language | Pyramid Bindings | Default Extensions |
++===========================+============================+====================+
+| Chameleon_ | pyramid_chameleon_ | .pt, .txt |
++---------------------------+----------------------------+--------------------+
+| Jinja2_ | pyramid_jinja2_ | .jinja2 |
++---------------------------+----------------------------+--------------------+
+| Mako_ | pyramid_mako_ | .mak, .mako |
++---------------------------+----------------------------+--------------------+
.. _Chameleon: http://chameleon.readthedocs.org/en/latest/
-.. _pyramid_chameleon: https://pypi.python.org/pypi/pyramid_chameleon
+.. _pyramid_chameleon:
+ http://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
.. _Jinja2: http://jinja.pocoo.org/docs/
-.. _pyramid_jinja2: https://pypi.python.org/pypi/pyramid_jinja2
+.. _pyramid_jinja2:
+ http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/
.. _Mako: http://www.makotemplates.org/
-.. _pyramid_mako: https://pypi.python.org/pypi/pyramid_mako
+.. _pyramid_mako:
+ http://docs.pylonsproject.org/projects/pyramid-mako/en/latest/
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index e001ad81c..ecda57489 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -128,8 +128,9 @@ functions accepts various arguments that influence the environment of the
test. See the :ref:`testing_module` API for information about the extra
arguments supported by these functions.
-If you also want to make :func:`~pyramid.threadlocal.get_current_request` return something
-other than ``None`` during the course of a single test, you can pass a
+If you also want to make :func:`~pyramid.threadlocal.get_current_request`
+return something other than ``None`` during the course of a single test, you
+can pass a
:term:`request` object into the :func:`pyramid.testing.setUp` within the
``setUp`` method of your test:
@@ -333,66 +334,49 @@ Creating Integration Tests
--------------------------
In :app:`Pyramid`, a *unit test* typically relies on "mock" or "dummy"
-implementations to give the code under test only enough context to run.
+implementations to give the code under test enough context to run.
"Integration testing" implies another sort of testing. In the context of a
-:app:`Pyramid` integration test, the test logic tests the functionality of
-some code *and* its integration with the rest of the :app:`Pyramid`
+:app:`Pyramid` integration test, the test logic exercises the functionality of
+the code under test *and* its integration with the rest of the :app:`Pyramid`
framework.
-In :app:`Pyramid` applications that are plugins to Pyramid, you can create an
-integration test by including its ``includeme`` function via
-:meth:`pyramid.config.Configurator.include` in the test's setup code. This
-causes the entire :app:`Pyramid` environment to be set up and torn down as if
-your application was running "for real". This is a heavy-hammer way of
-making sure that your tests have enough context to run properly, and it tests
-your code's integration with the rest of :app:`Pyramid`.
+Creating an integration test for a :app:`Pyramid` application usually means
+invoking the application's ``includeme`` function via
+:meth:`pyramid.config.Configurator.include` within the test's setup code. This
+causes the entire :app:`Pyramid` environment to be set up, simulating what
+happens when your application is run "for real". This is a heavy-hammer way of
+making sure that your tests have enough context to run properly, and tests your
+code's integration with the rest of :app:`Pyramid`.
-Let's demonstrate this by showing an integration test for a view. The below
-test assumes that your application's package name is ``myapp``, and that
-there is a ``views`` module in the app with a function with the name
-``my_view`` in it that returns the response 'Welcome to this application'
-after accessing some values that require a fully set up environment.
+.. seealso::
-.. code-block:: python
- :linenos:
+ See also :ref:`including_configuration`
- import unittest
+Let's demonstrate this by showing an integration test for a view.
- from pyramid import testing
+Given the following view definition, which assumes that your application's
+:term:`package` name is ``myproject``, and within that :term:`package` there
+exists a module ``views``, which in turn contains a :term:`view` function named
+``my_view``:
- class ViewIntegrationTests(unittest.TestCase):
- def setUp(self):
- """ This sets up the application registry with the
- registrations your application declares in its ``includeme``
- function.
- """
- import myapp
- self.config = testing.setUp()
- self.config.include('myapp')
+ .. literalinclude:: MyProject/myproject/views.py
+ :linenos:
+ :lines: 1-6
+ :language: python
- def tearDown(self):
- """ Clear out the application registry """
- testing.tearDown()
+You'd then create a ``tests`` module within your ``myproject`` package,
+containing the following test code:
- def test_my_view(self):
- from myapp.views import my_view
- request = testing.DummyRequest()
- result = my_view(request)
- self.assertEqual(result.status, '200 OK')
- body = result.app_iter[0]
- self.assertTrue('Welcome to' in body)
- self.assertEqual(len(result.headerlist), 2)
- self.assertEqual(result.headerlist[0],
- ('Content-Type', 'text/html; charset=UTF-8'))
- self.assertEqual(result.headerlist[1], ('Content-Length',
- str(len(body))))
-
-Unless you cannot avoid it, you should prefer writing unit tests that use the
-:class:`~pyramid.config.Configurator` API to set up the right "mock"
-registrations rather than creating an integration test. Unit tests will run
-faster (because they do less for each test) and the result of a unit test is
-usually easier to make assertions about.
+ .. literalinclude:: MyProject/myproject/tests.py
+ :linenos:
+ :pyobject: ViewIntegrationTests
+ :language: python
+
+Writing unit tests that use the :class:`~pyramid.config.Configurator` API to
+set up the right "mock" registrations is often preferred to creating
+integration tests. Unit tests will run faster (because they do less for each
+test) and are usually easier to reason about.
.. index::
single: functional tests
@@ -404,34 +388,40 @@ Creating Functional Tests
Functional tests test your literal application.
-The below test assumes that your application's package name is ``myapp``, and
-that there is a view that returns an HTML body when the root URL is invoked.
-It further assumes that you've added a ``tests_require`` dependency on the
-``WebTest`` package within your ``setup.py`` file. :term:`WebTest` is a
-functional testing package written by Ian Bicking.
+In Pyramid, functional tests are typically written using the :term:`WebTest`
+package, which provides APIs for invoking HTTP(S) requests to your application.
-.. code-block:: python
- :linenos:
+Regardless of which testing :term:`package` you use, ensure to add a
+``tests_require`` dependency on that package to to your application's
+``setup.py`` file:
- import unittest
+ .. literalinclude:: MyProject/setup.py
+ :linenos:
+ :emphasize-lines: 26-28,48
+ :language: python
- class FunctionalTests(unittest.TestCase):
- def setUp(self):
- from myapp import main
- app = main({})
- from webtest import TestApp
- self.testapp = TestApp(app)
-
- def test_root(self):
- res = self.testapp.get('/', status=200)
- self.assertTrue('Pyramid' in res.body)
-
-When this test is run, each test creates a "real" WSGI application using the
-``main`` function in your ``myapp.__init__`` module and uses :term:`WebTest`
-to wrap that WSGI application. It assigns the result to ``self.testapp``.
-In the test named ``test_root``, we use the testapp's ``get`` method to
-invoke the root URL. We then assert that the returned HTML has the string
-``Pyramid`` in it.
+Assuming your :term:`package` is named ``myproject``, which contains a
+``views`` module, which in turn contains a :term:`view` function ``my_view``
+that returns a HTML body when the root URL is invoked:
+
+ .. literalinclude:: MyProject/myproject/views.py
+ :linenos:
+ :language: python
+
+Then the following example functional test (shown below) demonstrates invoking
+the :term:`view` shown above:
+
+ .. literalinclude:: MyProject/myproject/tests.py
+ :linenos:
+ :pyobject: FunctionalTests
+ :language: python
+
+When this test is run, each test method creates a "real" :term:`WSGI`
+application using the ``main`` function in your ``myproject.__init__`` module,
+using :term:`WebTest` to wrap that WSGI application. It assigns the result to
+``self.testapp``. In the test named ``test_root``. The ``TestApp``'s ``get``
+method is used to invoke the root URL. Finally, an assertion is made that the
+returned HTML contains the text ``MyProject``.
See the :term:`WebTest` documentation for further information about the
methods available to a :class:`webtest.app.TestApp` instance.
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index adc53bd11..a0feef8d7 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -295,11 +295,14 @@ configured view.
*This is an advanced feature, not often used by "civilians"*.
``request_method``
- This value can be a string (typically ``"GET"``, ``"POST"``, ``"PUT"``,
- ``"DELETE"``, or ``"HEAD"``) representing an HTTP ``REQUEST_METHOD``. A view
- declaration with this argument ensures that the view will only be called
- when the request's ``method`` attribute (aka the ``REQUEST_METHOD`` of the
- WSGI environment) string matches the supplied value.
+ This value can be either a string (such as ``"GET"``, ``"POST"``,
+ ``"PUT"``, ``"DELETE"``, ``"HEAD"`` or ``"OPTIONS"``) representing an
+ HTTP ``REQUEST_METHOD``, or a tuple containing one or more of these
+ strings. A view declaration with this argument ensures that the
+ view will only be called when the ``method`` attribute of the
+ request (aka the ``REQUEST_METHOD`` of the WSGI environment) matches
+ a supplied value. Note that use of ``"GET"`` also implies that the
+ view will respond to ``"HEAD"`` as of Pyramid 1.4.
If ``request_method`` is not supplied, the view will be invoked regardless
of the ``REQUEST_METHOD`` of the :term:`WSGI` environment.
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index f0a4b5a0b..0eb070b06 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -310,6 +310,14 @@ Python's ``urllib2`` instead of a Javascript AJAX request:
req = urllib2.Request('http://localhost:6543/', json_payload, headers)
resp = urllib2.urlopen(req)
+If you are doing Cross-origin resource sharing (CORS), then the standard
+requires the browser to do a pre-flight HTTP OPTIONS request. The easiest way
+to handling this is adding an extra ``view_config`` for the same route, with
+``request_method`` set to ``OPTIONS``, and setting the desired response header
+before returning. You can find examples of response headers here_.
+
+.. _here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests
+
.. index::
single: cleaning up after request
@@ -408,6 +416,8 @@ Here are some highlights:
The content type *not* including the ``charset`` parameter.
Typical use: ``response.content_type = 'text/html'``.
+ Default value: ``response.content_type = 'text/html'``.
+
``response.charset``:
The ``charset`` parameter of the content-type, it also informs
encoding in ``response.unicode_body``.
@@ -466,9 +476,12 @@ argument to the class; e.g.:
from pyramid.response import Response
response = Response(body='hello world!', content_type='text/plain')
-The status defaults to ``'200 OK'``. The content_type does not default to
-anything, though if you subclass :class:`pyramid.response.Response` and set
-``default_content_type`` you can override this behavior.
+The status defaults to ``'200 OK'``.
+
+The value of content_type defaults to
+``webob.response.Response.default_content_type``; which is `text/html`.
+You can subclass :class:`pyramid.response.Response` and set
+``default_content_type`` to override this behavior.
.. index::
single: exception responses
diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst
index 4ab39bb11..41a0dc8c0 100644
--- a/docs/quick_tour.rst
+++ b/docs/quick_tour.rst
@@ -700,7 +700,7 @@ we might need to detect situations when other people use the site. We
need *logging*.
Fortunately Pyramid uses the normal Python approach to logging. The
-scaffold generated in your ``development.ini`` a number of lines that
+scaffold generated in your ``development.ini`` has a number of lines that
configure the logging for you to some reasonable defaults. You then see
messages sent by Pyramid (for example, when a new request comes in).
diff --git a/docs/quick_tour/views/views.py b/docs/quick_tour/views/views.py
index 9dc795f14..1449cbb38 100644
--- a/docs/quick_tour/views/views.py
+++ b/docs/quick_tour/views/views.py
@@ -1,3 +1,5 @@
+import cgi
+
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response
from pyramid.view import view_config
@@ -14,7 +16,8 @@ def home_view(request):
def hello_view(request):
name = request.params.get('name', 'No Name')
body = '<p>Hi %s, this <a href="/goto">redirects</a></p>'
- return Response(body % name)
+ # cgi.escape to prevent Cross-Site Scripting (XSS) [CWE 79]
+ return Response(body % cgi.escape(name))
# /goto which issues HTTP redirect to the last view
@@ -23,7 +26,7 @@ def redirect_view(request):
return HTTPFound(location="/problem")
-# /problem which causes an site error
+# /problem which causes a site error
@view_config(route_name='exception')
def exception_view(request):
raise Exception()
diff --git a/docs/quick_tutorial/authentication/development.ini b/docs/quick_tutorial/authentication/development.ini
index 5d4580ff5..8a39b2fe7 100644
--- a/docs/quick_tutorial/authentication/development.ini
+++ b/docs/quick_tutorial/authentication/development.ini
@@ -9,34 +9,3 @@ tutorial.secret = 98zd
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/authorization/development.ini b/docs/quick_tutorial/authorization/development.ini
index 5d4580ff5..8a39b2fe7 100644
--- a/docs/quick_tutorial/authorization/development.ini
+++ b/docs/quick_tutorial/authorization/development.ini
@@ -9,34 +9,3 @@ tutorial.secret = 98zd
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/databases.rst b/docs/quick_tutorial/databases.rst
index 20b3cd46d..7c019dbfc 100644
--- a/docs/quick_tutorial/databases.rst
+++ b/docs/quick_tutorial/databases.rst
@@ -115,7 +115,7 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests .
+ $ $VENV/bin/nosetests tutorial
..
-----------------------------------------------------------------
Ran 2 tests in 1.141s
diff --git a/docs/quick_tutorial/databases/development.ini b/docs/quick_tutorial/databases/development.ini
index 270da960f..04c249a62 100644
--- a/docs/quick_tutorial/databases/development.ini
+++ b/docs/quick_tutorial/databases/development.ini
@@ -11,39 +11,3 @@ sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial, sqlalchemy
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[logger_sqlalchemy]
-level = INFO
-handlers =
-qualname = sqlalchemy.engine
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst
index 1c540d8a2..d138eb760 100644
--- a/docs/quick_tutorial/debugtoolbar.rst
+++ b/docs/quick_tutorial/debugtoolbar.rst
@@ -58,32 +58,31 @@ Steps
Analysis
========
-``pyramid_debugtoolbar`` is a full-fledged Python package,
-available on PyPI just like thousands of other Python packages. Thus we
-start by installing the ``pyramid_debugtoolbar`` package into our
-virtual environment using normal Python package installation commands.
-
-The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on,
-which means we need to include its add-on configuration into our web
-application. We could do this with imperative configuration in
-``tutorial/__init__.py`` by using ``config.include``. Pyramid also
-supports wiring in add-on configuration via our ``development.ini``
-using ``pyramid.includes``. We use this to load the configuration for
-the debugtoolbar.
-
-You'll now see an attractive (and collapsible) menu in the right of
-your browser, providing introspective access to debugging information.
-Even better, if your web application generates an error,
-you will see a nice traceback on the screen. When you want to disable
-this toolbar, no need to change code: you can remove it from
-``pyramid.includes`` in the relevant ``.ini`` configuration file (thus
-showing why configuration files are handy.)
-
-Note that the toolbar mutates the HTML generated by our app and uses jQuery to
-overlay itself. If you are using the toolbar while you're developing and you
-start to experience otherwise inexplicable client-side weirdness, you can shut
-it off by commenting out the ``pyramid_debugtoolbar`` line in
-``pyramid.includes`` temporarily.
+``pyramid_debugtoolbar`` is a full-fledged Python package, available on PyPI
+just like thousands of other Python packages. Thus we start by installing the
+``pyramid_debugtoolbar`` package into our virtual environment using normal
+Python package installation commands.
+
+The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on, which
+means we need to include its add-on configuration into our web application. We
+could do this with imperative configuration in ``tutorial/__init__.py`` by
+using ``config.include``. Pyramid also supports wiring in add-on configuration
+via our ``development.ini`` using ``pyramid.includes``. We use this to load
+the configuration for the debugtoolbar.
+
+You'll now see an attractive button on the right side of your browser, which
+you may click to provide introspective access to debugging information in a
+new browser tab. Even better, if your web application generates an error, you
+will see a nice traceback on the screen. When you want to disable this
+toolbar, no need to change code: you can remove it from ``pyramid.includes``
+in the relevant ``.ini`` configuration file (thus showing why configuration
+files are handy.)
+
+Note that the toolbar injects a small amount of html/css into your app just
+before the closing ``</body>`` tag in order to display itself. If you start to
+experience otherwise inexplicable client-side weirdness, you can shut it off
+by commenting out the ``pyramid_debugtoolbar`` line in ``pyramid.includes``
+temporarily.
.. seealso:: See also :ref:`pyramid_debugtoolbar <toolbar:overview>`.
diff --git a/docs/quick_tutorial/debugtoolbar/development.ini b/docs/quick_tutorial/debugtoolbar/development.ini
index 470d92c57..52b2a3a41 100644
--- a/docs/quick_tutorial/debugtoolbar/development.ini
+++ b/docs/quick_tutorial/debugtoolbar/development.ini
@@ -7,34 +7,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/forms.rst b/docs/quick_tutorial/forms.rst
index e8bc0c8b4..b08167edc 100644
--- a/docs/quick_tutorial/forms.rst
+++ b/docs/quick_tutorial/forms.rst
@@ -104,7 +104,7 @@ assets which need to be published. We don't have to know where on disk
it is located. We point at the package, then the path inside the package.
We just need to include a call to ``add_static_view`` to make that
-directory available at a URL. For Pyramid-specific pages,
+directory available at a URL. For Pyramid-specific packages,
Pyramid provides a facility (``config.include()``) which even makes
that unnecessary for consumers of a package. (Deform is not specific to
Pyramid.)
diff --git a/docs/quick_tutorial/forms/development.ini b/docs/quick_tutorial/forms/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/forms/development.ini
+++ b/docs/quick_tutorial/forms/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/functional_testing.rst b/docs/quick_tutorial/functional_testing.rst
index 205ddf5cb..09b05b0bc 100644
--- a/docs/quick_tutorial/functional_testing.rst
+++ b/docs/quick_tutorial/functional_testing.rst
@@ -37,12 +37,15 @@ Steps
$ $VENV/bin/python setup.py develop
$ $VENV/bin/easy_install webtest
-#. Let's extend ``unit_testing/tutorial/tests.py`` to include a
+#. Let's extend ``functional_testing/tutorial/tests.py`` to include a
functional test:
.. literalinclude:: functional_testing/tutorial/tests.py
:linenos:
+ Be sure this file is not executable, or ``nosetests`` may not
+ include your tests.
+
#. Now run the tests:
.. code-block:: bash
@@ -67,4 +70,4 @@ execution time of our tests.
Extra Credit
============
-#. Why do our functional tests use ``b''``? \ No newline at end of file
+#. Why do our functional tests use ``b''``?
diff --git a/docs/quick_tutorial/functional_testing/development.ini b/docs/quick_tutorial/functional_testing/development.ini
index 470d92c57..52b2a3a41 100644
--- a/docs/quick_tutorial/functional_testing/development.ini
+++ b/docs/quick_tutorial/functional_testing/development.ini
@@ -7,34 +7,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst
index 1a9ba4c9d..4ae80ca87 100644
--- a/docs/quick_tutorial/hello_world.rst
+++ b/docs/quick_tutorial/hello_world.rst
@@ -77,7 +77,7 @@ explanation:
#. *Lines 12-14*. Use Pyramid's :term:`configurator` to connect
:term:`view` code to a particular URL :term:`route`.
-#. *Lines 6-7*. Implement the view code that generates the
+#. *Lines 6-8*. Implement the view code that generates the
:term:`response`.
#. *Lines 15-17*. Publish a :term:`WSGI` app using an HTTP
diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst
index 3402c50e8..b8720711b 100644
--- a/docs/quick_tutorial/ini.rst
+++ b/docs/quick_tutorial/ini.rst
@@ -14,9 +14,9 @@ Pyramid has a first-class concept of
:ref:`configuration <configuration_narr>` distinct from code.
This approach is optional, but its presence makes it distinct from
other Python web frameworks. It taps into Python's ``setuptools``
-library, which establishes conventions for how Python projects can be
-installed and provide "entry points". Pyramid uses an entry point to
-let a Pyramid application it where to find the WSGI app.
+library, which establishes conventions for installing and providing
+"entry points" for Python projects. Pyramid uses an entry point to
+let a Pyramid application know where to find the WSGI app.
Objectives
==========
diff --git a/docs/quick_tutorial/ini/development.ini b/docs/quick_tutorial/ini/development.ini
index ca7d9bf81..8853e2c2b 100644
--- a/docs/quick_tutorial/ini/development.ini
+++ b/docs/quick_tutorial/ini/development.ini
@@ -5,34 +5,3 @@ use = egg:tutorial
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/jinja2.rst b/docs/quick_tutorial/jinja2.rst
index 44d9f635b..613542349 100644
--- a/docs/quick_tutorial/jinja2.rst
+++ b/docs/quick_tutorial/jinja2.rst
@@ -20,8 +20,8 @@ Objectives
Steps
=====
-#. In this step let's start by installing the ``pyramid_jinja2``
- add-on, the copying the ``view_class`` step's directory:
+#. In this step let's start by copying the ``view_class`` step's
+ directory, and then installing the ``pyramid_jinja2`` add-on.
.. code-block:: bash
@@ -45,12 +45,6 @@ Steps
.. literalinclude:: jinja2/tutorial/home.jinja2
:language: html
-#. Get the ``pyramid.includes`` into the functional test setup in
- ``jinja2/tutorial/tests.py``:
-
- .. literalinclude:: jinja2/tutorial/tests.py
- :linenos:
-
#. Now run the tests:
.. code-block:: bash
@@ -78,9 +72,6 @@ Our view code stayed largely the same. We simply changed the file
extension on the renderer. For the template, the syntax for Chameleon
and Jinja2's basic variable insertion is very similar.
-Our functional tests don't have ``development.ini`` so they needed the
-``pyramid.includes`` to be setup in the test setup.
-
Extra Credit
============
@@ -88,9 +79,9 @@ Extra Credit
dependency manually. What is another way we could have made the
association?
-#. We used ``development.ini`` to get the :term:`configurator` to
- load ``pyramid_jinja2``'s configuration. What is another way could
- include it into the config?
+#. We used ``config.include`` which is an imperative configuration to get the
+ :term:`Configurator` to load ``pyramid_jinja2``'s configuration.
+ What is another way could include it into the config?
.. seealso:: `Jinja2 homepage <http://jinja.pocoo.org/>`_,
and
diff --git a/docs/quick_tutorial/jinja2/development.ini b/docs/quick_tutorial/jinja2/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/jinja2/development.ini
+++ b/docs/quick_tutorial/jinja2/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/jinja2/tutorial/tests.py b/docs/quick_tutorial/jinja2/tutorial/tests.py
index 0b22946f3..4381235ec 100644
--- a/docs/quick_tutorial/jinja2/tutorial/tests.py
+++ b/docs/quick_tutorial/jinja2/tutorial/tests.py
@@ -30,13 +30,7 @@ class TutorialViewTests(unittest.TestCase):
class TutorialFunctionalTests(unittest.TestCase):
def setUp(self):
from tutorial import main
-
- settings = {
- 'pyramid.includes': [
- 'pyramid_jinja2'
- ]
- }
- app = main({}, **settings)
+ app = main({})
from webtest import TestApp
self.testapp = TestApp(app)
diff --git a/docs/quick_tutorial/json.rst b/docs/quick_tutorial/json.rst
index ece8a61c0..aa789d833 100644
--- a/docs/quick_tutorial/json.rst
+++ b/docs/quick_tutorial/json.rst
@@ -40,7 +40,7 @@ Steps
:linenos:
#. Rather than implement a new view, we will "stack" another decorator
- on the ``hello`` view:
+ on the ``hello`` view in ``views.py``:
.. literalinclude:: json/tutorial/views.py
:linenos:
diff --git a/docs/quick_tutorial/json/development.ini b/docs/quick_tutorial/json/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/json/development.ini
+++ b/docs/quick_tutorial/json/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/logging.rst b/docs/quick_tutorial/logging.rst
index 855ded59f..e07d23d6d 100644
--- a/docs/quick_tutorial/logging.rst
+++ b/docs/quick_tutorial/logging.rst
@@ -16,7 +16,7 @@ we might need to detect problems when other people use the site. We
need *logging*.
Fortunately Pyramid uses the normal Python approach to logging. The
-scaffold generated, in your ``development.ini``, a number of lines that
+scaffold generated, in your ``development.ini``, has a number of lines that
configure the logging for you to some reasonable defaults. You then see
messages sent by Pyramid (for example, when a new request comes in.)
diff --git a/docs/quick_tutorial/more_view_classes.rst b/docs/quick_tutorial/more_view_classes.rst
index 1e5603554..9cc4cc520 100644
--- a/docs/quick_tutorial/more_view_classes.rst
+++ b/docs/quick_tutorial/more_view_classes.rst
@@ -34,7 +34,7 @@ that determine which view is matched to a request, based on factors
such as the request method, the form parameters, etc. These predicates
provide many axes of flexibility.
-The following shows a simple example with four operations operations:
+The following shows a simple example with four operations:
view a home page which leads to a form, save a change,
and press the delete button.
diff --git a/docs/quick_tutorial/more_view_classes/development.ini b/docs/quick_tutorial/more_view_classes/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/more_view_classes/development.ini
+++ b/docs/quick_tutorial/more_view_classes/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/more_view_classes/tutorial/views.py b/docs/quick_tutorial/more_view_classes/tutorial/views.py
index 635de0520..156e468a9 100644
--- a/docs/quick_tutorial/more_view_classes/tutorial/views.py
+++ b/docs/quick_tutorial/more_view_classes/tutorial/views.py
@@ -5,7 +5,7 @@ from pyramid.view import (
@view_defaults(route_name='hello')
-class TutorialViews:
+class TutorialViews(object):
def __init__(self, request):
self.request = request
self.view_name = 'TutorialViews'
@@ -25,13 +25,13 @@ class TutorialViews:
def hello(self):
return {'page_title': 'Hello View'}
- # Posting to /home via the "Edit" submit button
+ # Posting to /howdy/first/last via the "Edit" submit button
@view_config(request_method='POST', renderer='edit.pt')
def edit(self):
new_name = self.request.params['new_name']
return {'page_title': 'Edit View', 'new_name': new_name}
- # Posting to /home via the "Delete" submit button
+ # Posting to /howdy/first/last via the "Delete" submit button
@view_config(request_method='POST', request_param='form.delete',
renderer='delete.pt')
def delete(self):
diff --git a/docs/quick_tutorial/request_response/development.ini b/docs/quick_tutorial/request_response/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/request_response/development.ini
+++ b/docs/quick_tutorial/request_response/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst
index 72bb4a4f8..b5778ea42 100644
--- a/docs/quick_tutorial/requirements.rst
+++ b/docs/quick_tutorial/requirements.rst
@@ -187,9 +187,15 @@ pipe it to your environment's version of Python.
$ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | $VENV/bin/python
# Windows
- # Use your browser to download:
- # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.p
- # ...into c:\projects\quick_tutorial\ez_setup.py
+ #
+ # Use your web browser to download this file:
+ # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py
+ #
+ # ...and save it to:
+ # c:\projects\quick_tutorial\ez_setup.py
+ #
+ # Then run the following command:
+
c:\> %VENV%\Scripts\python ez_setup.py
If ``wget`` complains with a certificate error, then run this command instead:
diff --git a/docs/quick_tutorial/retail_forms/development.ini b/docs/quick_tutorial/retail_forms/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/retail_forms/development.ini
+++ b/docs/quick_tutorial/retail_forms/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/routing/development.ini b/docs/quick_tutorial/routing/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/routing/development.ini
+++ b/docs/quick_tutorial/routing/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/sessions.rst b/docs/quick_tutorial/sessions.rst
index 0f284e9a7..b4887beb8 100644
--- a/docs/quick_tutorial/sessions.rst
+++ b/docs/quick_tutorial/sessions.rst
@@ -13,10 +13,10 @@ When people use your web application, they frequently perform a task
that requires semi-permanent data to be saved. For example, a shopping
cart. This is called a :term:`session`.
-Pyramid has basic built-in support for sessions, with add-ons
-or your own custom sessioning engine) that can provide
-richer session support. Let's take a look at the
-:ref:`built-in sessioning support <sessions_chapter>`.
+Pyramid has basic built-in support for sessions. Third party packages such as
+``pyramid_redis_sessions`` provide richer session support. Or you can create
+your own custom sessioning engine. Let's take a look at the
+:doc:`built-in sessioning support <../narr/sessions>`.
Objectives
==========
diff --git a/docs/quick_tutorial/sessions/development.ini b/docs/quick_tutorial/sessions/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/sessions/development.ini
+++ b/docs/quick_tutorial/sessions/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/static_assets/development.ini b/docs/quick_tutorial/static_assets/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/static_assets/development.ini
+++ b/docs/quick_tutorial/static_assets/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/templating/development.ini b/docs/quick_tutorial/templating/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/templating/development.ini
+++ b/docs/quick_tutorial/templating/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/unit_testing/development.ini b/docs/quick_tutorial/unit_testing/development.ini
index 470d92c57..52b2a3a41 100644
--- a/docs/quick_tutorial/unit_testing/development.ini
+++ b/docs/quick_tutorial/unit_testing/development.ini
@@ -7,34 +7,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/view_classes/development.ini b/docs/quick_tutorial/view_classes/development.ini
index 62e0c5123..4d47e54a5 100644
--- a/docs/quick_tutorial/view_classes/development.ini
+++ b/docs/quick_tutorial/view_classes/development.ini
@@ -8,34 +8,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/quick_tutorial/views/development.ini b/docs/quick_tutorial/views/development.ini
index 470d92c57..52b2a3a41 100644
--- a/docs/quick_tutorial/views/development.ini
+++ b/docs/quick_tutorial/views/development.ini
@@ -7,34 +7,3 @@ pyramid.includes =
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, tutorial
-
-[logger_tutorial]
-level = DEBUG
-handlers =
-qualname = tutorial
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst
index eb785dd1c..28380bd66 100644
--- a/docs/tutorials/wiki/design.rst
+++ b/docs/tutorials/wiki/design.rst
@@ -53,10 +53,10 @@ Security
We'll eventually be adding security to our application. The components we'll
use to do this are below.
-- USERS, a dictionary mapping usernames to their
+- USERS, a dictionary mapping :term:`userids <userid>` to their
corresponding passwords.
-- GROUPS, a dictionary mapping usernames to a
+- GROUPS, a dictionary mapping :term:`userids <userid>` to a
list of groups to which they belong to.
- ``groupfinder``, an *authorization callback* that looks up
diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst
index df2c83398..ff7413668 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -53,7 +53,8 @@ Security
We'll eventually be adding security to our application. The components we'll
use to do this are below.
-- USERS, a dictionary mapping users names to their corresponding passwords.
+- USERS, a dictionary mapping users names (the user's :term:`userids
+ <userid>`) to their corresponding passwords.
- GROUPS, a dictionary mapping user names to a list of groups they belong to.
diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst
index 65e8393f8..1d863c937 100644
--- a/docs/whatsnew-1.5.rst
+++ b/docs/whatsnew-1.5.rst
@@ -384,9 +384,9 @@ Other Backwards Incompatibilities
are now "reified" properties that look up a locale name and localizer
respectively using the machinery described in :ref:`i18n_chapter`.
-- If you send an ``X-Vhm-Root`` header with a value that ends with a slash (or
- any number of slashes), the trailing slash(es) will be removed before a URL
- is generated when you use use :meth:`~pyramid.request.Request.resource_url`
+- If you send an ``X-Vhm-Root`` header with a value that ends with any number
+ of slashes, the trailing slashes will be removed before the URL
+ is generated when you use :meth:`~pyramid.request.Request.resource_url`
or :meth:`~pyramid.request.Request.resource_path`. Previously the virtual
root path would not have trailing slashes stripped, which would influence URL
generation.
diff --git a/pyramid/authentication.py b/pyramid/authentication.py
index b84981bbc..e0e241e52 100644
--- a/pyramid/authentication.py
+++ b/pyramid/authentication.py
@@ -3,7 +3,6 @@ from codecs import utf_8_decode
from codecs import utf_8_encode
import hashlib
import base64
-import datetime
import re
import time as time_mod
import warnings
@@ -335,11 +334,11 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy):
effective_principals.extend(groups)
return effective_principals
- def remember(self, request, principal, **kw):
- """ Store the ``principal`` as ``repoze.who.userid``.
+ def remember(self, request, userid, **kw):
+ """ Store the ``userid`` as ``repoze.who.userid``.
The identity to authenticated to :mod:`repoze.who`
- will contain the given principal as ``userid``, and
+ will contain the given userid as ``userid``, and
provide all keyword arguments as additional identity
keys. Useful keys could be ``max_age`` or ``userdata``.
"""
@@ -348,7 +347,7 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy):
return []
environ = request.environ
identity = kw
- identity['repoze.who.userid'] = principal
+ identity['repoze.who.userid'] = userid
return identifier.remember(environ, identity)
def forget(self, request):
@@ -404,7 +403,7 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy):
""" The ``REMOTE_USER`` value found within the ``environ``."""
return request.environ.get(self.environ_key)
- def remember(self, request, principal, **kw):
+ def remember(self, request, userid, **kw):
""" A no-op. The ``REMOTE_USER`` does not provide a protocol for
remembering the user. This will be application-specific and can
be done somewhere else or in a subclass."""
@@ -652,7 +651,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
if result:
return result['userid']
- def remember(self, request, principal, **kw):
+ def remember(self, request, userid, **kw):
""" Accepts the following kw args: ``max_age=<int-seconds>,
``tokens=<sequence-of-ascii-strings>``.
@@ -660,7 +659,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
the response.
"""
- return self.cookie.remember(request, principal, **kw)
+ return self.cookie.remember(request, userid, **kw)
def forget(self, request):
""" A list of headers which will delete appropriate cookies."""
@@ -741,7 +740,7 @@ def parse_ticket(secret, ticket, ip, hashalg='md5'):
If the ticket cannot be parsed, a ``BadTicket`` exception will be raised
with an explanation.
"""
- ticket = ticket.strip('"')
+ ticket = native_(ticket).strip('"')
digest_size = hashlib.new(hashalg).digest_size * 2
digest = ticket[:digest_size]
try:
@@ -929,7 +928,7 @@ class AuthTktCookieHelper(object):
if reissue and not hasattr(request, '_authtkt_reissued'):
if ( (now - timestamp) > self.reissue_time ):
- # work around https://github.com/Pylons/pyramid/issues#issue/108
+ # See https://github.com/Pylons/pyramid/issues#issue/108
tokens = list(filter(None, tokens))
headers = self.remember(request, userid, max_age=self.max_age,
tokens=tokens)
@@ -1061,13 +1060,13 @@ class SessionAuthenticationPolicy(CallbackAuthenticationPolicy):
self.userid_key = prefix + 'userid'
self.debug = debug
- def remember(self, request, principal, **kw):
- """ Store a principal in the session."""
- request.session[self.userid_key] = principal
+ def remember(self, request, userid, **kw):
+ """ Store a userid in the session."""
+ request.session[self.userid_key] = userid
return []
def forget(self, request):
- """ Remove the stored principal from the session."""
+ """ Remove the stored userid from the session."""
if self.userid_key in request.session:
del request.session[self.userid_key]
return []
@@ -1132,7 +1131,7 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy):
if credentials:
return credentials[0]
- def remember(self, request, principal, **kw):
+ def remember(self, request, userid, **kw):
""" A no-op. Basic authentication does not provide a protocol for
remembering the user. Credentials are sent on every request.
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 32cf82fba..2ab654b9a 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -125,6 +125,14 @@ class Configurator(
is passed (the default), the package is assumed to be the Python package
in which the *caller* of the ``Configurator`` constructor lives.
+ If the ``root_package`` is passed, it will propagate through the
+ configuration hierarchy as a way for included packages to locate
+ resources relative to the package in which the main ``Configurator`` was
+ created. If ``None`` is passed (the default), the ``root_package`` will
+ be derived from the ``package`` argument. The ``package`` attribute is
+ always pointing at the package being included when using :meth:`.include`,
+ whereas the ``root_package`` does not change.
+
If the ``settings`` argument is passed, it should be a Python dictionary
representing the :term:`deployment settings` for this application. These
are later retrievable using the
@@ -171,6 +179,11 @@ class Configurator(
See :ref:`changing_the_request_factory`. By default it is ``None``,
which means use the default request factory.
+ If ``response_factory`` is passed, it should be a :term:`response
+ factory` implementation or a :term:`dotted Python name` to the same.
+ See :ref:`changing_the_response_factory`. By default it is ``None``,
+ which means use the default response factory.
+
If ``default_permission`` is passed, it should be a
:term:`permission` string to be used as the default permission for
all view configuration registrations performed against this
@@ -182,7 +195,7 @@ class Configurator(
configurations which do not explicitly declare a permission will
always be executable by entirely anonymous users (any
authorization policy in effect is ignored).
-
+
.. seealso::
See also :ref:`setting_a_default_permission`.
@@ -243,6 +256,10 @@ class Configurator(
.. versionadded:: 1.3
The ``introspection`` argument.
+
+ .. versionadded:: 1.6
+ The ``root_package`` argument.
+ The ``response_factory`` argument.
"""
manager = manager # for testing injection
venusian = venusian # for testing injection
@@ -265,6 +282,7 @@ class Configurator(
debug_logger=None,
locale_negotiator=None,
request_factory=None,
+ response_factory=None,
default_permission=None,
session_factory=None,
default_view_mapper=None,
@@ -272,13 +290,17 @@ class Configurator(
exceptionresponse_view=default_exceptionresponse_view,
route_prefix=None,
introspection=True,
+ root_package=None,
):
if package is None:
package = caller_package()
+ if root_package is None:
+ root_package = package
name_resolver = DottedNameResolver(package)
self.name_resolver = name_resolver
self.package_name = name_resolver.get_package_name()
self.package = name_resolver.get_package()
+ self.root_package = root_package
self.registry = registry
self.autocommit = autocommit
self.route_prefix = route_prefix
@@ -295,6 +317,7 @@ class Configurator(
debug_logger=debug_logger,
locale_negotiator=locale_negotiator,
request_factory=request_factory,
+ response_factory=response_factory,
default_permission=default_permission,
session_factory=session_factory,
default_view_mapper=default_view_mapper,
@@ -310,6 +333,7 @@ class Configurator(
debug_logger=None,
locale_negotiator=None,
request_factory=None,
+ response_factory=None,
default_permission=None,
session_factory=None,
default_view_mapper=None,
@@ -397,6 +421,9 @@ class Configurator(
if request_factory:
self.set_request_factory(request_factory)
+ if response_factory:
+ self.set_response_factory(response_factory)
+
if default_permission:
self.set_default_permission(default_permission)
@@ -454,7 +481,7 @@ class Configurator(
_registry.registerSelfAdapter = registerSelfAdapter
# API
-
+
def _get_introspector(self):
introspector = getattr(self.registry, 'introspector', _marker)
if introspector is _marker:
@@ -481,6 +508,7 @@ class Configurator(
def _add_predicate(self, type, name, factory, weighs_more_than=None,
weighs_less_than=None):
+ factory = self.maybe_dotted(factory)
discriminator = ('%s predicate' % type, name)
intr = self.introspectable(
'%s predicates' % type,
@@ -488,7 +516,7 @@ class Configurator(
'%s predicate named %s' % (type, name),
'%s predicate' % type)
intr['name'] = name
- intr['factory'] = self.maybe_dotted(factory)
+ intr['factory'] = factory
intr['weighs_more_than'] = weighs_more_than
intr['weighs_less_than'] = weighs_less_than
def register():
@@ -746,6 +774,7 @@ class Configurator(
configurator = self.__class__(
registry=self.registry,
package=package_of(module),
+ root_package=self.root_package,
autocommit=self.autocommit,
route_prefix=route_prefix,
)
@@ -805,6 +834,7 @@ class Configurator(
configurator = self.__class__(
registry=self.registry,
package=package,
+ root_package=self.root_package,
autocommit=self.autocommit,
route_prefix=self.route_prefix,
introspection=self.introspection,
diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py
index 0616e6cda..9da092f08 100644
--- a/pyramid/config/assets.py
+++ b/pyramid/config/assets.py
@@ -1,3 +1,4 @@
+import os
import pkg_resources
import sys
@@ -79,7 +80,8 @@ class OverrideProvider(pkg_resources.DefaultProvider):
return result
return pkg_resources.DefaultProvider.resource_listdir(
self, resource_name)
-
+
+
@implementer(IPackageOverrides)
class PackageOverrides(object):
# pkg_resources arg in kw args below for testing
@@ -97,57 +99,61 @@ class PackageOverrides(object):
# optional)...
# A __loader__ attribute is basically metadata, and setuptools
# uses it as such.
- package.__loader__ = self
+ package.__loader__ = self
# we call register_loader_type for every instantiation of this
# class; that's OK, it's idempotent to do it more than once.
pkg_resources.register_loader_type(self.__class__, OverrideProvider)
self.overrides = []
self.overridden_package_name = package.__name__
- def insert(self, path, package, prefix):
+ def insert(self, path, source):
if not path or path.endswith('/'):
- override = DirectoryOverride(path, package, prefix)
+ override = DirectoryOverride(path, source)
else:
- override = FileOverride(path, package, prefix)
+ override = FileOverride(path, source)
self.overrides.insert(0, override)
return override
- def search_path(self, resource_name):
+ def filtered_sources(self, resource_name):
for override in self.overrides:
o = override(resource_name)
if o is not None:
- package, name = o
- yield package, name
+ yield o
def get_filename(self, resource_name):
- for package, rname in self.search_path(resource_name):
- if pkg_resources.resource_exists(package, rname):
- return pkg_resources.resource_filename(package, rname)
+ for source, path in self.filtered_sources(resource_name):
+ result = source.get_filename(path)
+ if result is not None:
+ return result
def get_stream(self, resource_name):
- for package, rname in self.search_path(resource_name):
- if pkg_resources.resource_exists(package, rname):
- return pkg_resources.resource_stream(package, rname)
+ for source, path in self.filtered_sources(resource_name):
+ result = source.get_stream(path)
+ if result is not None:
+ return result
def get_string(self, resource_name):
- for package, rname in self.search_path(resource_name):
- if pkg_resources.resource_exists(package, rname):
- return pkg_resources.resource_string(package, rname)
+ for source, path in self.filtered_sources(resource_name):
+ result = source.get_string(path)
+ if result is not None:
+ return result
def has_resource(self, resource_name):
- for package, rname in self.search_path(resource_name):
- if pkg_resources.resource_exists(package, rname):
+ for source, path in self.filtered_sources(resource_name):
+ if source.exists(path):
return True
def isdir(self, resource_name):
- for package, rname in self.search_path(resource_name):
- if pkg_resources.resource_exists(package, rname):
- return pkg_resources.resource_isdir(package, rname)
+ for source, path in self.filtered_sources(resource_name):
+ result = source.isdir(path)
+ if result is not None:
+ return result
def listdir(self, resource_name):
- for package, rname in self.search_path(resource_name):
- if pkg_resources.resource_exists(package, rname):
- return pkg_resources.resource_listdir(package, rname)
+ for source, path in self.filtered_sources(resource_name):
+ result = source.listdir(path)
+ if result is not None:
+ return result
@property
def real_loader(self):
@@ -174,72 +180,180 @@ class PackageOverrides(object):
""" See IPEP302Loader.
"""
return self.real_loader.get_source(fullname)
-
+
class DirectoryOverride:
- def __init__(self, path, package, prefix):
+ def __init__(self, path, source):
self.path = path
- self.package = package
- self.prefix = prefix
self.pathlen = len(self.path)
+ self.source = source
def __call__(self, resource_name):
if resource_name.startswith(self.path):
- name = '%s%s' % (self.prefix, resource_name[self.pathlen:])
- return self.package, name
+ new_path = resource_name[self.pathlen:]
+ return self.source, new_path
class FileOverride:
- def __init__(self, path, package, prefix):
+ def __init__(self, path, source):
self.path = path
- self.package = package
- self.prefix = prefix
+ self.source = source
def __call__(self, resource_name):
if resource_name == self.path:
- return self.package, self.prefix
+ return self.source, ''
+
+
+class PackageAssetSource(object):
+ """
+ An asset source relative to a package.
+
+ If this asset source is a file, then we expect the ``prefix`` to point
+ to the new name of the file, and the incoming ``resource_name`` will be
+ the empty string, as returned by the ``FileOverride``.
+
+ """
+ def __init__(self, package, prefix):
+ self.package = package
+ self.prefix = prefix
+
+ def get_path(self, resource_name):
+ return '%s%s' % (self.prefix, resource_name)
+
+ def get_filename(self, resource_name):
+ path = self.get_path(resource_name)
+ if pkg_resources.resource_exists(self.package, path):
+ return pkg_resources.resource_filename(self.package, path)
+
+ def get_stream(self, resource_name):
+ path = self.get_path(resource_name)
+ if pkg_resources.resource_exists(self.package, path):
+ return pkg_resources.resource_stream(self.package, path)
+
+ def get_string(self, resource_name):
+ path = self.get_path(resource_name)
+ if pkg_resources.resource_exists(self.package, path):
+ return pkg_resources.resource_string(self.package, path)
+
+ def exists(self, resource_name):
+ path = self.get_path(resource_name)
+ if pkg_resources.resource_exists(self.package, path):
+ return True
+
+ def isdir(self, resource_name):
+ path = self.get_path(resource_name)
+ if pkg_resources.resource_exists(self.package, path):
+ return pkg_resources.resource_isdir(self.package, path)
+
+ def listdir(self, resource_name):
+ path = self.get_path(resource_name)
+ if pkg_resources.resource_exists(self.package, path):
+ return pkg_resources.resource_listdir(self.package, path)
+
+
+class FSAssetSource(object):
+ """
+ An asset source relative to a path in the filesystem.
+
+ """
+ def __init__(self, prefix):
+ self.prefix = prefix
+
+ def get_filename(self, resource_name):
+ if resource_name:
+ path = os.path.join(self.prefix, resource_name)
+ else:
+ path = self.prefix
+
+ if os.path.exists(path):
+ return path
+
+ def get_stream(self, resource_name):
+ path = self.get_filename(resource_name)
+ if path is not None:
+ return open(path, 'rb')
+
+ def get_string(self, resource_name):
+ stream = self.get_stream(resource_name)
+ if stream is not None:
+ with stream:
+ return stream.read()
+
+ def exists(self, resource_name):
+ path = self.get_filename(resource_name)
+ if path is not None:
+ return True
+
+ def isdir(self, resource_name):
+ path = self.get_filename(resource_name)
+ if path is not None:
+ return os.path.isdir(path)
+
+ def listdir(self, resource_name):
+ path = self.get_filename(resource_name)
+ if path is not None:
+ return os.listdir(path)
class AssetsConfiguratorMixin(object):
- def _override(self, package, path, override_package, override_prefix,
+ def _override(self, package, path, override_source,
PackageOverrides=PackageOverrides):
pkg_name = package.__name__
- override_pkg_name = override_package.__name__
override = self.registry.queryUtility(IPackageOverrides, name=pkg_name)
if override is None:
override = PackageOverrides(package)
self.registry.registerUtility(override, IPackageOverrides,
name=pkg_name)
- override.insert(path, override_pkg_name, override_prefix)
+ override.insert(path, override_source)
@action_method
def override_asset(self, to_override, override_with, _override=None):
""" Add a :app:`Pyramid` asset override to the current
configuration state.
- ``to_override`` is a :term:`asset specification` to the
+ ``to_override`` is an :term:`asset specification` to the
asset being overridden.
- ``override_with`` is a :term:`asset specification` to the
- asset that is performing the override.
+ ``override_with`` is an :term:`asset specification` to the
+ asset that is performing the override. This may also be an absolute
+ path.
See :ref:`assets_chapter` for more
information about asset overrides."""
if to_override == override_with:
- raise ConfigurationError('You cannot override an asset with itself')
+ raise ConfigurationError(
+ 'You cannot override an asset with itself')
package = to_override
path = ''
if ':' in to_override:
package, path = to_override.split(':', 1)
- override_package = override_with
- override_prefix = ''
- if ':' in override_with:
- override_package, override_prefix = override_with.split(':', 1)
-
# *_isdir = override is package or directory
- overridden_isdir = path=='' or path.endswith('/')
- override_isdir = override_prefix=='' or override_prefix.endswith('/')
+ overridden_isdir = path == '' or path.endswith('/')
+
+ if os.path.isabs(override_with):
+ override_source = FSAssetSource(override_with)
+ if not os.path.exists(override_with):
+ raise ConfigurationError(
+ 'Cannot override asset with an absolute path that does '
+ 'not exist')
+ override_isdir = os.path.isdir(override_with)
+ override_package = None
+ override_prefix = override_with
+ else:
+ override_package = override_with
+ override_prefix = ''
+ if ':' in override_with:
+ override_package, override_prefix = override_with.split(':', 1)
+
+ __import__(override_package)
+ to_package = sys.modules[override_package]
+ override_source = PackageAssetSource(to_package, override_prefix)
+
+ override_isdir = (
+ override_prefix == '' or
+ override_with.endswith('/')
+ )
if overridden_isdir and (not override_isdir):
raise ConfigurationError(
@@ -255,10 +369,8 @@ class AssetsConfiguratorMixin(object):
def register():
__import__(package)
- __import__(override_package)
from_package = sys.modules[package]
- to_package = sys.modules[override_package]
- override(from_package, path, to_package, override_prefix)
+ override(from_package, path, override_source)
intr = self.introspectable(
'asset overrides',
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index 1990c377a..d7a48ba93 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -1,9 +1,10 @@
-from zope.deprecation import deprecate
+from zope.deprecation import deprecated
from zope.interface import implementer
from pyramid.interfaces import (
IDefaultRootFactory,
IRequestFactory,
+ IResponseFactory,
IRequestExtensions,
IRootFactory,
ISessionFactory,
@@ -97,6 +98,32 @@ class FactoriesConfiguratorMixin(object):
self.action(IRequestFactory, register, introspectables=(intr,))
@action_method
+ def set_response_factory(self, factory):
+ """ The object passed as ``factory`` should be an object (or a
+ :term:`dotted Python name` which refers to an object) which
+ will be used by the :app:`Pyramid` as the default response
+ objects. This factory object must have the same
+ methods and attributes as the
+ :class:`pyramid.request.Response` class.
+
+ .. note::
+
+ Using the ``response_factory`` argument to the
+ :class:`pyramid.config.Configurator` constructor
+ can be used to achieve the same purpose.
+ """
+ factory = self.maybe_dotted(factory)
+
+ def register():
+ self.registry.registerUtility(factory, IResponseFactory)
+
+ intr = self.introspectable('response factory', None,
+ self.object_description(factory),
+ 'response factory')
+ intr['factory'] = factory
+ self.action(IResponseFactory, register, introspectables=(intr,))
+
+ @action_method
def add_request_method(self,
callable=None,
name=None,
@@ -180,8 +207,6 @@ class FactoriesConfiguratorMixin(object):
introspectables=(intr,))
@action_method
- @deprecate('set_request_propery() is deprecated as of Pyramid 1.5; use '
- 'add_request_method() with the property=True argument instead')
def set_request_property(self, callable, name=None, reify=False):
""" Add a property to the request object.
@@ -195,6 +220,11 @@ class FactoriesConfiguratorMixin(object):
self.add_request_method(
callable, name=name, property=not reify, reify=reify)
+ deprecated(
+ set_request_property,
+ 'set_request_propery() is deprecated as of Pyramid 1.5; use '
+ 'add_request_method() with the property=True argument instead')
+
@implementer(IRequestExtensions)
class _RequestExtensions(object):
def __init__(self):
diff --git a/pyramid/config/settings.py b/pyramid/config/settings.py
index 565a6699c..492b7d524 100644
--- a/pyramid/config/settings.py
+++ b/pyramid/config/settings.py
@@ -17,7 +17,7 @@ class SettingsConfiguratorMixin(object):
def add_settings(self, settings=None, **kw):
"""Augment the :term:`deployment settings` with one or more
- key/value pairs.
+ key/value pairs.
You may pass a dictionary::
@@ -117,6 +117,11 @@ class Settings(dict):
config_prevent_http_cache)
eff_prevent_http_cache = asbool(eget('PYRAMID_PREVENT_HTTP_CACHE',
config_prevent_http_cache))
+ config_prevent_cachebust = self.get('prevent_cachebust', '')
+ config_prevent_cachebust = self.get('pyramid.prevent_cachebust',
+ config_prevent_cachebust)
+ eff_prevent_cachebust = asbool(eget('PYRAMID_PREVENT_CACHEBUST',
+ config_prevent_cachebust))
update = {
'debug_authorization': eff_debug_all or eff_debug_auth,
@@ -128,6 +133,7 @@ class Settings(dict):
'reload_assets':eff_reload_all or eff_reload_assets,
'default_locale_name':eff_locale_name,
'prevent_http_cache':eff_prevent_http_cache,
+ 'prevent_cachebust':eff_prevent_cachebust,
'pyramid.debug_authorization': eff_debug_all or eff_debug_auth,
'pyramid.debug_notfound': eff_debug_all or eff_debug_notfound,
@@ -138,6 +144,7 @@ class Settings(dict):
'pyramid.reload_assets':eff_reload_all or eff_reload_assets,
'pyramid.default_locale_name':eff_locale_name,
'pyramid.prevent_http_cache':eff_prevent_http_cache,
+ 'pyramid.prevent_cachebust':eff_prevent_cachebust,
}
self.update(update)
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 2f6c758ab..c01b72e12 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -34,6 +34,7 @@ from pyramid.interfaces import (
)
from pyramid import renderers
+from pyramid.static import PathSegmentMd5CacheBuster
from pyramid.compat import (
string_types,
@@ -44,11 +45,6 @@ from pyramid.compat import (
is_nonstr_iter
)
-from pyramid.encode import (
- quote_plus,
- urlencode,
-)
-
from pyramid.exceptions import (
ConfigurationError,
PredicateMismatch,
@@ -57,6 +53,7 @@ from pyramid.exceptions import (
from pyramid.httpexceptions import (
HTTPForbidden,
HTTPNotFound,
+ default_exceptionresponse_view,
)
from pyramid.registry import (
@@ -302,7 +299,7 @@ class ViewDeriver(object):
raise PredicateMismatch(
'predicate mismatch for view %s (%s)' % (
view_name, predicate.text()))
- return view(context, request)
+ return view(context, request)
def checker(context, request):
return all((predicate(context, request) for predicate in
preds))
@@ -879,11 +876,11 @@ class ViewsConfiguratorMixin(object):
request_method
- This value can be either a strings (such as ``GET``, ``POST``,
- ``PUT``, ``DELETE``, or ``HEAD``) representing an HTTP
- ``REQUEST_METHOD``, or a tuple containing one or more of these
- strings. A view declaration with this argument ensures that the
- view will only be called when the ``method`` attribute of the
+ This value can be either a string (such as ``"GET"``, ``"POST"``,
+ ``"PUT"``, ``"DELETE"``, ``"HEAD"`` or ``"OPTIONS"``) representing
+ an HTTP ``REQUEST_METHOD``, or a tuple containing one or more of
+ these strings. A view declaration with this argument ensures that
+ the view will only be called when the ``method`` attribute of the
request (aka the ``REQUEST_METHOD`` of the WSGI environment) matches
a supplied value. Note that use of ``GET`` also implies that the
view will respond to ``HEAD`` as of Pyramid 1.4.
@@ -894,8 +891,8 @@ class ViewsConfiguratorMixin(object):
request_param
- This value can be any string or any sequence of strings. A view
- declaration with this argument ensures that the view will only be
+ This value can be any string or any sequence of strings. A view
+ declaration with this argument ensures that the view will only be
called when the :term:`request` has a key in the ``request.params``
dictionary (an HTTP ``GET`` or ``POST`` variable) that has a
name which matches the supplied value (if the value is a string)
@@ -1001,7 +998,7 @@ class ViewsConfiguratorMixin(object):
Note that using this feature requires a :term:`session factory` to
have been configured.
-
+
.. versionadded:: 1.4a2
physical_path
@@ -1039,7 +1036,7 @@ class ViewsConfiguratorMixin(object):
This value should be a sequence of references to custom
predicate callables. Use custom predicates when no set of
predefined predicates do what you need. Custom predicates
- can be combined with predefined predicates as necessary.
+ can be combined with predefined predicates as necessary.
Each custom predicate callable should accept two arguments:
``context`` and ``request`` and should return either
``True`` or ``False`` after doing arbitrary evaluation of
@@ -1074,7 +1071,7 @@ class ViewsConfiguratorMixin(object):
DeprecationWarning,
stacklevel=4
)
-
+
view = self.maybe_dotted(view)
context = self.maybe_dotted(context)
for_ = self.maybe_dotted(for_)
@@ -1160,7 +1157,7 @@ class ViewsConfiguratorMixin(object):
view_desc = self.object_description(view)
tmpl_intr = None
-
+
view_intr = self.introspectable('views',
discriminator,
view_desc,
@@ -1189,10 +1186,6 @@ class ViewsConfiguratorMixin(object):
predlist = self.get_predlist('view')
def register(permission=permission, renderer=renderer):
- # the discrim_func above is guaranteed to have been called already
- order = view_intr['order']
- preds = view_intr['predicates']
- phash = view_intr['phash']
request_iface = IRequest
if route_name is not None:
request_iface = self.registry.queryUtility(IRouteRequest,
@@ -1569,7 +1562,7 @@ class ViewsConfiguratorMixin(object):
wrapper=None,
route_name=None,
request_type=None,
- request_method=None,
+ request_method=None,
request_param=None,
containment=None,
xhr=None,
@@ -1595,9 +1588,12 @@ class ViewsConfiguratorMixin(object):
config.add_forbidden_view(forbidden)
+ If ``view`` argument is not provided, the view callable defaults to
+ :func:`~pyramid.httpexceptions.default_exceptionresponse_view`.
+
All arguments have the same meaning as
:meth:`pyramid.config.Configurator.add_view` and each predicate
- argument restricts the set of circumstances under which this notfound
+ argument restricts the set of circumstances under which this forbidden
view will be invoked. Unlike
:meth:`pyramid.config.Configurator.add_view`, this method will raise
an exception if passed ``name``, ``permission``, ``context``,
@@ -1612,7 +1608,10 @@ class ViewsConfiguratorMixin(object):
'%s may not be used as an argument to add_forbidden_view'
% arg
)
-
+
+ if view is None:
+ view = default_exceptionresponse_view
+
settings = dict(
view=view,
context=HTTPForbidden,
@@ -1623,7 +1622,7 @@ class ViewsConfiguratorMixin(object):
containment=containment,
xhr=xhr,
accept=accept,
- header=header,
+ header=header,
path_info=path_info,
custom_predicates=custom_predicates,
decorator=decorator,
@@ -1638,7 +1637,7 @@ class ViewsConfiguratorMixin(object):
return self.add_view(**settings)
set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias
-
+
@viewdefaults
@action_method
def add_notfound_view(
@@ -1649,7 +1648,7 @@ class ViewsConfiguratorMixin(object):
wrapper=None,
route_name=None,
request_type=None,
- request_method=None,
+ request_method=None,
request_param=None,
containment=None,
xhr=None,
@@ -1675,6 +1674,9 @@ class ViewsConfiguratorMixin(object):
config.add_notfound_view(notfound)
+ If ``view`` argument is not provided, the view callable defaults to
+ :func:`~pyramid.httpexceptions.default_exceptionresponse_view`.
+
All arguments except ``append_slash`` have the same meaning as
:meth:`pyramid.config.Configurator.add_view` and each predicate
argument restricts the set of circumstances under which this notfound
@@ -1700,7 +1702,10 @@ class ViewsConfiguratorMixin(object):
'%s may not be used as an argument to add_notfound_view'
% arg
)
-
+
+ if view is None:
+ view = default_exceptionresponse_view
+
settings = dict(
view=view,
context=HTTPNotFound,
@@ -1711,7 +1716,7 @@ class ViewsConfiguratorMixin(object):
containment=containment,
xhr=xhr,
accept=accept,
- header=header,
+ header=header,
path_info=path_info,
custom_predicates=custom_predicates,
decorator=decorator,
@@ -1786,7 +1791,20 @@ class ViewsConfiguratorMixin(object):
``Expires`` and ``Cache-Control`` headers for static assets served.
Note that this argument has no effect when the ``name`` is a *url
prefix*. By default, this argument is ``None``, meaning that no
- particular Expires or Cache-Control headers are set in the response.
+ particular Expires or Cache-Control headers are set in the response,
+ unless ``cachebust`` is specified.
+
+ The ``cachebust`` keyword argument may be set to cause
+ :meth:`~pyramid.request.Request.static_url` to use cache busting when
+ generating URLs. See :ref:`cache_busting` for general information
+ about cache busting. The value of the ``cachebust`` argument may be
+ ``True``, in which case a default cache busting implementation is used.
+ The value of the ``cachebust`` argument may also be an object which
+ implements :class:`~pyramid.interfaces.ICacheBuster`. See the
+ :mod:`~pyramid.static` module for some implementations. If the
+ ``cachebust`` argument is provided, the default for ``cache_max_age``
+ is modified to be ten years. ``cache_max_age`` may still be explicitly
+ provided to override this default.
The ``permission`` keyword argument is used to specify the
:term:`permission` required by a user to execute the static view. By
@@ -1884,6 +1902,8 @@ def isexception(o):
@implementer(IStaticURLInfo)
class StaticURLInfo(object):
+ # Indirection for testing
+ _default_cachebust = PathSegmentMd5CacheBuster
def _get_registrations(self, registry):
try:
@@ -1897,11 +1917,14 @@ class StaticURLInfo(object):
registry = request.registry
except AttributeError: # bw compat (for tests)
registry = get_current_registry()
- for (url, spec, route_name) in self._get_registrations(registry):
+ registrations = self._get_registrations(registry)
+ for (url, spec, route_name, cachebust) in registrations:
if path.startswith(spec):
subpath = path[len(spec):]
if WIN: # pragma: no cover
subpath = subpath.replace('\\', '/') # windows
+ if cachebust:
+ subpath, kw = cachebust(subpath, kw)
if url is None:
kw['subpath'] = subpath
return request.route_url(route_name, **kw)
@@ -1928,7 +1951,7 @@ class StaticURLInfo(object):
sep = os.sep
else:
sep = '/'
- if not spec.endswith(sep):
+ if not spec.endswith(sep) and not spec.endswith(':'):
spec = spec + sep
# we also make sure the name ends with a slash, purely as a
@@ -1941,6 +1964,21 @@ class StaticURLInfo(object):
# make sure it ends with a slash
name = name + '/'
+ if config.registry.settings.get('pyramid.prevent_cachebust'):
+ cb = None
+ else:
+ cb = extra.pop('cachebust', None)
+ if cb is True:
+ cb = self._default_cachebust()
+ if cb:
+ def cachebust(subpath, kw):
+ token = cb.token(spec + subpath)
+ subpath_tuple = tuple(subpath.split('/'))
+ subpath_tuple, kw = cb.pregenerate(token, subpath_tuple, kw)
+ return '/'.join(subpath_tuple), kw
+ else:
+ cachebust = None
+
if url_parse(name).netloc:
# it's a URL
# url, spec, route_name
@@ -1949,10 +1987,14 @@ class StaticURLInfo(object):
else:
# it's a view name
url = None
- cache_max_age = extra.pop('cache_max_age', None)
+ ten_years = 10 * 365 * 24 * 60 * 60 # more or less
+ default = ten_years if cb else None
+ cache_max_age = extra.pop('cache_max_age', default)
+
# create a view
+ cb_match = getattr(cb, 'match', None)
view = static_view(spec, cache_max_age=cache_max_age,
- use_subpath=True)
+ use_subpath=True, cachebust_match=cb_match)
# Mutate extra to allow factory, etc to be passed through here.
# Treat permission specially because we'd like to default to
@@ -1993,7 +2035,7 @@ class StaticURLInfo(object):
registrations.pop(idx)
# url, spec, route_name
- registrations.append((url, spec, route_name))
+ registrations.append((url, spec, route_name, cachebust))
intr = config.introspectable('static views',
name,
@@ -2004,4 +2046,3 @@ class StaticURLInfo(object):
config.action(None, callable=register, introspectables=(intr,))
-
diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py
index ebee39ada..a30129e16 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -52,6 +52,9 @@ Exception
* 422 - HTTPUnprocessableEntity
* 423 - HTTPLocked
* 424 - HTTPFailedDependency
+ * 428 - HTTPPreconditionRequired
+ * 429 - HTTPTooManyRequests
+ * 431 - HTTPRequestHeaderFieldsTooLarge
HTTPServerError
* 500 - HTTPInternalServerError
* 501 - HTTPNotImplemented
@@ -868,7 +871,12 @@ class HTTPUnprocessableEntity(HTTPClientError):
subclass of :class:`~HTTPClientError`
This indicates that the server is unable to process the contained
- instructions. Only for WebDAV.
+ instructions.
+
+ May be used to notify the client that their JSON/XML is well formed, but
+ not correct for the current request.
+
+ See RFC4918 section 11 for more information.
code: 422, title: Unprocessable Entity
"""
@@ -881,7 +889,7 @@ class HTTPLocked(HTTPClientError):
"""
subclass of :class:`~HTTPClientError`
- This indicates that the resource is locked. Only for WebDAV
+ This indicates that the resource is locked.
code: 423, title: Locked
"""
@@ -896,7 +904,6 @@ class HTTPFailedDependency(HTTPClientError):
This indicates that the method could not be performed because the
requested action depended on another action and that action failed.
- Only for WebDAV.
code: 424, title: Failed Dependency
"""
@@ -907,6 +914,62 @@ class HTTPFailedDependency(HTTPClientError):
'The method could not be performed because the requested '
'action dependended on another action and that action failed')
+class HTTPPreconditionRequired(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the origin server requires the
+ request to be conditional.
+
+ Its typical use is to avoid the "lost update" problem, where a client
+ GETs a resource's state, modifies it, and PUTs it back to the server,
+ when meanwhile a third party has modified the state on the server,
+ leading to a conflict. By requiring requests to be conditional, the
+ server can assure that clients are working with the correct copies.
+
+ RFC 6585.3
+
+ code: 428, title: Precondition Required
+ """
+ code = 428
+ title = 'Precondition Required'
+ explanation = (
+ 'The origin server requires the request to be conditional.')
+
+class HTTPTooManyRequests(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the user has sent too many
+ requests in a given amount of time ("rate limiting").
+
+ RFC 6585.4
+
+ code: 429, title: Too Many Requests
+ """
+ code = 429
+ title = 'Too Many Requests'
+ explanation = (
+ 'The action could not be performed because there were too '
+ 'many requests by the client.')
+
+class HTTPRequestHeaderFieldsTooLarge(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is unwilling to process
+ the request because its header fields are too large. The request MAY
+ be resubmitted after reducing the size of the request header fields.
+
+ RFC 6585.5
+
+ code: 431, title: Request Header Fields Too Large
+ """
+ code = 431
+ title = 'Request Header Fields Too Large'
+ explanation = (
+ 'The requests header fields were too large.')
+
############################################################
## 5xx Server Error
############################################################
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 75b9b1cb9..b21c6b9cc 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -439,36 +439,55 @@ class IViewMapperFactory(Interface):
class IAuthenticationPolicy(Interface):
""" An object representing a Pyramid authentication policy. """
+
def authenticated_userid(request):
- """ Return the authenticated userid or ``None`` if no authenticated
- userid can be found. This method of the policy should ensure that a
- record exists in whatever persistent store is used related to the
- user (the user should not have been deleted); if a record associated
- with the current id does not exist in a persistent store, it should
- return ``None``."""
+ """ Return the authenticated :term:`userid` or ``None`` if
+ no authenticated userid can be found. This method of the
+ policy should ensure that a record exists in whatever
+ persistent store is used related to the user (the user
+ should not have been deleted); if a record associated with
+ the current id does not exist in a persistent store, it
+ should return ``None``.
+
+ """
def unauthenticated_userid(request):
- """ Return the *unauthenticated* userid. This method performs the
- same duty as ``authenticated_userid`` but is permitted to return the
- userid based only on data present in the request; it needn't (and
- shouldn't) check any persistent store to ensure that the user record
- related to the request userid exists."""
+ """ Return the *unauthenticated* userid. This method
+ performs the same duty as ``authenticated_userid`` but is
+ permitted to return the userid based only on data present
+ in the request; it needn't (and shouldn't) check any
+ persistent store to ensure that the user record related to
+ the request userid exists.
+
+ This method is intended primarily a helper to assist the
+ ``authenticated_userid`` method in pulling credentials out
+ of the request data, abstracting away the specific headers,
+ query strings, etc that are used to authenticate the request.
+
+ """
def effective_principals(request):
""" Return a sequence representing the effective principals
- including the userid and any groups belonged to by the current
- user, including 'system' groups such as Everyone and
- Authenticated. """
+ typically including the :term:`userid` and any groups belonged
+ to by the current user, always including 'system' groups such
+ as ``pyramid.security.Everyone`` and
+ ``pyramid.security.Authenticated``.
+
+ """
- def remember(request, principal, **kw):
+ def remember(request, userid, **kw):
""" Return a set of headers suitable for 'remembering' the
- principal named ``principal`` when set in a response. An
- individual authentication policy and its consumers can decide
- on the composition and meaning of ``**kw.`` """
+ :term:`userid` named ``userid`` when set in a response. An
+ individual authentication policy and its consumers can
+ decide on the composition and meaning of ``**kw``.
+
+ """
def forget(request):
""" Return a set of headers suitable for 'forgetting' the
- current user on subsequent requests. """
+ current user on subsequent requests.
+
+ """
class IAuthorizationPolicy(Interface):
""" An object representing a Pyramid authorization policy. """
@@ -708,7 +727,7 @@ class IRoute(Interface):
pregenerator = Attribute('This attribute should either be ``None`` or '
'a callable object implementing the '
'``IRoutePregenerator`` interface')
-
+
def match(path):
"""
If the ``path`` passed to this function can be matched by the
@@ -803,7 +822,7 @@ class IContextURL(IResourceURL):
# <__main__.Fudge object at 0x1cda890>
# <object object at 0x7fa678f3e2a0> <object object at 0x7fa678f3e2a0>
# <__main__.Another object at 0x1cda850>
-
+
def virtual_root():
""" Return the virtual root related to a request and the
current context"""
@@ -837,9 +856,9 @@ class IPEP302Loader(Interface):
def get_code(fullname):
""" Return the code object for the module identified by 'fullname'.
-
+
Return 'None' if it's a built-in or extension module.
-
+
If the loader doesn't have the code object but it does have the source
code, return the compiled source code.
@@ -848,16 +867,16 @@ class IPEP302Loader(Interface):
def get_source(fullname):
""" Return the source code for the module identified by 'fullname'.
-
+
Return a string, using newline characters for line endings, or None
if the source is not available.
-
+
Raise ImportError if the module can't be found by the importer at all.
"""
def get_filename(fullname):
""" Return the value of '__file__' if the named module was loaded.
-
+
If the module is not found, raise ImportError.
"""
@@ -910,7 +929,13 @@ class ISession(IDict):
``invalidate`` is implementation-dependent, but it should have
the effect of completely dissociating any data stored in the
session with the current request. It might set response
- values (such as one which clears a cookie), or it might not."""
+ values (such as one which clears a cookie), or it might not.
+
+ An invalidated session may be used after the call to ``invalidate``
+ with the effect that a new session is created to store the data. This
+ enables workflows requiring an entirely new session, such as in the
+ case of changing privilege levels or preventing fixation attacks.
+ """
def changed():
""" Mark the session as changed. A user of a session should
@@ -1158,6 +1183,56 @@ class IJSONAdapter(Interface):
class IPredicateList(Interface):
""" Interface representing a predicate list """
+class ICacheBuster(Interface):
+ """
+ Instances of ``ICacheBuster`` may be provided as arguments to
+ :meth:`~pyramid.config.Configurator.add_static_view`. Instances of
+ ``ICacheBuster`` provide mechanisms for generating a cache bust token for
+ a static asset, modifying a static asset URL to include a cache bust token,
+ and, optionally, unmodifying a static asset URL in order to look up an
+ asset. See :ref:`cache_busting`.
+
+ .. versionadded:: 1.6
+ """
+ def token(pathspec):
+ """
+ Computes and returns a token string used for cache busting.
+ ``pathspec`` is the path specification for the resource to be cache
+ busted. """
+
+ def pregenerate(token, subpath, kw):
+ """
+ Modifies a subpath and/or keyword arguments from which a static asset
+ URL will be computed during URL generation. The ``token`` argument is
+ a token string computed by
+ :meth:`~pyramid.interfaces.ICacheBuster.token` for a particular asset.
+ The ``subpath`` argument is a tuple of path elements that represent the
+ portion of the asset URL which is used to find the asset. The ``kw``
+ argument is a dict of keywords that are to be passed eventually to
+ :meth:`~pyramid.request.Request.route_url` for URL generation. The
+ return value should be a two-tuple of ``(subpath, kw)`` which are
+ versions of the same arguments modified to include the cachebust token
+ in the generated URL.
+ """
+
+ def match(subpath):
+ """
+ Performs the logical inverse of
+ :meth:`~pyramid.interfaces.ICacheBuster.pregenerate` by taking a
+ subpath from a cache busted URL and removing the cache bust token, so
+ that :app:`Pyramid` can find the underlying asset.
+
+ ``subpath`` is the subpath portion of the URL for an incoming request
+ for a static asset. The return value should be the same tuple with the
+ cache busting token elided.
+
+ If the cache busting scheme in use doesn't specifically modify the path
+ portion of the generated URL (e.g. it adds a query string), a method
+ which implements this interface may not be necessary. It is
+ permissible for an instance of
+ :class:`~pyramid.interfaces.ICacheBuster` to omit this method.
+ """
+
# configuration phases: a lower phase number means the actions associated
# with this phase will be executed earlier than those with later phase
# numbers. The default phase number is 0, FTR.
diff --git a/pyramid/registry.py b/pyramid/registry.py
index 606251a8d..8c05940b9 100644
--- a/pyramid/registry.py
+++ b/pyramid/registry.py
@@ -5,6 +5,7 @@ from zope.interface import implementer
from zope.interface.registry import Components
from pyramid.compat import text_
+from pyramid.decorator import reify
from pyramid.interfaces import (
ISettings,
@@ -42,6 +43,10 @@ class Registry(Components, dict):
# defeat bool determination via dict.__len__
return True
+ @reify
+ def package_name(self):
+ return self.__name__
+
def registerSubscriptionAdapter(self, *arg, **kw):
result = Components.registerSubscriptionAdapter(self, *arg, **kw)
self.has_listeners = True
diff --git a/pyramid/renderers.py b/pyramid/renderers.py
index 2addaa5fe..d840cc317 100644
--- a/pyramid/renderers.py
+++ b/pyramid/renderers.py
@@ -10,7 +10,6 @@ from zope.interface.registry import Components
from pyramid.interfaces import (
IJSONAdapter,
IRendererFactory,
- IResponseFactory,
IRendererInfo,
)
@@ -25,7 +24,7 @@ from pyramid.events import BeforeRender
from pyramid.path import caller_package
-from pyramid.response import Response
+from pyramid.response import Response, _get_response_factory
from pyramid.threadlocal import get_current_registry
# API
@@ -248,7 +247,7 @@ class JSON(object):
When you've done this, the JSON renderer will be able to serialize
instances of the ``Foo`` class when they're encountered in your view
results."""
-
+
self.components.registerAdapter(adapter, (type_or_iface,),
IJSONAdapter)
@@ -265,7 +264,7 @@ class JSON(object):
response.content_type = 'application/json'
default = self._make_default(request)
return self.serializer(value, default=default, **self.kw)
-
+
return _render
def _make_default(self, request):
@@ -286,7 +285,7 @@ json_renderer_factory = JSON() # bw compat
class JSONP(JSON):
""" `JSONP <http://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper
which implements a hybrid json/jsonp renderer. JSONP is useful for
- making cross-domain AJAX requests.
+ making cross-domain AJAX requests.
Configure a JSONP renderer using the
:meth:`pyramid.config.Configurator.add_renderer` API at application
@@ -309,7 +308,7 @@ class JSONP(JSON):
config = Configurator()
config.add_renderer('jsonp', JSONP(param_name='callback', indent=4))
-
+
.. versionchanged:: 1.4
The ability of this class to accept a ``**kw`` in its constructor.
@@ -448,10 +447,8 @@ class RendererHelper(object):
if response is None:
# request is None or request is not a pyramid.response.Response
registry = self.registry
- response_factory = registry.queryUtility(IResponseFactory,
- default=Response)
-
- response = response_factory()
+ response_factory = _get_response_factory(registry)
+ response = response_factory(request)
if result is not None:
if isinstance(result, text_type):
@@ -491,18 +488,18 @@ class NullRendererHelper(RendererHelper):
@property
def settings(self):
- return get_current_registry().settings or {}
+ return {}
def render_view(self, request, value, view, context):
return value
def render(self, value, system_values, request=None):
return value
-
+
def render_to_response(self, value, system_values, request=None):
return value
def clone(self, name=None, package=None, registry=None):
return self
-
+
null_renderer = NullRendererHelper()
diff --git a/pyramid/request.py b/pyramid/request.py
index f8514066e..b2e2efe05 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -1,3 +1,4 @@
+from collections import deque
import json
from zope.interface import implementer
@@ -9,7 +10,6 @@ from pyramid.interfaces import (
IRequest,
IResponse,
ISessionFactory,
- IResponseFactory,
)
from pyramid.compat import (
@@ -20,7 +20,7 @@ from pyramid.compat import (
from pyramid.decorator import reify
from pyramid.i18n import LocalizerRequestMixin
-from pyramid.response import Response
+from pyramid.response import Response, _get_response_factory
from pyramid.security import (
AuthenticationAPIMixin,
AuthorizationAPIMixin,
@@ -32,8 +32,8 @@ class TemplateContext(object):
pass
class CallbackMethodsMixin(object):
- response_callbacks = ()
- finished_callbacks = ()
+ response_callbacks = None
+ finished_callbacks = None
def add_response_callback(self, callback):
"""
Add a callback to the set of callbacks to be called by the
@@ -59,8 +59,8 @@ class CallbackMethodsMixin(object):
called if an exception happens in application code, or if the
response object returned by :term:`view` code is invalid.
- All response callbacks are called *after* the
- :class:`pyramid.events.NewResponse` event is sent.
+ All response callbacks are called *after* the tweens and
+ *before* the :class:`pyramid.events.NewResponse` event is sent.
Errors raised by callbacks are not handled specially. They
will be propagated to the caller of the :app:`Pyramid`
@@ -72,15 +72,15 @@ class CallbackMethodsMixin(object):
"""
callbacks = self.response_callbacks
- if not callbacks:
- callbacks = []
+ if callbacks is None:
+ callbacks = deque()
callbacks.append(callback)
self.response_callbacks = callbacks
def _process_response_callbacks(self, response):
callbacks = self.response_callbacks
while callbacks:
- callback = callbacks.pop(0)
+ callback = callbacks.popleft()
callback(self, response)
def add_finished_callback(self, callback):
@@ -132,15 +132,15 @@ class CallbackMethodsMixin(object):
"""
callbacks = self.finished_callbacks
- if not callbacks:
- callbacks = []
+ if callbacks is None:
+ callbacks = deque()
callbacks.append(callback)
self.finished_callbacks = callbacks
def _process_finished_callbacks(self):
callbacks = self.finished_callbacks
while callbacks:
- callback = callbacks.pop(0)
+ callback = callbacks.popleft()
callback(self)
@implementer(IRequest)
@@ -213,10 +213,8 @@ class Request(
right" attributes (e.g. by calling ``request.response.set_cookie()``)
within a view that uses a renderer. Mutations to this response object
will be preserved in the response sent to the client."""
- registry = self.registry
- response_factory = registry.queryUtility(IResponseFactory,
- default=Response)
- return response_factory()
+ response_factory = _get_response_factory(self.registry)
+ return response_factory(self)
def is_response(self, ob):
""" Return ``True`` if the object passed as ``ob`` is a valid
diff --git a/pyramid/response.py b/pyramid/response.py
index 0f61af472..892e5dfff 100644
--- a/pyramid/response.py
+++ b/pyramid/response.py
@@ -8,7 +8,8 @@ import venusian
from webob import Response as _Response
from zope.interface import implementer
-from pyramid.interfaces import IResponse
+from pyramid.interfaces import IResponse, IResponseFactory
+
def init_mimetypes(mimetypes):
# this is a function so it can be unittested
@@ -52,15 +53,23 @@ class FileResponse(Response):
"""
def __init__(self, path, request=None, cache_max_age=None,
content_type=None, content_encoding=None):
- super(FileResponse, self).__init__(conditional_response=True)
- self.last_modified = getmtime(path)
- if content_type is None:
- content_type, content_encoding = mimetypes.guess_type(path,
- strict=False)
if content_type is None:
- content_type = 'application/octet-stream'
- self.content_type = content_type
- self.content_encoding = content_encoding
+ content_type, content_encoding = mimetypes.guess_type(
+ path,
+ strict=False
+ )
+ if content_type is None:
+ content_type = 'application/octet-stream'
+ # str-ifying content_type is a workaround for a bug in Python 2.7.7
+ # on Windows where mimetypes.guess_type returns unicode for the
+ # content_type.
+ content_type = str(content_type)
+ super(FileResponse, self).__init__(
+ conditional_response=True,
+ content_type=content_type,
+ content_encoding=content_encoding
+ )
+ self.last_modified = getmtime(path)
content_length = getsize(path)
f = open(path, 'rb')
app_iter = None
@@ -135,7 +144,7 @@ class response_adapter(object):
@response_adapter(dict, list)
def myadapter(ob):
return Response(json.dumps(ob))
-
+
This method will have no effect until a :term:`scan` is performed
agains the package or module which contains it, ala:
@@ -159,3 +168,15 @@ class response_adapter(object):
def __call__(self, wrapped):
self.venusian.attach(wrapped, self.register, category='pyramid')
return wrapped
+
+
+def _get_response_factory(registry):
+ """ Obtain a :class: `pyramid.response.Response` using the
+ `pyramid.interfaces.IResponseFactory`.
+ """
+ response_factory = registry.queryUtility(
+ IResponseFactory,
+ default=lambda r: Response()
+ )
+
+ return response_factory
diff --git a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl
index 76451f9b5..3f1d23d47 100644
--- a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl
+++ b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl
@@ -8,12 +8,12 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="${request.static_url('{{package}}:static/pyramid-16x16.png')}">
- <title>Starter Template for The Pyramid Web Framework</title>
+ <title>Alchemy Scaffold for The Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
- <!-- Custom styles for this template -->
+ <!-- Custom styles for this scaffold -->
<link href="${request.static_url('{{package}}:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -33,24 +33,25 @@
</div>
<div class="col-md-10">
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">starter template</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework {{pyramid_version}}</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li class="current-version">Currently v1.5</li>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org">Docs</a></li>
+ <li class="current-version">Generated by v{{pyramid_version}}</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ </ul>
</div>
</div>
<div class="row">
<div class="copyright">
- Copyright © Pylons Project
+ Copyright &copy; Pylons Project
</div>
</div>
</div>
diff --git a/pyramid/scaffolds/alchemy/+package+/views.py_tmpl b/pyramid/scaffolds/alchemy/+package+/views.py_tmpl
index be0b45b0d..292bce579 100644
--- a/pyramid/scaffolds/alchemy/+package+/views.py_tmpl
+++ b/pyramid/scaffolds/alchemy/+package+/views.py_tmpl
@@ -17,12 +17,13 @@ def my_view(request):
return Response(conn_err_msg, content_type='text/plain', status_int=500)
return {'one': one, 'project': '{{project}}'}
+
conn_err_msg = """\
Pyramid is having a problem using your SQL database. The problem
might be caused by one of the following things:
1. You may need to run the "initialize_{{project}}_db" script
- to initialize your database tables. Check your virtual
+ to initialize your database tables. Check your virtual
environment's "bin" directory for this script and try to run it.
2. Your database server may not be running. Check that the
diff --git a/pyramid/scaffolds/alchemy/development.ini_tmpl b/pyramid/scaffolds/alchemy/development.ini_tmpl
index bdf08171c..448803c8f 100644
--- a/pyramid/scaffolds/alchemy/development.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/development.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -32,7 +32,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/alchemy/production.ini_tmpl b/pyramid/scaffolds/alchemy/production.ini_tmpl
index 69b08e458..022bc0b7b 100644
--- a/pyramid/scaffolds/alchemy/production.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/production.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -23,7 +23,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
@@ -59,4 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl
index 76451f9b5..87fae3817 100644
--- a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl
+++ b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl
@@ -8,12 +8,12 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="${request.static_url('{{package}}:static/pyramid-16x16.png')}">
- <title>Starter Template for The Pyramid Web Framework</title>
+ <title>Starter Scaffold for The Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
- <!-- Custom styles for this template -->
+ <!-- Custom styles for this scaffold -->
<link href="${request.static_url('{{package}}:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -33,24 +33,25 @@
</div>
<div class="col-md-10">
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">starter template</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework {{pyramid_version}}</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li class="current-version">Currently v1.5</li>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org">Docs</a></li>
+ <li class="current-version">Generated by v{{pyramid_version}}</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ </ul>
</div>
</div>
<div class="row">
<div class="copyright">
- Copyright © Pylons Project
+ Copyright &copy; Pylons Project
</div>
</div>
</div>
diff --git a/pyramid/scaffolds/starter/development.ini_tmpl b/pyramid/scaffolds/starter/development.ini_tmpl
index 33c454086..c2a28e178 100644
--- a/pyramid/scaffolds/starter/development.ini_tmpl
+++ b/pyramid/scaffolds/starter/development.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -11,7 +11,7 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
+pyramid.includes =
pyramid_debugtoolbar
# By default, the toolbar only appears for clients from IP addresses
@@ -29,7 +29,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/starter/production.ini_tmpl b/pyramid/scaffolds/starter/production.ini_tmpl
index dd2637e5b..b2681c71d 100644
--- a/pyramid/scaffolds/starter/production.ini_tmpl
+++ b/pyramid/scaffolds/starter/production.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -23,7 +23,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
@@ -51,4 +51,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl
index 76451f9b5..e109ab829 100644
--- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl
+++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl
@@ -8,12 +8,12 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="${request.static_url('{{package}}:static/pyramid-16x16.png')}">
- <title>Starter Template for The Pyramid Web Framework</title>
+ <title>ZODB Scaffold for The Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
- <!-- Custom styles for this template -->
+ <!-- Custom styles for this scaffold -->
<link href="${request.static_url('{{package}}:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -33,24 +33,25 @@
</div>
<div class="col-md-10">
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">starter template</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework {{pyramid_version}}</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li class="current-version">Currently v1.5</li>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org">Docs</a></li>
+ <li class="current-version">Generated by v{{pyramid_version}}</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ </ul>
</div>
</div>
<div class="row">
<div class="copyright">
- Copyright © Pylons Project
+ Copyright &copy; Pylons Project
</div>
</div>
</div>
diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl
index 746f7ded3..199ddfab4 100644
--- a/pyramid/scaffolds/zodb/development.ini_tmpl
+++ b/pyramid/scaffolds/zodb/development.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -34,7 +34,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
@@ -62,4 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scaffolds/zodb/production.ini_tmpl b/pyramid/scaffolds/zodb/production.ini_tmpl
index 9ce639ec3..522ff7651 100644
--- a/pyramid/scaffolds/zodb/production.ini_tmpl
+++ b/pyramid/scaffolds/zodb/production.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -29,7 +29,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/pyramid/scripts/pcreate.py b/pyramid/scripts/pcreate.py
index 9a3b53b33..edf2c39f7 100644
--- a/pyramid/scripts/pcreate.py
+++ b/pyramid/scripts/pcreate.py
@@ -53,6 +53,8 @@ class PCreateCommand(object):
action='store_true',
help='When a file would be overwritten, interrogate')
+ pyramid_dist = pkg_resources.get_distribution("pyramid")
+
def __init__(self, argv, quiet=False):
self.quiet = quiet
self.options, self.args = self.parser.parse_args(argv[1:])
@@ -79,13 +81,34 @@ class PCreateCommand(object):
args = self.args
output_dir = os.path.abspath(os.path.normpath(args[0]))
project_name = os.path.basename(os.path.split(output_dir)[1])
- pkg_name = _bad_chars_re.sub('', project_name.lower())
+ pkg_name = _bad_chars_re.sub(
+ '', project_name.lower().replace('-', '_'))
safe_name = pkg_resources.safe_name(project_name)
egg_name = pkg_resources.to_filename(safe_name)
+
+ # get pyramid package version
+ pyramid_version = self.pyramid_dist.version
+
+ ## map pyramid package version of the documentation branch ##
+ # if version ends with 'dev' then docs version is 'master'
+ if self.pyramid_dist.version[-3:] == 'dev':
+ pyramid_docs_branch = 'master'
+ else:
+ # if not version is not 'dev' find the version.major_version string
+ # and combine it with '-branch'
+ version_match = re.match(r'(\d+\.\d+)', self.pyramid_dist.version)
+ if version_match is not None:
+ pyramid_docs_branch = "%s-branch" % version_match.group()
+ # if can not parse the version then default to 'latest'
+ else:
+ pyramid_docs_branch = 'latest'
+
vars = {
'project': project_name,
'package': pkg_name,
'egg': egg_name,
+ 'pyramid_version': pyramid_version,
+ 'pyramid_docs_branch': pyramid_docs_branch,
}
for scaffold_name in options.scaffold_name:
for scaffold in self.scaffolds:
diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py
index 2ab3b8bb9..34eeadf32 100644
--- a/pyramid/scripts/prequest.py
+++ b/pyramid/scripts/prequest.py
@@ -5,7 +5,7 @@ import textwrap
from pyramid.compat import url_unquote
from pyramid.request import Request
-from pyramid.paster import get_app
+from pyramid.paster import get_app, setup_logging
from pyramid.scripts.common import parse_vars
def main(argv=sys.argv, quiet=False):
@@ -97,12 +97,18 @@ class PRequestCommand(object):
if not self.quiet:
print(msg)
+ def configure_logging(self, app_spec):
+ setup_logging(app_spec)
+
def run(self):
if not len(self.args) >= 2:
self.out('You must provide at least two arguments')
return 2
app_spec = self.args[0]
path = self.args[1]
+
+ self.configure_logging(app_spec)
+
if not path.startswith('/'):
path = '/' + path
diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py
index 5784026bb..d0c1aa13e 100644
--- a/pyramid/scripts/proutes.py
+++ b/pyramid/scripts/proutes.py
@@ -5,10 +5,15 @@ import textwrap
from pyramid.paster import bootstrap
from pyramid.scripts.common import parse_vars
+
+PAD = 3
+
+
def main(argv=sys.argv, quiet=False):
command = PRoutesCommand(argv, quiet)
return command.run()
+
class PRoutesCommand(object):
description = """\
Print all URL dispatch routes used by a Pyramid application in the
@@ -43,7 +48,7 @@ class PRoutesCommand(object):
def out(self, msg): # pragma: no cover
if not self.quiet:
print(msg)
-
+
def run(self, quiet=False):
if not self.args:
self.out('requires a config file argument')
@@ -52,6 +57,8 @@ class PRoutesCommand(object):
from pyramid.interfaces import IRouteRequest
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IView
+ from pyramid.interfaces import IMultiView
+
from zope.interface import Interface
config_uri = self.args[0]
@@ -59,13 +66,23 @@ class PRoutesCommand(object):
registry = env['registry']
mapper = self._get_mapper(registry)
if mapper is not None:
+ mapped_routes = [('Name', 'Pattern', 'View')]
+
+ max_name = len('Name')
+ max_pattern = len('Pattern')
+ max_view = len('View')
+
routes = mapper.get_routes()
- fmt = '%-15s %-30s %-25s'
+
if not routes:
return 0
- self.out(fmt % ('Name', 'Pattern', 'View'))
- self.out(
- fmt % ('-'*len('Name'), '-'*len('Pattern'), '-'*len('View')))
+
+ mapped_routes.append((
+ '-' * max_name,
+ '-' * max_pattern,
+ '-' * max_view,
+ ))
+
for route in routes:
pattern = route.pattern
if not pattern.startswith('/'):
@@ -73,13 +90,50 @@ class PRoutesCommand(object):
request_iface = registry.queryUtility(IRouteRequest,
name=route.name)
view_callable = None
+
if (request_iface is None) or (route.factory is not None):
- self.out(fmt % (route.name, pattern, '<unknown>'))
+ view_callable = '<unknown>'
else:
view_callable = registry.adapters.lookup(
(IViewClassifier, request_iface, Interface),
IView, name='', default=None)
- self.out(fmt % (route.name, pattern, view_callable))
+
+ if view_callable is not None:
+ if IMultiView.providedBy(view_callable):
+ view_callables = [
+ x[1] for x in view_callable.views
+ ]
+ else:
+ view_callables = [view_callable]
+
+ for view_func in view_callables:
+ view_callable = '%s.%s' % (
+ view_func.__module__,
+ view_func.__name__,
+ )
+ else:
+ view_callable = str(None)
+
+ if len(route.name) > max_name:
+ max_name = len(route.name)
+
+ if len(pattern) > max_pattern:
+ max_pattern = len(pattern)
+
+ if len(view_callable) > max_view:
+ max_view = len(view_callable)
+
+ mapped_routes.append((route.name, pattern, view_callable))
+
+ fmt = '%-{0}s %-{1}s %-{2}s'.format(
+ max_name + PAD,
+ max_pattern + PAD,
+ max_view + PAD,
+ )
+
+ for route_data in mapped_routes:
+ self.out(fmt % route_data)
+
return 0
if __name__ == '__main__': # pragma: no cover
diff --git a/pyramid/scripts/pshell.py b/pyramid/scripts/pshell.py
index 12b078677..1168ba78a 100644
--- a/pyramid/scripts/pshell.py
+++ b/pyramid/scripts/pshell.py
@@ -1,9 +1,11 @@
from code import interact
import optparse
+import os
import sys
import textwrap
from pyramid.compat import configparser
+from pyramid.compat import exec_
from pyramid.util import DottedNameResolver
from pyramid.paster import bootstrap
@@ -51,6 +53,7 @@ class PShellCommand(object):
loaded_objects = {}
object_help = {}
setup = None
+ pystartup = os.environ.get('PYTHONSTARTUP')
def __init__(self, argv, quiet=False):
self.quiet = quiet
@@ -144,6 +147,12 @@ class PShellCommand(object):
if shell is None:
shell = self.make_shell()
+ if self.pystartup and os.path.isfile(self.pystartup):
+ with open(self.pystartup, 'rb') as fp:
+ exec_(fp.read().decode('utf-8'), env)
+ if '__builtins__' in env:
+ del env['__builtins__']
+
try:
shell(env, help)
finally:
diff --git a/pyramid/security.py b/pyramid/security.py
index c98d4e6cc..cbb4b895f 100644
--- a/pyramid/security.py
+++ b/pyramid/security.py
@@ -17,6 +17,8 @@ Authenticated = 'system.Authenticated'
Allow = 'Allow'
Deny = 'Deny'
+_marker = object()
+
class AllPermissionsList(object):
""" Stand in 'permission list' to represent all permissions """
def __iter__(self):
@@ -59,7 +61,7 @@ def has_permission(permission, context, request):
deprecated(
'has_permission',
'As of Pyramid 1.5 the "pyramid.security.has_permission" API is now '
- 'deprecated. It will be removed in Pyramd 1.8. Use the '
+ 'deprecated. It will be removed in Pyramid 1.8. Use the '
'"has_permission" method of the Pyramid request instead.'
)
@@ -77,7 +79,7 @@ def authenticated_userid(request):
deprecated(
'authenticated_userid',
'As of Pyramid 1.5 the "pyramid.security.authenticated_userid" API is now '
- 'deprecated. It will be removed in Pyramd 1.8. Use the '
+ 'deprecated. It will be removed in Pyramid 1.8. Use the '
'"authenticated_userid" attribute of the Pyramid request instead.'
)
@@ -94,7 +96,7 @@ def unauthenticated_userid(request):
deprecated(
'unauthenticated_userid',
'As of Pyramid 1.5 the "pyramid.security.unauthenticated_userid" API is '
- 'now deprecated. It will be removed in Pyramd 1.8. Use the '
+ 'now deprecated. It will be removed in Pyramid 1.8. Use the '
'"unauthenticated_userid" attribute of the Pyramid request instead.'
)
@@ -111,16 +113,16 @@ def effective_principals(request):
deprecated(
'effective_principals',
'As of Pyramid 1.5 the "pyramid.security.effective_principals" API is '
- 'now deprecated. It will be removed in Pyramd 1.8. Use the '
+ 'now deprecated. It will be removed in Pyramid 1.8. Use the '
'"effective_principals" attribute of the Pyramid request instead.'
)
-def remember(request, principal, **kw):
+def remember(request, userid=_marker, **kw):
"""
Returns a sequence of header tuples (e.g. ``[('Set-Cookie', 'foo=abc')]``)
on this request's response.
These headers are suitable for 'remembering' a set of credentials
- implied by the data passed as ``principal`` and ``*kw`` using the
+ implied by the data passed as ``userid`` and ``*kw`` using the
current :term:`authentication policy`. Common usage might look
like so within the body of a view function (``response`` is
assumed to be a :term:`WebOb` -style :term:`response` object
@@ -138,11 +140,28 @@ def remember(request, principal, **kw):
always return an empty sequence. If used, the composition and
meaning of ``**kw`` must be agreed upon by the calling code and
the effective authentication policy.
+
+ .. deprecated:: 1.6
+ Renamed the ``principal`` argument to ``userid`` to clarify its
+ purpose.
"""
+ if userid is _marker:
+ principal = kw.pop('principal', _marker)
+ if principal is _marker:
+ raise TypeError(
+ 'remember() missing 1 required positional argument: '
+ '\'userid\'')
+ else:
+ deprecated(
+ 'principal',
+ 'The "principal" argument was deprecated in Pyramid 1.6. '
+ 'It will be removed in Pyramid 1.9. Use the "userid" '
+ 'argument instead.')
+ userid = principal
policy = _get_authentication_policy(request)
if policy is None:
return []
- return policy.remember(request, principal, **kw)
+ return policy.remember(request, userid, **kw)
def forget(request):
"""
diff --git a/pyramid/static.py b/pyramid/static.py
index 63ca58597..c4a9e3cc4 100644
--- a/pyramid/static.py
+++ b/pyramid/static.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+import hashlib
import os
from os.path import (
@@ -26,7 +27,7 @@ from pyramid.httpexceptions import (
HTTPMovedPermanently,
)
-from pyramid.path import caller_package
+from pyramid.path import AssetResolver, caller_package
from pyramid.response import FileResponse
from pyramid.traversal import traversal_path_info
@@ -58,7 +59,7 @@ class static_view(object):
``cache_max_age`` influences the ``Expires`` and ``Max-Age``
response headers returned by the view (default is 3600 seconds or
- five minutes).
+ one hour).
``use_subpath`` influences whether ``request.subpath`` will be used as
``PATH_INFO`` when calling the underlying WSGI application which actually
@@ -78,7 +79,7 @@ class static_view(object):
"""
def __init__(self, root_dir, cache_max_age=3600, package_name=None,
- use_subpath=False, index='index.html'):
+ use_subpath=False, index='index.html', cachebust_match=None):
# package_name is for bw compat; it is preferred to pass in a
# package-relative path as root_dir
# (e.g. ``anotherpackage:foo/static``).
@@ -91,13 +92,15 @@ class static_view(object):
self.docroot = docroot
self.norm_docroot = normcase(normpath(docroot))
self.index = index
+ self.cachebust_match = cachebust_match
def __call__(self, context, request):
if self.use_subpath:
path_tuple = request.subpath
else:
path_tuple = traversal_path_info(request.environ['PATH_INFO'])
-
+ if self.cachebust_match:
+ path_tuple = self.cachebust_match(path_tuple)
path = _secure_path(path_tuple)
if path is None:
@@ -154,3 +157,97 @@ def _secure_path(path_tuple):
encoded = slash.join(path_tuple) # will be unicode
return encoded
+def _generate_md5(spec):
+ asset = AssetResolver(None).resolve(spec)
+ md5 = hashlib.md5()
+ with asset.stream() as stream:
+ for block in iter(lambda: stream.read(4096), b''):
+ md5.update(block)
+ return md5.hexdigest()
+
+class Md5AssetTokenGenerator(object):
+ """
+ A mixin class which provides an implementation of
+ :meth:`~pyramid.interfaces.ICacheBuster.target` which generates an md5
+ checksum token for an asset, caching it for subsequent calls.
+ """
+ def __init__(self):
+ self.token_cache = {}
+
+ def token(self, pathspec):
+ # An astute observer will notice that this use of token_cache doesn't
+ # look particularly thread safe. Basic read/write operations on Python
+ # dicts, however, are atomic, so simply accessing and writing values
+ # to the dict shouldn't cause a segfault or other catastrophic failure.
+ # (See: http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm)
+ #
+ # We do have a race condition that could result in the same md5
+ # checksum getting computed twice or more times in parallel. Since
+ # the program would still function just fine if this were to occur,
+ # the extra overhead of using locks to serialize access to the dict
+ # seems an unnecessary burden.
+ #
+ token = self.token_cache.get(pathspec)
+ if not token:
+ self.token_cache[pathspec] = token = _generate_md5(pathspec)
+ return token
+
+class PathSegmentMd5CacheBuster(Md5AssetTokenGenerator):
+ """
+ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which
+ inserts an md5 checksum token for cache busting in the path portion of an
+ asset URL. Generated md5 checksums are cached in order to speed up
+ subsequent calls.
+
+ .. versionadded:: 1.6
+ """
+ def pregenerate(self, token, subpath, kw):
+ return (token,) + subpath, kw
+
+ def match(self, subpath):
+ return subpath[1:]
+
+class QueryStringMd5CacheBuster(Md5AssetTokenGenerator):
+ """
+ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds
+ an md5 checksum token for cache busting in the query string of an asset
+ URL. Generated md5 checksums are cached in order to speed up subsequent
+ calls.
+
+ The optional ``param`` argument determines the name of the parameter added
+ to the query string and defaults to ``'x'``.
+
+ .. versionadded:: 1.6
+ """
+ def __init__(self, param='x'):
+ super(QueryStringMd5CacheBuster, self).__init__()
+ self.param = param
+
+ def pregenerate(self, token, subpath, kw):
+ query = kw.setdefault('_query', {})
+ if isinstance(query, dict):
+ query[self.param] = token
+ else:
+ kw['_query'] = tuple(query) + ((self.param, token),)
+ return subpath, kw
+
+class QueryStringConstantCacheBuster(QueryStringMd5CacheBuster):
+ """
+ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds
+ an arbitrary token for cache busting in the query string of an asset URL.
+
+ The ``token`` parameter is the token string to use for cache busting and
+ will be the same for every request.
+
+ The optional ``param`` argument determines the name of the parameter added
+ to the query string and defaults to ``'x'``.
+
+ .. versionadded:: 1.6
+ """
+ def __init__(self, token, param='x'):
+ self._token = token
+ self.param = param
+
+ def token(self, pathspec):
+ return self._token
+
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 91dc41dd5..667e6af4e 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -9,7 +9,6 @@ from zope.interface import (
from pyramid.interfaces import (
IRequest,
- IResponseFactory,
ISession,
)
@@ -21,7 +20,8 @@ from pyramid.compat import (
from pyramid.config import Configurator
from pyramid.decorator import reify
-from pyramid.response import Response
+from pyramid.path import caller_package
+from pyramid.response import Response, _get_response_factory
from pyramid.registry import Registry
from pyramid.security import (
@@ -41,6 +41,7 @@ from pyramid.request import CallbackMethodsMixin
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
+
_marker = object()
class DummyRootFactory(object):
@@ -78,8 +79,8 @@ class DummySecurityPolicy(object):
effective_principals.extend(self.groupids)
return effective_principals
- def remember(self, request, principal, **kw):
- self.remembered = principal
+ def remember(self, request, userid, **kw):
+ self.remembered = userid
return self.remember_result
def forget(self, request):
@@ -382,13 +383,13 @@ class DummyRequest(
@reify
def response(self):
- f = self.registry.queryUtility(IResponseFactory, default=Response)
- return f()
+ f = _get_response_factory(self.registry)
+ return f(self)
have_zca = True
def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
- settings=None):
+ settings=None, package=None):
"""
Set :app:`Pyramid` registry and request thread locals for the
duration of a single unit test.
@@ -432,9 +433,15 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
:mod:`zope.component` package cannot be imported, or if
``hook_zca`` is ``False``, the hook will not be set.
- If ``settings`` is not None, it must be a dictionary representing the
+ If ``settings`` is not ``None``, it must be a dictionary representing the
values passed to a Configurator as its ``settings=`` argument.
+ If ``package`` is ``None`` it will be set to the caller's package. The
+ ``package`` setting in the :class:`pyramid.config.Configurator` will
+ affect any relative imports made via
+ :meth:`pyramid.config.Configurator.include` or
+ :meth:`pyramid.config.Configurator.maybe_dotted`.
+
This function returns an instance of the
:class:`pyramid.config.Configurator` class, which can be
used for further configuration to set up an environment suitable
@@ -447,7 +454,10 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
manager.clear()
if registry is None:
registry = Registry('testing')
- config = Configurator(registry=registry, autocommit=autocommit)
+ if package is None:
+ package = caller_package()
+ config = Configurator(registry=registry, autocommit=autocommit,
+ package=package)
if settings is None:
settings = {}
if getattr(registry, 'settings', None) is None:
@@ -505,6 +515,10 @@ def tearDown(unhook_zca=True):
def cleanUp(*arg, **kw):
""" An alias for :func:`pyramid.testing.setUp`. """
+ package = kw.get('package', None)
+ if package is None:
+ package = caller_package()
+ kw['package'] = package
return setUp(*arg, **kw)
class DummyRendererFactory(object):
diff --git a/pyramid/tests/fixtures/minimal.jpg b/pyramid/tests/fixtures/minimal.jpg
new file mode 100644
index 000000000..1cda9a53d
--- /dev/null
+++ b/pyramid/tests/fixtures/minimal.jpg
Binary files differ
diff --git a/pyramid/tests/fixtures/minimal.pdf b/pyramid/tests/fixtures/minimal.pdf
new file mode 100755
index 000000000..e267be996
--- /dev/null
+++ b/pyramid/tests/fixtures/minimal.pdf
Binary files differ
diff --git a/pyramid/tests/fixtures/minimal.xml b/pyramid/tests/fixtures/minimal.xml
new file mode 100644
index 000000000..1972c155d
--- /dev/null
+++ b/pyramid/tests/fixtures/minimal.xml
@@ -0,0 +1 @@
+<hello/> \ No newline at end of file
diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py
index e25e9faa1..c7fc1c211 100644
--- a/pyramid/tests/test_authentication.py
+++ b/pyramid/tests/test_authentication.py
@@ -1211,26 +1211,26 @@ class Test_parse_ticket(unittest.TestCase):
self._assertRaisesBadTicket('secret', ticket, '0.0.0.0')
def test_correct_with_user_data(self):
- ticket = '66f9cc3e423dc57c91df696cf3d1f0d80000000auserid!a,b!'
+ ticket = text_('66f9cc3e423dc57c91df696cf3d1f0d80000000auserid!a,b!')
result = self._callFUT('secret', ticket, '0.0.0.0')
self.assertEqual(result, (10, 'userid', ['a', 'b'], ''))
def test_correct_with_user_data_sha512(self):
- ticket = '7d947cdef99bad55f8e3382a8bd089bb9dd0547f7925b7d189adc1160cab'\
- '0ec0e6888faa41eba641a18522b26f19109f3ffafb769767ba8a26d02aae'\
- 'ae56599a0000000auserid!a,b!'
+ ticket = text_('7d947cdef99bad55f8e3382a8bd089bb9dd0547f7925b7d189adc1'
+ '160cab0ec0e6888faa41eba641a18522b26f19109f3ffafb769767'
+ 'ba8a26d02aaeae56599a0000000auserid!a,b!')
result = self._callFUT('secret', ticket, '0.0.0.0', 'sha512')
self.assertEqual(result, (10, 'userid', ['a', 'b'], ''))
def test_ipv4(self):
- ticket = 'b3e7156db4f8abde4439c4a6499a0668f9e7ffd7fa27b798400ecdade8d7'\
- '6c530000000auserid!'
+ ticket = text_('b3e7156db4f8abde4439c4a6499a0668f9e7ffd7fa27b798400ecd'
+ 'ade8d76c530000000auserid!')
result = self._callFUT('secret', ticket, '198.51.100.1', 'sha256')
self.assertEqual(result, (10, 'userid', [''], ''))
def test_ipv6(self):
- ticket = 'd025b601a0f12ca6d008aa35ff3a22b7d8f3d1c1456c85becf8760cd7a2f'\
- 'a4910000000auserid!'
+ ticket = text_('d025b601a0f12ca6d008aa35ff3a22b7d8f3d1c1456c85becf8760'
+ 'cd7a2fa4910000000auserid!')
result = self._callFUT('secret', ticket, '2001:db8::1', 'sha256')
self.assertEqual(result, (10, 'userid', [''], ''))
pass
diff --git a/pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt b/pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt
diff --git a/pyramid/tests/test_config/test_assets.py b/pyramid/tests/test_config/test_assets.py
index 345e7f8d6..b605a602d 100644
--- a/pyramid/tests/test_config/test_assets.py
+++ b/pyramid/tests/test_config/test_assets.py
@@ -1,6 +1,10 @@
+import os.path
import unittest
from pyramid.testing import cleanUp
+# we use this folder
+here = os.path.dirname(os.path.abspath(__file__))
+
class TestAssetsConfiguratorMixin(unittest.TestCase):
def _makeOne(self, *arg, **kw):
from pyramid.config import Configurator
@@ -10,27 +14,31 @@ class TestAssetsConfiguratorMixin(unittest.TestCase):
def test_override_asset_samename(self):
from pyramid.exceptions import ConfigurationError
config = self._makeOne()
- self.assertRaises(ConfigurationError, config.override_asset,'a', 'a')
+ self.assertRaises(ConfigurationError, config.override_asset, 'a', 'a')
def test_override_asset_directory_with_file(self):
from pyramid.exceptions import ConfigurationError
config = self._makeOne()
self.assertRaises(ConfigurationError, config.override_asset,
- 'a:foo/', 'a:foo.pt')
+ 'a:foo/',
+ 'pyramid.tests.test_config.pkgs.asset:foo.pt')
def test_override_asset_file_with_directory(self):
from pyramid.exceptions import ConfigurationError
config = self._makeOne()
self.assertRaises(ConfigurationError, config.override_asset,
- 'a:foo.pt', 'a:foo/')
+ 'a:foo.pt',
+ 'pyramid.tests.test_config.pkgs.asset:templates/')
def test_override_asset_file_with_package(self):
from pyramid.exceptions import ConfigurationError
config = self._makeOne()
self.assertRaises(ConfigurationError, config.override_asset,
- 'a:foo.pt', 'a')
+ 'a:foo.pt',
+ 'pyramid.tests.test_config.pkgs.asset')
def test_override_asset_file_with_file(self):
+ from pyramid.config.assets import PackageAssetSource
config = self._makeOne(autocommit=True)
override = DummyUnderOverride()
config.override_asset(
@@ -41,10 +49,13 @@ class TestAssetsConfiguratorMixin(unittest.TestCase):
from pyramid.tests.test_config.pkgs.asset import subpackage
self.assertEqual(override.package, asset)
self.assertEqual(override.path, 'templates/foo.pt')
- self.assertEqual(override.override_package, subpackage)
- self.assertEqual(override.override_prefix, 'templates/bar.pt')
+ source = override.source
+ self.assertTrue(isinstance(source, PackageAssetSource))
+ self.assertEqual(source.package, subpackage)
+ self.assertEqual(source.prefix, 'templates/bar.pt')
def test_override_asset_package_with_package(self):
+ from pyramid.config.assets import PackageAssetSource
config = self._makeOne(autocommit=True)
override = DummyUnderOverride()
config.override_asset(
@@ -55,10 +66,13 @@ class TestAssetsConfiguratorMixin(unittest.TestCase):
from pyramid.tests.test_config.pkgs.asset import subpackage
self.assertEqual(override.package, asset)
self.assertEqual(override.path, '')
- self.assertEqual(override.override_package, subpackage)
- self.assertEqual(override.override_prefix, '')
+ source = override.source
+ self.assertTrue(isinstance(source, PackageAssetSource))
+ self.assertEqual(source.package, subpackage)
+ self.assertEqual(source.prefix, '')
def test_override_asset_directory_with_directory(self):
+ from pyramid.config.assets import PackageAssetSource
config = self._makeOne(autocommit=True)
override = DummyUnderOverride()
config.override_asset(
@@ -69,10 +83,13 @@ class TestAssetsConfiguratorMixin(unittest.TestCase):
from pyramid.tests.test_config.pkgs.asset import subpackage
self.assertEqual(override.package, asset)
self.assertEqual(override.path, 'templates/')
- self.assertEqual(override.override_package, subpackage)
- self.assertEqual(override.override_prefix, 'templates/')
+ source = override.source
+ self.assertTrue(isinstance(source, PackageAssetSource))
+ self.assertEqual(source.package, subpackage)
+ self.assertEqual(source.prefix, 'templates/')
def test_override_asset_directory_with_package(self):
+ from pyramid.config.assets import PackageAssetSource
config = self._makeOne(autocommit=True)
override = DummyUnderOverride()
config.override_asset(
@@ -83,10 +100,13 @@ class TestAssetsConfiguratorMixin(unittest.TestCase):
from pyramid.tests.test_config.pkgs.asset import subpackage
self.assertEqual(override.package, asset)
self.assertEqual(override.path, 'templates/')
- self.assertEqual(override.override_package, subpackage)
- self.assertEqual(override.override_prefix, '')
+ source = override.source
+ self.assertTrue(isinstance(source, PackageAssetSource))
+ self.assertEqual(source.package, subpackage)
+ self.assertEqual(source.prefix, '')
def test_override_asset_package_with_directory(self):
+ from pyramid.config.assets import PackageAssetSource
config = self._makeOne(autocommit=True)
override = DummyUnderOverride()
config.override_asset(
@@ -97,32 +117,105 @@ class TestAssetsConfiguratorMixin(unittest.TestCase):
from pyramid.tests.test_config.pkgs.asset import subpackage
self.assertEqual(override.package, asset)
self.assertEqual(override.path, '')
- self.assertEqual(override.override_package, subpackage)
- self.assertEqual(override.override_prefix, 'templates/')
+ source = override.source
+ self.assertTrue(isinstance(source, PackageAssetSource))
+ self.assertEqual(source.package, subpackage)
+ self.assertEqual(source.prefix, 'templates/')
+
+ def test_override_asset_directory_with_absfile(self):
+ from pyramid.exceptions import ConfigurationError
+ config = self._makeOne()
+ self.assertRaises(ConfigurationError, config.override_asset,
+ 'a:foo/',
+ os.path.join(here, 'pkgs', 'asset', 'foo.pt'))
+
+ def test_override_asset_file_with_absdirectory(self):
+ from pyramid.exceptions import ConfigurationError
+ config = self._makeOne()
+ abspath = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates')
+ self.assertRaises(ConfigurationError, config.override_asset,
+ 'a:foo.pt',
+ abspath)
+
+ def test_override_asset_file_with_missing_abspath(self):
+ from pyramid.exceptions import ConfigurationError
+ config = self._makeOne()
+ self.assertRaises(ConfigurationError, config.override_asset,
+ 'a:foo.pt',
+ os.path.join(here, 'wont_exist'))
+
+ def test_override_asset_file_with_absfile(self):
+ from pyramid.config.assets import FSAssetSource
+ config = self._makeOne(autocommit=True)
+ override = DummyUnderOverride()
+ abspath = os.path.join(here, 'pkgs', 'asset', 'subpackage',
+ 'templates', 'bar.pt')
+ config.override_asset(
+ 'pyramid.tests.test_config.pkgs.asset:templates/foo.pt',
+ abspath,
+ _override=override)
+ from pyramid.tests.test_config.pkgs import asset
+ self.assertEqual(override.package, asset)
+ self.assertEqual(override.path, 'templates/foo.pt')
+ source = override.source
+ self.assertTrue(isinstance(source, FSAssetSource))
+ self.assertEqual(source.prefix, abspath)
+
+ def test_override_asset_directory_with_absdirectory(self):
+ from pyramid.config.assets import FSAssetSource
+ config = self._makeOne(autocommit=True)
+ override = DummyUnderOverride()
+ abspath = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates')
+ config.override_asset(
+ 'pyramid.tests.test_config.pkgs.asset:templates/',
+ abspath,
+ _override=override)
+ from pyramid.tests.test_config.pkgs import asset
+ self.assertEqual(override.package, asset)
+ self.assertEqual(override.path, 'templates/')
+ source = override.source
+ self.assertTrue(isinstance(source, FSAssetSource))
+ self.assertEqual(source.prefix, abspath)
+
+ def test_override_asset_package_with_absdirectory(self):
+ from pyramid.config.assets import FSAssetSource
+ config = self._makeOne(autocommit=True)
+ override = DummyUnderOverride()
+ abspath = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates')
+ config.override_asset(
+ 'pyramid.tests.test_config.pkgs.asset',
+ abspath,
+ _override=override)
+ from pyramid.tests.test_config.pkgs import asset
+ self.assertEqual(override.package, asset)
+ self.assertEqual(override.path, '')
+ source = override.source
+ self.assertTrue(isinstance(source, FSAssetSource))
+ self.assertEqual(source.prefix, abspath)
def test__override_not_yet_registered(self):
from pyramid.interfaces import IPackageOverrides
package = DummyPackage('package')
- opackage = DummyPackage('opackage')
+ source = DummyAssetSource()
config = self._makeOne()
- config._override(package, 'path', opackage, 'oprefix',
+ config._override(package, 'path', source,
PackageOverrides=DummyPackageOverrides)
overrides = config.registry.queryUtility(IPackageOverrides,
name='package')
- self.assertEqual(overrides.inserted, [('path', 'opackage', 'oprefix')])
+ self.assertEqual(overrides.inserted, [('path', source)])
self.assertEqual(overrides.package, package)
def test__override_already_registered(self):
from pyramid.interfaces import IPackageOverrides
package = DummyPackage('package')
- opackage = DummyPackage('opackage')
+ source = DummyAssetSource()
overrides = DummyPackageOverrides(package)
config = self._makeOne()
config.registry.registerUtility(overrides, IPackageOverrides,
name='package')
- config._override(package, 'path', opackage, 'oprefix',
+ config._override(package, 'path', source,
PackageOverrides=DummyPackageOverrides)
- self.assertEqual(overrides.inserted, [('path', 'opackage', 'oprefix')])
+ self.assertEqual(overrides.inserted, [('path', source)])
self.assertEqual(overrides.package, package)
@@ -148,30 +241,24 @@ class TestOverrideProvider(unittest.TestCase):
reg.registerUtility(overrides, IPackageOverrides, name=name)
def test_get_resource_filename_no_overrides(self):
- import os
resource_name = 'test_assets.py'
import pyramid.tests.test_config
provider = self._makeOne(pyramid.tests.test_config)
- here = os.path.dirname(os.path.abspath(__file__))
expected = os.path.join(here, resource_name)
result = provider.get_resource_filename(None, resource_name)
self.assertEqual(result, expected)
def test_get_resource_stream_no_overrides(self):
- import os
resource_name = 'test_assets.py'
import pyramid.tests.test_config
provider = self._makeOne(pyramid.tests.test_config)
- here = os.path.dirname(os.path.abspath(__file__))
with provider.get_resource_stream(None, resource_name) as result:
_assertBody(result.read(), os.path.join(here, resource_name))
def test_get_resource_string_no_overrides(self):
- import os
resource_name = 'test_assets.py'
import pyramid.tests.test_config
provider = self._makeOne(pyramid.tests.test_config)
- here = os.path.dirname(os.path.abspath(__file__))
result = provider.get_resource_string(None, resource_name)
_assertBody(result, os.path.join(here, resource_name))
@@ -202,11 +289,9 @@ class TestOverrideProvider(unittest.TestCase):
def test_get_resource_filename_override_returns_None(self):
overrides = DummyOverrides(None)
self._registerOverrides(overrides)
- import os
resource_name = 'test_assets.py'
import pyramid.tests.test_config
provider = self._makeOne(pyramid.tests.test_config)
- here = os.path.dirname(os.path.abspath(__file__))
expected = os.path.join(here, resource_name)
result = provider.get_resource_filename(None, resource_name)
self.assertEqual(result, expected)
@@ -214,22 +299,18 @@ class TestOverrideProvider(unittest.TestCase):
def test_get_resource_stream_override_returns_None(self):
overrides = DummyOverrides(None)
self._registerOverrides(overrides)
- import os
resource_name = 'test_assets.py'
import pyramid.tests.test_config
provider = self._makeOne(pyramid.tests.test_config)
- here = os.path.dirname(os.path.abspath(__file__))
with provider.get_resource_stream(None, resource_name) as result:
_assertBody(result.read(), os.path.join(here, resource_name))
def test_get_resource_string_override_returns_None(self):
overrides = DummyOverrides(None)
self._registerOverrides(overrides)
- import os
resource_name = 'test_assets.py'
import pyramid.tests.test_config
provider = self._makeOne(pyramid.tests.test_config)
- here = os.path.dirname(os.path.abspath(__file__))
result = provider.get_resource_string(None, resource_name)
_assertBody(result, os.path.join(here, resource_name))
@@ -378,8 +459,8 @@ class TestPackageOverrides(unittest.TestCase):
from pyramid.config.assets import DirectoryOverride
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= [None]
- po.insert('foo/', 'package', 'bar/')
+ po.overrides = [None]
+ po.insert('foo/', DummyAssetSource())
self.assertEqual(len(po.overrides), 2)
override = po.overrides[0]
self.assertEqual(override.__class__, DirectoryOverride)
@@ -388,8 +469,8 @@ class TestPackageOverrides(unittest.TestCase):
from pyramid.config.assets import FileOverride
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= [None]
- po.insert('foo.pt', 'package', 'bar.pt')
+ po.overrides = [None]
+ po.insert('foo.pt', DummyAssetSource())
self.assertEqual(len(po.overrides), 2)
override = po.overrides[0]
self.assertEqual(override.__class__, FileOverride)
@@ -399,132 +480,137 @@ class TestPackageOverrides(unittest.TestCase):
from pyramid.config.assets import DirectoryOverride
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= [None]
- po.insert('', 'package', 'bar/')
+ po.overrides = [None]
+ source = DummyAssetSource()
+ po.insert('', source)
self.assertEqual(len(po.overrides), 2)
override = po.overrides[0]
self.assertEqual(override.__class__, DirectoryOverride)
- def test_search_path(self):
- overrides = [ DummyOverride(None), DummyOverride(('package', 'name'))]
+ def test_filtered_sources(self):
+ overrides = [ DummyOverride(None), DummyOverride('foo')]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
- self.assertEqual(list(po.search_path('whatever')),
- [('package', 'name')])
+ po.overrides = overrides
+ self.assertEqual(list(po.filtered_sources('whatever')), ['foo'])
def test_get_filename(self):
- import os
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'test_assets.py'))]
+ source = DummyAssetSource(filename='foo.pt')
+ overrides = [ DummyOverride(None), DummyOverride((source, ''))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
- here = os.path.dirname(os.path.abspath(__file__))
- expected = os.path.join(here, 'test_assets.py')
- self.assertEqual(po.get_filename('whatever'), expected)
+ po.overrides = overrides
+ result = po.get_filename('whatever')
+ self.assertEqual(result, 'foo.pt')
+ self.assertEqual(source.resource_name, '')
def test_get_filename_file_doesnt_exist(self):
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'wont_exist'))]
+ source = DummyAssetSource(filename=None)
+ overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
+ po.overrides = overrides
self.assertEqual(po.get_filename('whatever'), None)
-
+ self.assertEqual(source.resource_name, 'wont_exist')
+
def test_get_stream(self):
- import os
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'test_assets.py'))]
+ source = DummyAssetSource(stream='a stream?')
+ overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
- here = os.path.dirname(os.path.abspath(__file__))
- with po.get_stream('whatever') as stream:
- _assertBody(stream.read(), os.path.join(here, 'test_assets.py'))
+ po.overrides = overrides
+ self.assertEqual(po.get_stream('whatever'), 'a stream?')
+ self.assertEqual(source.resource_name, 'foo.pt')
def test_get_stream_file_doesnt_exist(self):
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'wont_exist'))]
+ source = DummyAssetSource(stream=None)
+ overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
+ po.overrides = overrides
self.assertEqual(po.get_stream('whatever'), None)
+ self.assertEqual(source.resource_name, 'wont_exist')
def test_get_string(self):
- import os
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'test_assets.py'))]
+ source = DummyAssetSource(string='a string')
+ overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
- here = os.path.dirname(os.path.abspath(__file__))
- _assertBody(po.get_string('whatever'),
- os.path.join(here, 'test_assets.py'))
+ po.overrides = overrides
+ self.assertEqual(po.get_string('whatever'), 'a string')
+ self.assertEqual(source.resource_name, 'foo.pt')
def test_get_string_file_doesnt_exist(self):
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'wont_exist'))]
+ source = DummyAssetSource(string=None)
+ overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
+ po.overrides = overrides
self.assertEqual(po.get_string('whatever'), None)
+ self.assertEqual(source.resource_name, 'wont_exist')
def test_has_resource(self):
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'test_assets.py'))]
+ source = DummyAssetSource(exists=True)
+ overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
+ po.overrides = overrides
self.assertEqual(po.has_resource('whatever'), True)
+ self.assertEqual(source.resource_name, 'foo.pt')
def test_has_resource_file_doesnt_exist(self):
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'wont_exist'))]
+ source = DummyAssetSource(exists=None)
+ overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
+ po.overrides = overrides
self.assertEqual(po.has_resource('whatever'), None)
+ self.assertEqual(source.resource_name, 'wont_exist')
def test_isdir_false(self):
- overrides = [ DummyOverride(
- ('pyramid.tests.test_config', 'test_assets.py'))]
+ source = DummyAssetSource(isdir=False)
+ overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
+ po.overrides = overrides
self.assertEqual(po.isdir('whatever'), False)
-
+ self.assertEqual(source.resource_name, 'foo.pt')
+
def test_isdir_true(self):
- overrides = [ DummyOverride(
- ('pyramid.tests.test_config', 'files'))]
+ source = DummyAssetSource(isdir=True)
+ overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
+ po.overrides = overrides
self.assertEqual(po.isdir('whatever'), True)
+ self.assertEqual(source.resource_name, 'foo.pt')
def test_isdir_doesnt_exist(self):
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'wont_exist'))]
+ source = DummyAssetSource(isdir=None)
+ overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
+ po.overrides = overrides
self.assertEqual(po.isdir('whatever'), None)
+ self.assertEqual(source.resource_name, 'wont_exist')
def test_listdir(self):
- overrides = [ DummyOverride(
- ('pyramid.tests.test_config', 'files'))]
+ source = DummyAssetSource(listdir=True)
+ overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
- self.assertTrue(po.listdir('whatever'))
+ po.overrides = overrides
+ self.assertEqual(po.listdir('whatever'), True)
+ self.assertEqual(source.resource_name, 'foo.pt')
def test_listdir_doesnt_exist(self):
- overrides = [ DummyOverride(None), DummyOverride(
- ('pyramid.tests.test_config', 'wont_exist'))]
+ source = DummyAssetSource(listdir=None)
+ overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
package = DummyPackage('package')
po = self._makeOne(package)
- po.overrides= overrides
+ po.overrides = overrides
self.assertEqual(po.listdir('whatever'), None)
+ self.assertEqual(source.resource_name, 'wont_exist')
# PEP 302 __loader__ extensions: use the "real" __loader__, if present.
def test_get_data_pkg_has_no___loader__(self):
@@ -570,27 +656,124 @@ class TestPackageOverrides(unittest.TestCase):
def test_get_source_pkg_has___loader__(self):
package = DummyPackage('package')
- loader = package.__loader__ = DummyLoader()
+ loader = package.__loader__ = DummyLoader()
po = self._makeOne(package)
self.assertEqual(po.get_source('whatever'), 'def foo():\n pass')
self.assertEqual(loader._got_source, 'whatever')
+class AssetSourceIntegrationTests(object):
+
+ def test_get_filename(self):
+ source = self._makeOne('')
+ self.assertEqual(source.get_filename('test_assets.py'),
+ os.path.join(here, 'test_assets.py'))
+
+ def test_get_filename_with_prefix(self):
+ source = self._makeOne('test_assets.py')
+ self.assertEqual(source.get_filename(''),
+ os.path.join(here, 'test_assets.py'))
+
+ def test_get_filename_file_doesnt_exist(self):
+ source = self._makeOne('')
+ self.assertEqual(source.get_filename('wont_exist'), None)
+
+ def test_get_stream(self):
+ source = self._makeOne('')
+ with source.get_stream('test_assets.py') as stream:
+ _assertBody(stream.read(), os.path.join(here, 'test_assets.py'))
+
+ def test_get_stream_with_prefix(self):
+ source = self._makeOne('test_assets.py')
+ with source.get_stream('') as stream:
+ _assertBody(stream.read(), os.path.join(here, 'test_assets.py'))
+
+ def test_get_stream_file_doesnt_exist(self):
+ source = self._makeOne('')
+ self.assertEqual(source.get_stream('wont_exist'), None)
+
+ def test_get_string(self):
+ source = self._makeOne('')
+ _assertBody(source.get_string('test_assets.py'),
+ os.path.join(here, 'test_assets.py'))
+
+ def test_get_string_with_prefix(self):
+ source = self._makeOne('test_assets.py')
+ _assertBody(source.get_string(''),
+ os.path.join(here, 'test_assets.py'))
+
+ def test_get_string_file_doesnt_exist(self):
+ source = self._makeOne('')
+ self.assertEqual(source.get_string('wont_exist'), None)
+
+ def test_exists(self):
+ source = self._makeOne('')
+ self.assertEqual(source.exists('test_assets.py'), True)
+
+ def test_exists_with_prefix(self):
+ source = self._makeOne('test_assets.py')
+ self.assertEqual(source.exists(''), True)
+
+ def test_exists_file_doesnt_exist(self):
+ source = self._makeOne('')
+ self.assertEqual(source.exists('wont_exist'), None)
+
+ def test_isdir_false(self):
+ source = self._makeOne('')
+ self.assertEqual(source.isdir('test_assets.py'), False)
+
+ def test_isdir_true(self):
+ source = self._makeOne('')
+ self.assertEqual(source.isdir('files'), True)
+
+ def test_isdir_doesnt_exist(self):
+ source = self._makeOne('')
+ self.assertEqual(source.isdir('wont_exist'), None)
+
+ def test_listdir(self):
+ source = self._makeOne('')
+ self.assertTrue(source.listdir('files'))
+
+ def test_listdir_doesnt_exist(self):
+ source = self._makeOne('')
+ self.assertEqual(source.listdir('wont_exist'), None)
+
+class TestPackageAssetSource(AssetSourceIntegrationTests, unittest.TestCase):
+
+ def _getTargetClass(self):
+ from pyramid.config.assets import PackageAssetSource
+ return PackageAssetSource
+
+ def _makeOne(self, prefix, package='pyramid.tests.test_config'):
+ klass = self._getTargetClass()
+ return klass(package, prefix)
+
+class TestFSAssetSource(AssetSourceIntegrationTests, unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.config.assets import FSAssetSource
+ return FSAssetSource
+
+ def _makeOne(self, prefix, base_prefix=here):
+ klass = self._getTargetClass()
+ return klass(os.path.join(base_prefix, prefix))
+
class TestDirectoryOverride(unittest.TestCase):
def _getTargetClass(self):
from pyramid.config.assets import DirectoryOverride
return DirectoryOverride
- def _makeOne(self, path, package, prefix):
+ def _makeOne(self, path, source):
klass = self._getTargetClass()
- return klass(path, package, prefix)
+ return klass(path, source)
def test_it_match(self):
- o = self._makeOne('foo/', 'package', 'bar/')
+ source = DummyAssetSource()
+ o = self._makeOne('foo/', source)
result = o('foo/something.pt')
- self.assertEqual(result, ('package', 'bar/something.pt'))
+ self.assertEqual(result, (source, 'something.pt'))
def test_it_no_match(self):
- o = self._makeOne('foo/', 'package', 'bar/')
+ source = DummyAssetSource()
+ o = self._makeOne('foo/', source)
result = o('baz/notfound.pt')
self.assertEqual(result, None)
@@ -599,17 +782,19 @@ class TestFileOverride(unittest.TestCase):
from pyramid.config.assets import FileOverride
return FileOverride
- def _makeOne(self, path, package, prefix):
+ def _makeOne(self, path, source):
klass = self._getTargetClass()
- return klass(path, package, prefix)
+ return klass(path, source)
def test_it_match(self):
- o = self._makeOne('foo.pt', 'package', 'bar.pt')
+ source = DummyAssetSource()
+ o = self._makeOne('foo.pt', source)
result = o('foo.pt')
- self.assertEqual(result, ('package', 'bar.pt'))
+ self.assertEqual(result, (source, ''))
def test_it_no_match(self):
- o = self._makeOne('foo.pt', 'package', 'bar.pt')
+ source = DummyAssetSource()
+ o = self._makeOne('foo.pt', source)
result = o('notfound.pt')
self.assertEqual(result, None)
@@ -634,8 +819,8 @@ class DummyPackageOverrides:
self.package = package
self.inserted = []
- def insert(self, path, package, prefix):
- self.inserted.append((path, package, prefix))
+ def insert(self, path, source):
+ self.inserted.append((path, source))
class DummyPkgResources:
def __init__(self):
@@ -647,6 +832,34 @@ class DummyPkgResources:
class DummyPackage:
def __init__(self, name):
self.__name__ = name
+
+class DummyAssetSource:
+ def __init__(self, **kw):
+ self.kw = kw
+
+ def get_filename(self, resource_name):
+ self.resource_name = resource_name
+ return self.kw['filename']
+
+ def get_stream(self, resource_name):
+ self.resource_name = resource_name
+ return self.kw['stream']
+
+ def get_string(self, resource_name):
+ self.resource_name = resource_name
+ return self.kw['string']
+
+ def exists(self, resource_name):
+ self.resource_name = resource_name
+ return self.kw['exists']
+
+ def isdir(self, resource_name):
+ self.resource_name = resource_name
+ return self.kw['isdir']
+
+ def listdir(self, resource_name):
+ self.resource_name = resource_name
+ return self.kw['listdir']
class DummyLoader:
_got_data = _is_package = None
@@ -664,12 +877,10 @@ class DummyLoader:
return 'def foo():\n pass'
class DummyUnderOverride:
- def __call__(self, package, path, override_package, override_prefix,
- _info=''):
+ def __call__(self, package, path, source, _info=''):
self.package = package
self.path = path
- self.override_package = override_package
- self.override_prefix = override_prefix
+ self.source = source
def read_(src):
with open(src, 'rb') as f:
diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py
index 6e679397f..0bd5336ff 100644
--- a/pyramid/tests/test_config/test_factories.py
+++ b/pyramid/tests/test_config/test_factories.py
@@ -23,6 +23,21 @@ class TestFactoriesMixin(unittest.TestCase):
self.assertEqual(config.registry.getUtility(IRequestFactory),
dummyfactory)
+ def test_set_response_factory(self):
+ from pyramid.interfaces import IResponseFactory
+ config = self._makeOne(autocommit=True)
+ factory = lambda r: object()
+ config.set_response_factory(factory)
+ self.assertEqual(config.registry.getUtility(IResponseFactory), factory)
+
+ def test_set_response_factory_dottedname(self):
+ from pyramid.interfaces import IResponseFactory
+ config = self._makeOne(autocommit=True)
+ config.set_response_factory(
+ 'pyramid.tests.test_config.dummyfactory')
+ self.assertEqual(config.registry.getUtility(IResponseFactory),
+ dummyfactory)
+
def test_set_root_factory(self):
from pyramid.interfaces import IRootFactory
config = self._makeOne()
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index d6dba17f6..aeebe3c91 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -546,6 +546,18 @@ class ConfiguratorTests(unittest.TestCase):
utility = reg.getUtility(IRequestFactory)
self.assertEqual(utility, factory)
+ def test_setup_registry_response_factory(self):
+ from pyramid.registry import Registry
+ from pyramid.interfaces import IResponseFactory
+ reg = Registry()
+ config = self._makeOne(reg)
+ factory = lambda r: object()
+ config.setup_registry(response_factory=factory)
+ self.assertEqual(reg.queryUtility(IResponseFactory), None)
+ config.commit()
+ utility = reg.getUtility(IResponseFactory)
+ self.assertEqual(utility, factory)
+
def test_setup_registry_request_factory_dottedname(self):
from pyramid.registry import Registry
from pyramid.interfaces import IRequestFactory
@@ -736,6 +748,18 @@ pyramid.tests.test_config.dummy_include2""",
else: # pragma: no cover
raise AssertionError
+ def test_include_constant_root_package(self):
+ from pyramid import tests
+ from pyramid.tests import test_config
+ config = self._makeOne(root_package=tests)
+ results = {}
+ def include(config):
+ results['package'] = config.package
+ results['root_package'] = config.root_package
+ config.include(include)
+ self.assertEqual(results['root_package'], tests)
+ self.assertEqual(results['package'], test_config)
+
def test_action_branching_kw_is_None(self):
config = self._makeOne(autocommit=True)
self.assertEqual(config.action('discrim'), None)
diff --git a/pyramid/tests/test_config/test_settings.py b/pyramid/tests/test_config/test_settings.py
index c74f96375..d2a98b347 100644
--- a/pyramid/tests/test_config/test_settings.py
+++ b/pyramid/tests/test_config/test_settings.py
@@ -57,7 +57,7 @@ class TestSettingsConfiguratorMixin(unittest.TestCase):
self.assertEqual(settings['a'], 1)
class TestSettings(unittest.TestCase):
-
+
def _getTargetClass(self):
from pyramid.config.settings import Settings
return Settings
@@ -131,6 +131,35 @@ class TestSettings(unittest.TestCase):
self.assertEqual(result['prevent_http_cache'], True)
self.assertEqual(result['pyramid.prevent_http_cache'], True)
+ def test_prevent_cachebust(self):
+ settings = self._makeOne({})
+ self.assertEqual(settings['prevent_cachebust'], False)
+ self.assertEqual(settings['pyramid.prevent_cachebust'], False)
+ result = self._makeOne({'prevent_cachebust':'false'})
+ self.assertEqual(result['prevent_cachebust'], False)
+ self.assertEqual(result['pyramid.prevent_cachebust'], False)
+ result = self._makeOne({'prevent_cachebust':'t'})
+ self.assertEqual(result['prevent_cachebust'], True)
+ self.assertEqual(result['pyramid.prevent_cachebust'], True)
+ result = self._makeOne({'prevent_cachebust':'1'})
+ self.assertEqual(result['prevent_cachebust'], True)
+ self.assertEqual(result['pyramid.prevent_cachebust'], True)
+ result = self._makeOne({'pyramid.prevent_cachebust':'t'})
+ self.assertEqual(result['prevent_cachebust'], True)
+ self.assertEqual(result['pyramid.prevent_cachebust'], True)
+ result = self._makeOne({}, {'PYRAMID_PREVENT_CACHEBUST':'1'})
+ self.assertEqual(result['prevent_cachebust'], True)
+ self.assertEqual(result['pyramid.prevent_cachebust'], True)
+ result = self._makeOne({'prevent_cachebust':'false',
+ 'pyramid.prevent_cachebust':'1'})
+ self.assertEqual(result['prevent_cachebust'], True)
+ self.assertEqual(result['pyramid.prevent_cachebust'], True)
+ result = self._makeOne({'prevent_cachebust':'false',
+ 'pyramid.prevent_cachebust':'f'},
+ {'PYRAMID_PREVENT_CACHEBUST':'1'})
+ self.assertEqual(result['prevent_cachebust'], True)
+ self.assertEqual(result['pyramid.prevent_cachebust'], True)
+
def test_reload_templates(self):
settings = self._makeOne({})
self.assertEqual(settings['reload_templates'], False)
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 57bb5e9d0..b0d03fb72 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -113,7 +113,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
config.add_view(renderer='dummy.pt')
view = self._getViewCallable(config)
self.assertRaises(ValueError, view, None, None)
-
+
def test_add_view_with_tmpl_renderer_factory_no_renderer_factory(self):
config = self._makeOne(autocommit=True)
introspector = DummyIntrospector()
@@ -136,7 +136,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
('renderer factories', '.pt') in introspector.related[-1])
view = self._getViewCallable(config)
self.assertTrue(b'Hello!' in view(None, None).body)
-
+
def test_add_view_wrapped_view_is_decorated(self):
def view(request): # request-only wrapper
""" """
@@ -1783,6 +1783,21 @@ class TestViewsConfigurationMixin(unittest.TestCase):
result = view(None, request)
self.assertEqual(result, 'OK')
+ def test_add_forbidden_view_no_view_argument(self):
+ from zope.interface import implementedBy
+ from pyramid.interfaces import IRequest
+ from pyramid.httpexceptions import HTTPForbidden
+ config = self._makeOne(autocommit=True)
+ config.setup_registry()
+ config.add_forbidden_view()
+ request = self._makeRequest(config)
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPForbidden),
+ request_iface=IRequest)
+ context = HTTPForbidden()
+ result = view(context, request)
+ self.assertEqual(result, context)
+
def test_add_forbidden_view_allows_other_predicates(self):
from pyramid.renderers import null_renderer
config = self._makeOne(autocommit=True)
@@ -1860,6 +1875,21 @@ class TestViewsConfigurationMixin(unittest.TestCase):
result = view(None, request)
self.assertEqual(result, (None, request))
+ def test_add_notfound_view_no_view_argument(self):
+ from zope.interface import implementedBy
+ from pyramid.interfaces import IRequest
+ from pyramid.httpexceptions import HTTPNotFound
+ config = self._makeOne(autocommit=True)
+ config.setup_registry()
+ config.add_notfound_view()
+ request = self._makeRequest(config)
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPNotFound),
+ request_iface=IRequest)
+ context = HTTPNotFound()
+ result = view(context, request)
+ self.assertEqual(result, context)
+
def test_add_notfound_view_allows_other_predicates(self):
from pyramid.renderers import null_renderer
config = self._makeOne(autocommit=True)
@@ -3742,8 +3772,9 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_registration_miss(self):
inst = self._makeOne()
- registrations = [(None, 'spec', 'route_name'),
- ('http://example.com/foo/', 'package:path/', None)]
+ registrations = [
+ (None, 'spec', 'route_name', None),
+ ('http://example.com/foo/', 'package:path/', None, None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
result = inst.generate('package:path/abc', request)
@@ -3751,7 +3782,8 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_registration_no_registry_on_request(self):
inst = self._makeOne()
- registrations = [('http://example.com/foo/', 'package:path/', None)]
+ registrations = [
+ ('http://example.com/foo/', 'package:path/', None, None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
del request.registry
@@ -3760,7 +3792,8 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_slash_in_name1(self):
inst = self._makeOne()
- registrations = [('http://example.com/foo/', 'package:path/', None)]
+ registrations = [
+ ('http://example.com/foo/', 'package:path/', None, None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
result = inst.generate('package:path/abc', request)
@@ -3768,7 +3801,8 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_slash_in_name2(self):
inst = self._makeOne()
- registrations = [('http://example.com/foo/', 'package:path/', None)]
+ registrations = [
+ ('http://example.com/foo/', 'package:path/', None, None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
result = inst.generate('package:path/', request)
@@ -3788,7 +3822,7 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_route_url(self):
inst = self._makeOne()
- registrations = [(None, 'package:path/', '__viewname/')]
+ registrations = [(None, 'package:path/', '__viewname/', None)]
inst._get_registrations = lambda *x: registrations
def route_url(n, **kw):
self.assertEqual(n, '__viewname/')
@@ -3801,7 +3835,7 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_url_unquoted_local(self):
inst = self._makeOne()
- registrations = [(None, 'package:path/', '__viewname/')]
+ registrations = [(None, 'package:path/', '__viewname/', None)]
inst._get_registrations = lambda *x: registrations
def route_url(n, **kw):
self.assertEqual(n, '__viewname/')
@@ -3814,7 +3848,7 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_url_quoted_remote(self):
inst = self._makeOne()
- registrations = [('http://example.com/', 'package:path/', None)]
+ registrations = [('http://example.com/', 'package:path/', None, None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
result = inst.generate('package:path/abc def', request, a=1)
@@ -3822,7 +3856,7 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_url_with_custom_query(self):
inst = self._makeOne()
- registrations = [('http://example.com/', 'package:path/', None)]
+ registrations = [('http://example.com/', 'package:path/', None, None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
result = inst.generate('package:path/abc def', request, a=1,
@@ -3832,7 +3866,7 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_url_with_custom_anchor(self):
inst = self._makeOne()
- registrations = [('http://example.com/', 'package:path/', None)]
+ registrations = [('http://example.com/', 'package:path/', None, None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
uc = text_(b'La Pe\xc3\xb1a', 'utf-8')
@@ -3841,33 +3875,57 @@ class TestStaticURLInfo(unittest.TestCase):
self.assertEqual(result,
'http://example.com/abc%20def#La%20Pe%C3%B1a')
+ def test_generate_url_cachebust(self):
+ def cachebust(subpath, kw):
+ kw['foo'] = 'bar'
+ return 'foo' + '/' + subpath, kw
+ inst = self._makeOne()
+ registrations = [(None, 'package:path/', '__viewname', cachebust)]
+ inst._get_registrations = lambda *x: registrations
+ request = self._makeRequest()
+ def route_url(n, **kw):
+ self.assertEqual(n, '__viewname')
+ self.assertEqual(kw, {'subpath':'foo/abc', 'foo':'bar'})
+ request.route_url = route_url
+ inst.generate('package:path/abc', request)
+
def test_add_already_exists(self):
inst = self._makeOne()
config = self._makeConfig(
[('http://example.com/', 'package:path/', None)])
inst.add(config, 'http://example.com', 'anotherpackage:path')
- expected = [('http://example.com/', 'anotherpackage:path/', None)]
+ expected = [
+ ('http://example.com/', 'anotherpackage:path/', None, None)]
+ self._assertRegistrations(config, expected)
+
+ def test_add_package_root(self):
+ inst = self._makeOne()
+ config = self._makeConfig()
+ inst.add(config, 'http://example.com', 'package:')
+ expected = [('http://example.com/', 'package:', None, None)]
self._assertRegistrations(config, expected)
def test_add_url_withendslash(self):
inst = self._makeOne()
config = self._makeConfig()
inst.add(config, 'http://example.com/', 'anotherpackage:path')
- expected = [('http://example.com/', 'anotherpackage:path/', None)]
+ expected = [
+ ('http://example.com/', 'anotherpackage:path/', None, None)]
self._assertRegistrations(config, expected)
def test_add_url_noendslash(self):
inst = self._makeOne()
config = self._makeConfig()
inst.add(config, 'http://example.com', 'anotherpackage:path')
- expected = [('http://example.com/', 'anotherpackage:path/', None)]
+ expected = [
+ ('http://example.com/', 'anotherpackage:path/', None, None)]
self._assertRegistrations(config, expected)
def test_add_url_noscheme(self):
inst = self._makeOne()
config = self._makeConfig()
inst.add(config, '//example.com', 'anotherpackage:path')
- expected = [('//example.com/', 'anotherpackage:path/', None)]
+ expected = [('//example.com/', 'anotherpackage:path/', None, None)]
self._assertRegistrations(config, expected)
def test_add_viewname(self):
@@ -3876,7 +3934,7 @@ class TestStaticURLInfo(unittest.TestCase):
config = self._makeConfig()
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1)
- expected = [(None, 'anotherpackage:path/', '__view/')]
+ expected = [(None, 'anotherpackage:path/', '__view/', None)]
self._assertRegistrations(config, expected)
self.assertEqual(config.route_args, ('__view/', 'view/*subpath'))
self.assertEqual(config.view_kw['permission'], NO_PERMISSION_REQUIRED)
@@ -3887,7 +3945,7 @@ class TestStaticURLInfo(unittest.TestCase):
config.route_prefix = '/abc'
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path',)
- expected = [(None, 'anotherpackage:path/', '__/abc/view/')]
+ expected = [(None, 'anotherpackage:path/', '__/abc/view/', None)]
self._assertRegistrations(config, expected)
self.assertEqual(config.route_args, ('__/abc/view/', 'view/*subpath'))
@@ -3904,7 +3962,7 @@ class TestStaticURLInfo(unittest.TestCase):
inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
context=DummyContext)
self.assertEqual(config.view_kw['context'], DummyContext)
-
+
def test_add_viewname_with_for_(self):
config = self._makeConfig()
inst = self._makeOne()
@@ -3920,6 +3978,34 @@ class TestStaticURLInfo(unittest.TestCase):
self.assertEqual(config.view_kw['renderer'],
'mypackage:templates/index.pt')
+ def test_add_cachebust_default(self):
+ config = self._makeConfig()
+ inst = self._makeOne()
+ inst._default_cachebust = DummyCacheBuster
+ inst.add(config, 'view', 'mypackage:path', cachebust=True)
+ cachebust = config.registry._static_url_registrations[0][3]
+ subpath, kw = cachebust('some/path', {})
+ self.assertEqual(subpath, 'some/path')
+ self.assertEqual(kw['x'], 'foo')
+
+ def test_add_cachebust_prevented(self):
+ config = self._makeConfig()
+ config.registry.settings['pyramid.prevent_cachebust'] = True
+ inst = self._makeOne()
+ inst.add(config, 'view', 'mypackage:path', cachebust=True)
+ cachebust = config.registry._static_url_registrations[0][3]
+ self.assertEqual(cachebust, None)
+
+ def test_add_cachebust_custom(self):
+ config = self._makeConfig()
+ inst = self._makeOne()
+ inst.add(config, 'view', 'mypackage:path',
+ cachebust=DummyCacheBuster())
+ cachebust = config.registry._static_url_registrations[0][3]
+ subpath, kw = cachebust('some/path', {})
+ self.assertEqual(subpath, 'some/path')
+ self.assertEqual(kw['x'], 'foo')
+
class Test_view_description(unittest.TestCase):
def _callFUT(self, view):
from pyramid.config.views import view_description
@@ -3939,7 +4025,8 @@ class Test_view_description(unittest.TestCase):
class DummyRegistry:
- pass
+ def __init__(self):
+ self.settings = {}
from zope.interface import implementer
from pyramid.interfaces import IResponse
@@ -4025,6 +4112,13 @@ class DummyMultiView:
def __permitted__(self, context, request):
""" """
+class DummyCacheBuster(object):
+ def token(self, pathspec):
+ return 'foo'
+ def pregenerate(self, token, subpath, kw):
+ kw['x'] = token
+ return subpath, kw
+
def parse_httpdate(s):
import datetime
# cannot use %Z, must use literal GMT; Jython honors timezone
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index 35648ed38..c2786c391 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -81,7 +81,7 @@ class StaticAppBase(IntegrationBase):
res = self.testapp.get('/static/.hiddenfile', status=200)
_assertBody(res.body, os.path.join(here, 'fixtures/static/.hiddenfile'))
- if defaultlocale is not None:
+ if defaultlocale is not None: # pragma: no cover
# These tests are expected to fail on LANG=C systems due to decode
# errors and on non-Linux systems due to git highchar handling
# vagaries
diff --git a/pyramid/tests/test_registry.py b/pyramid/tests/test_registry.py
index 11019b852..50f49f24d 100644
--- a/pyramid/tests/test_registry.py
+++ b/pyramid/tests/test_registry.py
@@ -12,6 +12,11 @@ class TestRegistry(unittest.TestCase):
registry = self._makeOne()
self.assertEqual(registry.__nonzero__(), True)
+ def test_package_name(self):
+ package_name = 'testing'
+ registry = self._getTargetClass()(package_name)
+ self.assertEqual(registry.package_name, package_name)
+
def test_registerHandler_and_notify(self):
registry = self._makeOne()
self.assertEqual(registry.has_listeners, False)
diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py
index bb5b0455a..30fdef051 100644
--- a/pyramid/tests/test_renderers.py
+++ b/pyramid/tests/test_renderers.py
@@ -182,7 +182,10 @@ class TestRendererHelper(unittest.TestCase):
from pyramid.interfaces import IResponseFactory
class ResponseFactory(object):
pass
- self.config.registry.registerUtility(ResponseFactory, IResponseFactory)
+
+ self.config.registry.registerUtility(
+ lambda r: ResponseFactory(), IResponseFactory
+ )
def test_render_to_response(self):
self._registerRendererFactory()
@@ -327,7 +330,9 @@ class TestRendererHelper(unittest.TestCase):
class ResponseFactory(object):
def __init__(self):
pass
- self.config.registry.registerUtility(ResponseFactory, IResponseFactory)
+ self.config.registry.registerUtility(
+ lambda r: ResponseFactory(), IResponseFactory
+ )
request = testing.DummyRequest()
helper = self._makeOne('loo.foo')
response = helper._make_response(b'abc', request)
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index ed41b62ff..48af98f59 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -1,3 +1,4 @@
+from collections import deque
import unittest
from pyramid import testing
@@ -119,13 +120,13 @@ class TestRequest(unittest.TestCase):
def test_add_response_callback(self):
inst = self._makeOne()
- self.assertEqual(inst.response_callbacks, ())
+ self.assertEqual(inst.response_callbacks, None)
def callback(request, response):
""" """
inst.add_response_callback(callback)
- self.assertEqual(inst.response_callbacks, [callback])
+ self.assertEqual(list(inst.response_callbacks), [callback])
inst.add_response_callback(callback)
- self.assertEqual(inst.response_callbacks, [callback, callback])
+ self.assertEqual(list(inst.response_callbacks), [callback, callback])
def test__process_response_callbacks(self):
inst = self._makeOne()
@@ -135,24 +136,48 @@ class TestRequest(unittest.TestCase):
def callback2(request, response):
request.called2 = True
response.called2 = True
- inst.response_callbacks = [callback1, callback2]
+ inst.add_response_callback(callback1)
+ inst.add_response_callback(callback2)
response = DummyResponse()
inst._process_response_callbacks(response)
self.assertEqual(inst.called1, True)
self.assertEqual(inst.called2, True)
self.assertEqual(response.called1, True)
self.assertEqual(response.called2, True)
- self.assertEqual(inst.response_callbacks, [])
+ self.assertEqual(len(inst.response_callbacks), 0)
+
+ def test__process_response_callback_adding_response_callback(self):
+ """
+ When a response callback adds another callback, that new callback should still be called.
+
+ See https://github.com/Pylons/pyramid/pull/1373
+ """
+ inst = self._makeOne()
+ def callback1(request, response):
+ request.called1 = True
+ response.called1 = True
+ request.add_response_callback(callback2)
+ def callback2(request, response):
+ request.called2 = True
+ response.called2 = True
+ inst.add_response_callback(callback1)
+ response = DummyResponse()
+ inst._process_response_callbacks(response)
+ self.assertEqual(inst.called1, True)
+ self.assertEqual(inst.called2, True)
+ self.assertEqual(response.called1, True)
+ self.assertEqual(response.called2, True)
+ self.assertEqual(len(inst.response_callbacks), 0)
def test_add_finished_callback(self):
inst = self._makeOne()
- self.assertEqual(inst.finished_callbacks, ())
+ self.assertEqual(inst.finished_callbacks, None)
def callback(request):
""" """
inst.add_finished_callback(callback)
- self.assertEqual(inst.finished_callbacks, [callback])
+ self.assertEqual(list(inst.finished_callbacks), [callback])
inst.add_finished_callback(callback)
- self.assertEqual(inst.finished_callbacks, [callback, callback])
+ self.assertEqual(list(inst.finished_callbacks), [callback, callback])
def test__process_finished_callbacks(self):
inst = self._makeOne()
@@ -160,11 +185,12 @@ class TestRequest(unittest.TestCase):
request.called1 = True
def callback2(request):
request.called2 = True
- inst.finished_callbacks = [callback1, callback2]
+ inst.add_finished_callback(callback1)
+ inst.add_finished_callback(callback2)
inst._process_finished_callbacks()
self.assertEqual(inst.called1, True)
self.assertEqual(inst.called2, True)
- self.assertEqual(inst.finished_callbacks, [])
+ self.assertEqual(len(inst.finished_callbacks), 0)
def test_resource_url(self):
self._registerResourceURL()
diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py
index e6d90f979..ad55882c9 100644
--- a/pyramid/tests/test_response.py
+++ b/pyramid/tests/test_response.py
@@ -1,4 +1,5 @@
import io
+import mimetypes
import os
import unittest
from pyramid import testing
@@ -7,7 +8,7 @@ class TestResponse(unittest.TestCase):
def _getTargetClass(self):
from pyramid.response import Response
return Response
-
+
def test_implements_IResponse(self):
from pyramid.interfaces import IResponse
cls = self._getTargetClass()
@@ -23,22 +24,62 @@ class TestFileResponse(unittest.TestCase):
from pyramid.response import FileResponse
return FileResponse(file, **kw)
- def _getPath(self):
+ def _getPath(self, suffix='txt'):
here = os.path.dirname(__file__)
- return os.path.join(here, 'fixtures', 'minimal.txt')
+ return os.path.join(here, 'fixtures', 'minimal.%s' % (suffix,))
- def test_with_content_type(self):
- path = self._getPath()
+ def test_with_image_content_type(self):
+ path = self._getPath('jpg')
r = self._makeOne(path, content_type='image/jpeg')
self.assertEqual(r.content_type, 'image/jpeg')
+ self.assertEqual(r.headers['content-type'], 'image/jpeg')
+ path = self._getPath()
r.app_iter.close()
- def test_without_content_type(self):
- path = self._getPath()
- r = self._makeOne(path)
- self.assertEqual(r.content_type, 'text/plain')
+ def test_with_xml_content_type(self):
+ path = self._getPath('xml')
+ r = self._makeOne(path, content_type='application/xml')
+ self.assertEqual(r.content_type, 'application/xml')
+ self.assertEqual(r.headers['content-type'],
+ 'application/xml; charset=UTF-8')
+ r.app_iter.close()
+
+ def test_with_pdf_content_type(self):
+ path = self._getPath('xml')
+ r = self._makeOne(path, content_type='application/pdf')
+ self.assertEqual(r.content_type, 'application/pdf')
+ self.assertEqual(r.headers['content-type'], 'application/pdf')
r.app_iter.close()
+ def test_without_content_type(self):
+ for suffix in ('txt', 'xml', 'pdf'):
+ path = self._getPath(suffix)
+ r = self._makeOne(path)
+ self.assertEqual(r.headers['content-type'].split(';')[0],
+ mimetypes.guess_type(path, strict=False)[0])
+ r.app_iter.close()
+
+ def test_python_277_bug_15207(self):
+ # python 2.7.7 on windows has a bug where its mimetypes.guess_type
+ # function returns Unicode for the content_type, unlike any previous
+ # version of Python. See https://github.com/Pylons/pyramid/issues/1360
+ # for more information.
+ from pyramid.compat import text_
+ import mimetypes as old_mimetypes
+ from pyramid import response
+ class FakeMimetypesModule(object):
+ def guess_type(self, *arg, **kw):
+ return text_('foo/bar'), None
+ fake_mimetypes = FakeMimetypesModule()
+ try:
+ response.mimetypes = fake_mimetypes
+ path = self._getPath('xml')
+ r = self._makeOne(path)
+ self.assertEqual(r.content_type, 'foo/bar')
+ self.assertEqual(type(r.content_type), str)
+ finally:
+ response.mimetypes = old_mimetypes
+
class TestFileIter(unittest.TestCase):
def _makeOne(self, file, block_size):
from pyramid.response import FileIter
@@ -78,7 +119,7 @@ class Test_patch_mimetypes(unittest.TestCase):
result = self._callFUT(module)
self.assertEqual(result, True)
self.assertEqual(module.initted, True)
-
+
def test_missing_init(self):
class DummyMimetypes(object):
pass
@@ -133,6 +174,17 @@ class TestResponseAdapter(unittest.TestCase):
self.assertEqual(dummy_venusian.attached,
[(foo, dec.register, 'pyramid')])
+
+class TestGetResponseFactory(unittest.TestCase):
+ def test_get_factory(self):
+ from pyramid.registry import Registry
+ from pyramid.response import Response, _get_response_factory
+
+ registry = Registry()
+ response = _get_response_factory(registry)(None)
+ self.assertTrue(isinstance(response, Response))
+
+
class Dummy(object):
pass
@@ -149,5 +201,3 @@ class DummyVenusian(object):
def attach(self, wrapped, fn, category=None):
self.attached.append((wrapped, fn, category))
-
-
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index 838e52db0..c6c6eea1c 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -522,7 +522,7 @@ class TestRouter(unittest.TestCase):
def view(context, request):
def callback(request, response):
response.called_back = True
- request.response_callbacks = [callback]
+ request.add_response_callback(callback)
return response
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
@@ -545,7 +545,7 @@ class TestRouter(unittest.TestCase):
def view(context, request):
def callback(request):
request.environ['called_back'] = True
- request.finished_callbacks = [callback]
+ request.add_finished_callback(callback)
return response
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
@@ -567,7 +567,7 @@ class TestRouter(unittest.TestCase):
def view(context, request):
def callback(request):
request.environ['called_back'] = True
- request.finished_callbacks = [callback]
+ request.add_finished_callback(callback)
raise NotImplementedError
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
diff --git a/pyramid/tests/test_scripts/pystartup.py b/pyramid/tests/test_scripts/pystartup.py
new file mode 100644
index 000000000..c4e5bcc80
--- /dev/null
+++ b/pyramid/tests/test_scripts/pystartup.py
@@ -0,0 +1 @@
+foo = 1
diff --git a/pyramid/tests/test_scripts/test_pcreate.py b/pyramid/tests/test_scripts/test_pcreate.py
index 6516ac229..020721ca7 100644
--- a/pyramid/tests/test_scripts/test_pcreate.py
+++ b/pyramid/tests/test_scripts/test_pcreate.py
@@ -7,7 +7,7 @@ class TestPCreateCommand(unittest.TestCase):
def out(self, msg):
self.out_.write(msg)
-
+
def _getTargetClass(self):
from pyramid.scripts.pcreate import PCreateCommand
return PCreateCommand
@@ -25,7 +25,7 @@ class TestPCreateCommand(unittest.TestCase):
self.assertEqual(result, 0)
out = self.out_.getvalue()
self.assertTrue(out.startswith('Available scaffolds'))
-
+
def test_run_show_scaffolds_none_exist(self):
cmd = self._makeOne('-l')
cmd.scaffolds = []
@@ -33,7 +33,7 @@ class TestPCreateCommand(unittest.TestCase):
self.assertEqual(result, 0)
out = self.out_.getvalue()
self.assertTrue(out.startswith('No scaffolds available'))
-
+
def test_run_no_scaffold_name(self):
cmd = self._makeOne()
result = cmd.run()
@@ -61,6 +61,7 @@ class TestPCreateCommand(unittest.TestCase):
cmd = self._makeOne('-s', 'dummy', 'Distro')
scaffold = DummyScaffold('dummy')
cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.1")
result = cmd.run()
self.assertEqual(result, 0)
self.assertEqual(
@@ -69,14 +70,34 @@ class TestPCreateCommand(unittest.TestCase):
)
self.assertEqual(
scaffold.vars,
- {'project': 'Distro', 'egg': 'Distro', 'package': 'distro'})
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
+
+ def test_scaffold_with_hyphen_in_project_name(self):
+ import os
+ cmd = self._makeOne('-s', 'dummy', 'Distro-')
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.1")
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.output_dir,
+ os.path.normpath(os.path.join(os.getcwd(), 'Distro-'))
+ )
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Distro-', 'egg': 'Distro_', 'package': 'distro_',
+ 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
def test_known_scaffold_absolute_path(self):
import os
path = os.path.abspath('Distro')
cmd = self._makeOne('-s', 'dummy', path)
+ cmd.pyramid_dist = DummyDist("0.1")
scaffold = DummyScaffold('dummy')
cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.1")
result = cmd.run()
self.assertEqual(result, 0)
self.assertEqual(
@@ -85,7 +106,8 @@ class TestPCreateCommand(unittest.TestCase):
)
self.assertEqual(
scaffold.vars,
- {'project': 'Distro', 'egg': 'Distro', 'package': 'distro'})
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
def test_known_scaffold_multiple_rendered(self):
import os
@@ -93,6 +115,7 @@ class TestPCreateCommand(unittest.TestCase):
scaffold1 = DummyScaffold('dummy1')
scaffold2 = DummyScaffold('dummy2')
cmd.scaffolds = [scaffold1, scaffold2]
+ cmd.pyramid_dist = DummyDist("0.1")
result = cmd.run()
self.assertEqual(result, 0)
self.assertEqual(
@@ -101,20 +124,23 @@ class TestPCreateCommand(unittest.TestCase):
)
self.assertEqual(
scaffold1.vars,
- {'project': 'Distro', 'egg': 'Distro', 'package': 'distro'})
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
self.assertEqual(
scaffold2.output_dir,
os.path.normpath(os.path.join(os.getcwd(), 'Distro'))
)
self.assertEqual(
scaffold2.vars,
- {'project': 'Distro', 'egg': 'Distro', 'package': 'distro'})
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
def test_known_scaffold_with_path_as_project_target_rendered(self):
import os
cmd = self._makeOne('-s', 'dummy', '/tmp/foo/Distro/')
scaffold = DummyScaffold('dummy')
cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.1")
result = cmd.run()
self.assertEqual(result, 0)
self.assertEqual(
@@ -123,8 +149,73 @@ class TestPCreateCommand(unittest.TestCase):
)
self.assertEqual(
scaffold.vars,
- {'project': 'Distro', 'egg': 'Distro', 'package': 'distro'})
-
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
+
+
+ def test_scaffold_with_prod_pyramid_version(self):
+ cmd = self._makeOne('-s', 'dummy', 'Distro')
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.2")
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': '0.2', 'pyramid_docs_branch':'0.2-branch'})
+
+ def test_scaffold_with_prod_pyramid_long_version(self):
+ cmd = self._makeOne('-s', 'dummy', 'Distro')
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.2.1")
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': '0.2.1', 'pyramid_docs_branch':'0.2-branch'})
+
+ def test_scaffold_with_prod_pyramid_unparsable_version(self):
+ cmd = self._makeOne('-s', 'dummy', 'Distro')
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("abc")
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': 'abc', 'pyramid_docs_branch':'latest'})
+
+ def test_scaffold_with_dev_pyramid_version(self):
+ cmd = self._makeOne('-s', 'dummy', 'Distro')
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.12dev")
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': '0.12dev',
+ 'pyramid_docs_branch': 'master'})
+
+ def test_scaffold_with_dev_pyramid_long_version(self):
+ cmd = self._makeOne('-s', 'dummy', 'Distro')
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.10.1dev")
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
+ 'pyramid_version': '0.10.1dev',
+ 'pyramid_docs_branch': 'master'})
+
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.pcreate import main
@@ -142,4 +233,7 @@ class DummyScaffold(object):
self.command = command
self.output_dir = output_dir
self.vars = vars
-
+
+class DummyDist(object):
+ def __init__(self, version):
+ self.version = version
diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py
index 37f1d3c0f..95cec0518 100644
--- a/pyramid/tests/test_scripts/test_prequest.py
+++ b/pyramid/tests/test_scripts/test_prequest.py
@@ -210,8 +210,21 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._path_info, '/')
self.assertEqual(self._spec, 'development.ini')
self.assertEqual(self._app_name, None)
+
self.assertEqual(self._out, [b'abc'])
+ def test_command_method_configures_logging(self):
+ command = self._makeOne(['', 'development.ini', '/'])
+ called_args = []
+
+ def configure_logging(app_spec):
+ called_args.append(app_spec)
+
+ command.configure_logging = configure_logging
+ command.run()
+ self.assertEqual(called_args, ['development.ini'])
+
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.prequest import main
diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py
index 25a3cd2e3..32202af4b 100644
--- a/pyramid/tests/test_scripts/test_proutes.py
+++ b/pyramid/tests/test_scripts/test_proutes.py
@@ -123,8 +123,53 @@ class TestPRoutesCommand(unittest.TestCase):
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
compare_to = L[-1].split()[:3]
- self.assertEqual(compare_to, ['a', '/a', '<function'])
-
+ self.assertEqual(
+ compare_to,
+ ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view']
+ )
+
+ def test_one_route_with_long_name_one_view_registered(self):
+ from zope.interface import Interface
+ from pyramid.registry import Registry
+ from pyramid.interfaces import IRouteRequest
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IView
+ registry = Registry()
+ def view():pass
+
+ class IMyRoute(Interface):
+ pass
+
+ registry.registerAdapter(
+ view,
+ (IViewClassifier, IMyRoute, Interface),
+ IView, ''
+ )
+
+ registry.registerUtility(IMyRoute, IRouteRequest,
+ name='very_long_name_123')
+
+ command = self._makeOne()
+ route = dummy.DummyRoute(
+ 'very_long_name_123',
+ '/and_very_long_pattern_as_well'
+ )
+ mapper = dummy.DummyMapper(route)
+ command._get_mapper = lambda *arg: mapper
+ L = []
+ command.out = L.append
+ command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ result = command.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(len(L), 3)
+ compare_to = L[-1].split()[:3]
+ self.assertEqual(
+ compare_to,
+ ['very_long_name_123',
+ '/and_very_long_pattern_as_well',
+ 'pyramid.tests.test_scripts.test_proutes.view']
+ )
+
def test_single_route_one_view_registered_with_factory(self):
from zope.interface import Interface
from pyramid.registry import Registry
@@ -154,6 +199,47 @@ class TestPRoutesCommand(unittest.TestCase):
self.assertEqual(len(L), 3)
self.assertEqual(L[-1].split()[:3], ['a', '/a', '<unknown>'])
+ def test_single_route_multiview_registered(self):
+ from zope.interface import Interface
+ from pyramid.registry import Registry
+ from pyramid.interfaces import IRouteRequest
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IMultiView
+
+ registry = Registry()
+
+ def view(): pass
+
+ class IMyRoute(Interface):
+ pass
+
+ multiview1 = dummy.DummyMultiView(
+ view, context='context',
+ view_name='a1'
+ )
+
+ registry.registerAdapter(
+ multiview1,
+ (IViewClassifier, IMyRoute, Interface),
+ IMultiView, ''
+ )
+ registry.registerUtility(IMyRoute, IRouteRequest, name='a')
+ command = self._makeOne()
+ route = dummy.DummyRoute('a', '/a')
+ mapper = dummy.DummyMapper(route)
+ command._get_mapper = lambda *arg: mapper
+ L = []
+ command.out = L.append
+ command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ result = command.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(len(L), 3)
+ compare_to = L[-1].split()[:3]
+ self.assertEqual(
+ compare_to,
+ ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view']
+ )
+
def test__get_mapper(self):
from pyramid.registry import Registry
from pyramid.urldispatch import RoutesMapper
@@ -161,7 +247,7 @@ class TestPRoutesCommand(unittest.TestCase):
registry = Registry()
result = command._get_mapper(registry)
self.assertEqual(result.__class__, RoutesMapper)
-
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.proutes import main
@@ -170,4 +256,3 @@ class Test_main(unittest.TestCase):
def test_it(self):
result = self._callFUT(['proutes'])
self.assertEqual(result, 2)
-
diff --git a/pyramid/tests/test_scripts/test_pshell.py b/pyramid/tests/test_scripts/test_pshell.py
index 7cb130c41..a6ba2eaea 100644
--- a/pyramid/tests/test_scripts/test_pshell.py
+++ b/pyramid/tests/test_scripts/test_pshell.py
@@ -1,3 +1,4 @@
+import os
import unittest
from pyramid.tests.test_scripts import dummy
@@ -24,6 +25,9 @@ class TestPShellCommand(unittest.TestCase):
self.options.python_shell = ''
self.options.setup = None
cmd.options = self.options
+ # default to None to prevent side-effects from running tests in
+ # unknown environments
+ cmd.pystartup = None
return cmd
def test_make_default_shell(self):
@@ -369,6 +373,25 @@ class TestPShellCommand(unittest.TestCase):
self.assertTrue(self.bootstrap.closer.called)
self.assertTrue(shell.help)
+ def test_command_loads_pythonstartup(self):
+ command = self._makeOne()
+ command.pystartup = (
+ os.path.abspath(
+ os.path.join(
+ os.path.dirname(__file__),
+ 'pystartup.py')))
+ shell = dummy.DummyShell()
+ command.run(shell)
+ self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
+ self.assertEqual(shell.env, {
+ 'app':self.bootstrap.app, 'root':self.bootstrap.root,
+ 'registry':self.bootstrap.registry,
+ 'request':self.bootstrap.request,
+ 'root_factory':self.bootstrap.root_factory,
+ 'foo':1,
+ })
+ self.assertTrue(self.bootstrap.closer.called)
+ self.assertTrue(shell.help)
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
diff --git a/pyramid/tests/test_security.py b/pyramid/tests/test_security.py
index 6f08a100c..6d75ac8e3 100644
--- a/pyramid/tests/test_security.py
+++ b/pyramid/tests/test_security.py
@@ -134,9 +134,9 @@ class TestRemember(unittest.TestCase):
def tearDown(self):
testing.tearDown()
- def _callFUT(self, *arg):
+ def _callFUT(self, *arg, **kwarg):
from pyramid.security import remember
- return remember(*arg)
+ return remember(*arg, **kwarg)
def test_no_authentication_policy(self):
request = _makeRequest()
@@ -159,6 +159,19 @@ class TestRemember(unittest.TestCase):
result = self._callFUT(request, 'me')
self.assertEqual(result, [('X-Pyramid-Test', 'me')])
+ def test_with_deprecated_principal_arg(self):
+ request = _makeRequest()
+ registry = request.registry
+ _registerAuthenticationPolicy(registry, 'yo')
+ result = self._callFUT(request, principal='me')
+ self.assertEqual(result, [('X-Pyramid-Test', 'me')])
+
+ def test_with_missing_arg(self):
+ request = _makeRequest()
+ registry = request.registry
+ _registerAuthenticationPolicy(registry, 'yo')
+ self.assertRaises(TypeError, lambda: self._callFUT(request))
+
class TestForget(unittest.TestCase):
def setUp(self):
testing.setUp()
@@ -462,8 +475,8 @@ class DummyAuthenticationPolicy:
def authenticated_userid(self, request):
return self.result
- def remember(self, request, principal, **kw):
- headers = [(_TEST_HEADER, principal)]
+ def remember(self, request, userid, **kw):
+ headers = [(_TEST_HEADER, userid)]
self._header_remembered = headers[0]
return headers
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index 35c234e99..b013ffa66 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -521,7 +521,7 @@ class Test_manage_accessed(unittest.TestCase):
result = wrapper(session, 'a')
self.assertEqual(result, 1)
callbacks = request.response_callbacks
- self.assertEqual(len(callbacks), 0)
+ if callbacks is not None: self.assertEqual(len(callbacks), 0)
class Test_manage_changed(unittest.TestCase):
def _makeOne(self, wrapped):
diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py
index 94497d4f6..2f4de249e 100644
--- a/pyramid/tests/test_static.py
+++ b/pyramid/tests/test_static.py
@@ -26,7 +26,7 @@ class Test_static_view_use_subpath_False(unittest.TestCase):
if kw is not None:
environ.update(kw)
return Request(environ=environ)
-
+
def test_ctor_defaultargs(self):
inst = self._makeOne('package:resource_name')
self.assertEqual(inst.package_name, 'package')
@@ -110,6 +110,14 @@ class Test_static_view_use_subpath_False(unittest.TestCase):
response = inst(context, request)
self.assertTrue(b'<html>static</html>' in response.body)
+ def test_cachebust_match(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ inst.cachebust_match = lambda subpath: subpath[1:]
+ request = self._makeRequest({'PATH_INFO':'/foo/index.html'})
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertTrue(b'<html>static</html>' in response.body)
+
def test_resource_is_file_with_wsgi_file_wrapper(self):
from pyramid.response import _BLOCK_SIZE
inst = self._makeOne('pyramid.tests:fixtures/static')
@@ -218,7 +226,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase):
if kw is not None:
environ.update(kw)
return Request(environ=environ)
-
+
def test_ctor_defaultargs(self):
inst = self._makeOne('package:resource_name')
self.assertEqual(inst.package_name, 'package')
@@ -273,7 +281,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase):
context = DummyContext()
from pyramid.httpexceptions import HTTPNotFound
self.assertRaises(HTTPNotFound, inst, context, request)
-
+
def test_oob_os_sep(self):
import os
inst = self._makeOne('pyramid.tests:fixtures/static')
@@ -360,6 +368,155 @@ class Test_static_view_use_subpath_True(unittest.TestCase):
from pyramid.httpexceptions import HTTPNotFound
self.assertRaises(HTTPNotFound, inst, context, request)
+class TestMd5AssetTokenGenerator(unittest.TestCase):
+ _fspath = None
+ _tmp = None
+
+ @property
+ def fspath(self):
+ if self._fspath:
+ return self._fspath
+
+ import os
+ import tempfile
+ self._tmp = tmp = tempfile.mkdtemp()
+ self._fspath = os.path.join(tmp, 'test.txt')
+ return self._fspath
+
+ def tearDown(self):
+ import shutil
+ if self._tmp:
+ shutil.rmtree(self._tmp)
+
+ def _makeOne(self):
+ from pyramid.static import Md5AssetTokenGenerator as cls
+ return cls()
+
+ def test_package_resource(self):
+ fut = self._makeOne().token
+ expected = '76d653a3a044e2f4b38bb001d283e3d9'
+ token = fut('pyramid.tests:fixtures/static/index.html')
+ self.assertEqual(token, expected)
+
+ def test_filesystem_resource(self):
+ fut = self._makeOne().token
+ expected = 'd5155f250bef0e9923e894dbc713c5dd'
+ with open(self.fspath, 'w') as f:
+ f.write("Are we rich yet?")
+ token = fut(self.fspath)
+ self.assertEqual(token, expected)
+
+ def test_cache(self):
+ fut = self._makeOne().token
+ expected = 'd5155f250bef0e9923e894dbc713c5dd'
+ with open(self.fspath, 'w') as f:
+ f.write("Are we rich yet?")
+ token = fut(self.fspath)
+ self.assertEqual(token, expected)
+
+ # md5 shouldn't change because we've cached it
+ with open(self.fspath, 'w') as f:
+ f.write("Sorry for the convenience.")
+ token = fut(self.fspath)
+ self.assertEqual(token, expected)
+
+class TestPathSegmentMd5CacheBuster(unittest.TestCase):
+
+ def _makeOne(self):
+ from pyramid.static import PathSegmentMd5CacheBuster as cls
+ inst = cls()
+ inst.token = lambda pathspec: 'foo'
+ return inst
+
+ def test_token(self):
+ fut = self._makeOne().token
+ self.assertEqual(fut('whatever'), 'foo')
+
+ def test_pregenerate(self):
+ fut = self._makeOne().pregenerate
+ self.assertEqual(fut('foo', ('bar',), 'kw'), (('foo', 'bar'), 'kw'))
+
+ def test_match(self):
+ fut = self._makeOne().match
+ self.assertEqual(fut(('foo', 'bar')), ('bar',))
+
+class TestQueryStringMd5CacheBuster(unittest.TestCase):
+
+ def _makeOne(self, param=None):
+ from pyramid.static import QueryStringMd5CacheBuster as cls
+ if param:
+ inst = cls(param)
+ else:
+ inst = cls()
+ inst.token = lambda pathspec: 'foo'
+ return inst
+
+ def test_token(self):
+ fut = self._makeOne().token
+ self.assertEqual(fut('whatever'), 'foo')
+
+ def test_pregenerate(self):
+ fut = self._makeOne().pregenerate
+ self.assertEqual(
+ fut('foo', ('bar',), {}),
+ (('bar',), {'_query': {'x': 'foo'}}))
+
+ def test_pregenerate_change_param(self):
+ fut = self._makeOne('y').pregenerate
+ self.assertEqual(
+ fut('foo', ('bar',), {}),
+ (('bar',), {'_query': {'y': 'foo'}}))
+
+ def test_pregenerate_query_is_already_tuples(self):
+ fut = self._makeOne().pregenerate
+ self.assertEqual(
+ fut('foo', ('bar',), {'_query': [('a', 'b')]}),
+ (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))}))
+
+ def test_pregenerate_query_is_tuple_of_tuples(self):
+ fut = self._makeOne().pregenerate
+ self.assertEqual(
+ fut('foo', ('bar',), {'_query': (('a', 'b'),)}),
+ (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))}))
+
+class TestQueryStringConstantCacheBuster(TestQueryStringMd5CacheBuster):
+
+ def _makeOne(self, param=None):
+ from pyramid.static import QueryStringConstantCacheBuster as cls
+ if param:
+ inst = cls('foo', param)
+ else:
+ inst = cls('foo')
+ return inst
+
+ def test_token(self):
+ fut = self._makeOne().token
+ self.assertEqual(fut('whatever'), 'foo')
+
+ def test_pregenerate(self):
+ fut = self._makeOne().pregenerate
+ self.assertEqual(
+ fut('foo', ('bar',), {}),
+ (('bar',), {'_query': {'x': 'foo'}}))
+
+ def test_pregenerate_change_param(self):
+ fut = self._makeOne('y').pregenerate
+ self.assertEqual(
+ fut('foo', ('bar',), {}),
+ (('bar',), {'_query': {'y': 'foo'}}))
+
+ def test_pregenerate_query_is_already_tuples(self):
+ fut = self._makeOne().pregenerate
+ self.assertEqual(
+ fut('foo', ('bar',), {'_query': [('a', 'b')]}),
+ (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))}))
+
+ def test_pregenerate_query_is_tuple_of_tuples(self):
+ fut = self._makeOne().pregenerate
+ self.assertEqual(
+ fut('foo', ('bar',), {'_query': (('a', 'b'),)}),
+ (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))}))
+
class DummyContext:
pass
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index da57c6301..113f7e5f4 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -217,7 +217,7 @@ class TestDummyRequest(unittest.TestCase):
def test_add_response_callback(self):
request = self._makeOne()
request.add_response_callback(1)
- self.assertEqual(request.response_callbacks, [1])
+ self.assertEqual(list(request.response_callbacks), [1])
def test_registry_is_config_registry_when_setup_is_called_after_ctor(self):
# see https://github.com/Pylons/pyramid/issues/165
@@ -259,7 +259,9 @@ class TestDummyRequest(unittest.TestCase):
registry = Registry('this_test')
class ResponseFactory(object):
pass
- registry.registerUtility(ResponseFactory, IResponseFactory)
+ registry.registerUtility(
+ lambda r: ResponseFactory(), IResponseFactory
+ )
request = self._makeOne()
request.registry = registry
resp = request.response
@@ -347,6 +349,7 @@ class Test_setUp(unittest.TestCase):
self.assertEqual(config.registry, current['registry'])
self.assertEqual(current['registry'].__class__, Registry)
self.assertEqual(current['request'], None)
+ self.assertEqual(config.package.__name__, 'pyramid.tests')
self._assertSMHook(get_current_registry)
def test_it_with_registry(self):
@@ -364,6 +367,10 @@ class Test_setUp(unittest.TestCase):
current = manager.get()
self.assertEqual(current['request'], request)
+ def test_it_with_package(self):
+ config = self._callFUT(package='pyramid')
+ self.assertEqual(config.package.__name__, 'pyramid')
+
def test_it_with_hook_zca_false(self):
from pyramid.registry import Registry
registry = Registry()
diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py
index 2ca4c4a66..ac5ea0683 100644
--- a/pyramid/tests/test_util.py
+++ b/pyramid/tests/test_util.py
@@ -217,6 +217,49 @@ class Test_WeakOrderedSet(unittest.TestCase):
self.assertEqual(list(wos), [])
self.assertEqual(wos.last, None)
+class Test_strings_differ(unittest.TestCase):
+ def _callFUT(self, *args, **kw):
+ from pyramid.util import strings_differ
+ return strings_differ(*args, **kw)
+
+ def test_it(self):
+ self.assertFalse(self._callFUT(b'foo', b'foo'))
+ self.assertTrue(self._callFUT(b'123', b'345'))
+ self.assertTrue(self._callFUT(b'1234', b'123'))
+ self.assertTrue(self._callFUT(b'123', b'1234'))
+
+ def test_it_with_internal_comparator(self):
+ result = self._callFUT(b'foo', b'foo', compare_digest=None)
+ self.assertFalse(result)
+
+ result = self._callFUT(b'123', b'abc', compare_digest=None)
+ self.assertTrue(result)
+
+ def test_it_with_external_comparator(self):
+ class DummyComparator(object):
+ called = False
+ def __init__(self, ret_val):
+ self.ret_val = ret_val
+
+ def __call__(self, a, b):
+ self.called = True
+ return self.ret_val
+
+ dummy_compare = DummyComparator(True)
+ result = self._callFUT(b'foo', b'foo', compare_digest=dummy_compare)
+ self.assertTrue(dummy_compare.called)
+ self.assertFalse(result)
+
+ dummy_compare = DummyComparator(False)
+ result = self._callFUT(b'123', b'345', compare_digest=dummy_compare)
+ self.assertTrue(dummy_compare.called)
+ self.assertTrue(result)
+
+ dummy_compare = DummyComparator(False)
+ result = self._callFUT(b'abc', b'abc', compare_digest=dummy_compare)
+ self.assertTrue(dummy_compare.called)
+ self.assertTrue(result)
+
class Test_object_description(unittest.TestCase):
def _callFUT(self, object):
from pyramid.util import object_description
@@ -281,7 +324,7 @@ class Test_object_description(unittest.TestCase):
self.assertEqual(
self._callFUT(inst),
"object %s" % str(inst))
-
+
def test_shortened_repr(self):
inst = ['1'] * 1000
self.assertEqual(
@@ -549,7 +592,7 @@ class TestActionInfo(unittest.TestCase):
def _getTargetClass(self):
from pyramid.util import ActionInfo
return ActionInfo
-
+
def _makeOne(self, filename, lineno, function, linerepr):
return self._getTargetClass()(filename, lineno, function, linerepr)
diff --git a/pyramid/tests/test_wsgi.py b/pyramid/tests/test_wsgi.py
index 63499b43b..4ddbc9201 100644
--- a/pyramid/tests/test_wsgi.py
+++ b/pyramid/tests/test_wsgi.py
@@ -5,6 +5,9 @@ class WSGIAppTests(unittest.TestCase):
from pyramid.wsgi import wsgiapp
return wsgiapp(app)
+ def test_wsgiapp_none(self):
+ self.assertRaises(ValueError, self._callFUT, None)
+
def test_decorator(self):
context = DummyContext()
request = DummyRequest()
@@ -25,6 +28,9 @@ class WSGIApp2Tests(unittest.TestCase):
from pyramid.wsgi import wsgiapp2
return wsgiapp2(app)
+ def test_wsgiapp2_none(self):
+ self.assertRaises(ValueError, self._callFUT, None)
+
def test_decorator_with_subpath_and_view_name(self):
context = DummyContext()
request = DummyRequest()
diff --git a/pyramid/url.py b/pyramid/url.py
index bf4d4ff48..a0f3d7f2f 100644
--- a/pyramid/url.py
+++ b/pyramid/url.py
@@ -223,7 +223,7 @@ class URLMethodsMixin(object):
named portion in the generated URL. For example, if you pass
``_host='foo.com'``, and the URL that would have been generated
without the host replacement is ``http://example.com/a``, the result
- will be ``https://foo.com/a``.
+ will be ``http://foo.com/a``.
Note that if ``_scheme`` is passed as ``https``, and ``_port`` is not
passed, the ``_port`` value is assumed to have been passed as
@@ -414,7 +414,7 @@ class URLMethodsMixin(object):
portion in the generated URL. For example, if you pass
``host='foo.com'``, and the URL that would have been generated
without the host replacement is ``http://example.com/a``, the result
- will be ``https://foo.com/a``.
+ will be ``http://foo.com/a``.
If ``scheme`` is passed as ``https``, and an explicit ``port`` is not
passed, the ``port`` value is assumed to have been passed as ``443``.
diff --git a/pyramid/util.py b/pyramid/util.py
index 6b92f17fc..4ca2937a1 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -1,4 +1,9 @@
import functools
+try:
+ # py2.7.7+ and py3.3+ have native comparison support
+ from hmac import compare_digest
+except ImportError: # pragma: nocover
+ compare_digest = None
import inspect
import traceback
import weakref
@@ -10,6 +15,10 @@ from pyramid.exceptions import (
CyclicDependencyError,
)
+from pyramid.interfaces import (
+ IResponseFactory,
+ )
+
from pyramid.compat import (
iteritems_,
is_nonstr_iter,
@@ -20,6 +29,7 @@ from pyramid.compat import (
)
from pyramid.interfaces import IActionInfo
+from pyramid.response import Response
from pyramid.path import DottedNameResolver as _DottedNameResolver
class DottedNameResolver(_DottedNameResolver):
@@ -227,7 +237,7 @@ class WeakOrderedSet(object):
oid = self._order[-1]
return self._items[oid]()
-def strings_differ(string1, string2):
+def strings_differ(string1, string2, compare_digest=compare_digest):
"""Check whether two strings differ while avoiding timing attacks.
This function returns True if the given strings differ and False
@@ -237,14 +247,25 @@ def strings_differ(string1, string2):
http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf
- """
- if len(string1) != len(string2):
- return True
-
- invalid_bits = 0
- for a, b in zip(string1, string2):
- invalid_bits += a != b
+ .. versionchanged:: 1.6
+ Support :func:`hmac.compare_digest` if it is available (Python 2.7.7+
+ and Python 3.3+).
+ """
+ len_eq = len(string1) == len(string2)
+ if len_eq:
+ invalid_bits = 0
+ left = string1
+ else:
+ invalid_bits = 1
+ left = string2
+ right = string2
+
+ if compare_digest is not None:
+ invalid_bits += not compare_digest(left, right)
+ else:
+ for a, b in zip(left, right):
+ invalid_bits += a != b
return invalid_bits != 0
def object_description(object):
@@ -534,4 +555,3 @@ def action_method(wrapped):
functools.update_wrapper(wrapper, wrapped)
wrapper.__docobj__ = wrapped
return wrapper
-
diff --git a/pyramid/wsgi.py b/pyramid/wsgi.py
index d176e4ce5..1c1bded32 100644
--- a/pyramid/wsgi.py
+++ b/pyramid/wsgi.py
@@ -29,6 +29,10 @@ def wsgiapp(wrapped):
view.
"""
+
+ if wrapped is None:
+ raise ValueError('wrapped can not be None')
+
def decorator(context, request):
return request.get_response(wrapped)
@@ -69,6 +73,9 @@ def wsgiapp2(wrapped):
subpath is used as the ``SCRIPT_NAME``. The new environment is passed to
the downstream WSGI application."""
+ if wrapped is None:
+ raise ValueError('wrapped can not be None')
+
def decorator(context, request):
return call_app_with_subpath_as_path_info(request, wrapped)
diff --git a/rtd.txt b/rtd.txt
index b449ac73c..4aecd9933 100644
--- a/rtd.txt
+++ b/rtd.txt
@@ -1,4 +1,4 @@
+Sphinx >= 1.2.3
repoze.sphinx.autointerface
repoze.lru
pylons_sphinx_latesturl
-
diff --git a/setup.cfg b/setup.cfg
index a877ffb7f..9633b6980 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -7,6 +7,7 @@ where=pyramid
nocapture=1
cover-package=pyramid
cover-erase=1
+cover-min-percentage=100
[aliases]
dev = develop easy_install pyramid[testing]
diff --git a/setup.py b/setup.py
index 0ace211d7..629b77f3d 100644
--- a/setup.py
+++ b/setup.py
@@ -30,7 +30,7 @@ else:
here = os.path.abspath(os.path.dirname(__file__))
try:
- with open(os.path.join(here, 'README.txt')) as f:
+ with open(os.path.join(here, 'README.rst')) as f:
README = f.read()
with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
@@ -56,7 +56,7 @@ if not PY3:
tests_require.append('zope.component>=3.11.0')
docs_extras = [
- 'Sphinx',
+ 'Sphinx >= 1.2.3',
'docutils',
'repoze.sphinx.autointerface',
]
@@ -72,6 +72,7 @@ setup(name='pyramid',
description='The Pyramid Web Framework, a Pylons project',
long_description=README + '\n\n' + CHANGES,
classifiers=[
+ "Development Status :: 6 - Mature",
"Intended Audience :: Developers",
"Programming Language :: Python",
"Programming Language :: Python :: 2.6",
@@ -90,7 +91,7 @@ setup(name='pyramid',
keywords='web wsgi pylons pyramid',
author="Chris McDonough, Agendaless Consulting",
author_email="pylons-discuss@googlegroups.com",
- url="http://pylonsproject.org",
+ url="http://docs.pylonsproject.org/en/latest/docs/pyramid.html",
license="BSD-derived (http://www.repoze.org/LICENSE.txt)",
packages=find_packages(),
include_package_data=True,
diff --git a/tox.ini b/tox.ini
index 2bf213ca4..714c5b6d3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist =
- py26,py27,py32,py33,py34,pypy,cover
+ py26,py27,py32,py33,py34,pypy,pypy3,cover
[testenv]
commands =